test(phase-02): add Nyquist validation tests — fill SEC-05, AUTH-08, SEC-03 and frontend gaps

8 test files, 60 new tests (14 backend + 46 frontend). All green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-05-31 12:04:21 +02:00
co-authored by Claude Sonnet 4.6
parent 6c79f92d70
commit d98e3ab7a1
8 changed files with 1381 additions and 0 deletions
+223
View File
@@ -0,0 +1,223 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
// Mock api/client.js — no real HTTP calls
vi.mock('../../api/client.js', () => ({
login: vi.fn(),
register: vi.fn(),
logout: vi.fn(),
logoutAll: vi.fn(),
refreshToken: vi.fn(),
getMyQuota: vi.fn(),
}))
import { useAuthStore } from '../auth.js'
import * as api from '../../api/client.js'
// ── Fake localStorage / sessionStorage to detect any writes ──────────────────
// happy-dom may not provide window.localStorage, so we install our own stubs
// and check whether they were called.
const fakeLocalStorage = {
_store: {},
setItem: vi.fn(),
getItem: vi.fn(key => null),
removeItem: vi.fn(),
clear: vi.fn(),
}
const fakeSessionStorage = {
_store: {},
setItem: vi.fn(),
getItem: vi.fn(key => null),
removeItem: vi.fn(),
clear: vi.fn(),
}
// Install stubs globally before tests
Object.defineProperty(globalThis, 'localStorage', {
value: fakeLocalStorage,
writable: true,
configurable: true,
})
Object.defineProperty(globalThis, 'sessionStorage', {
value: fakeSessionStorage,
writable: true,
configurable: true,
})
beforeEach(() => {
setActivePinia(createPinia())
vi.clearAllMocks()
// Reset the storage spies
fakeLocalStorage.setItem.mockClear()
fakeSessionStorage.setItem.mockClear()
})
afterEach(() => {
// nothing to restore — vi.clearAllMocks() handles it
})
// ── Security invariant: no browser storage writes ─────────────────────────────
describe('useAuthStore — no browser storage writes (security invariant)', () => {
it('login() never writes accessToken to localStorage', async () => {
api.login.mockResolvedValue({
access_token: 'test-access-token',
user: { id: '1', handle: 'alice', email: 'alice@example.com', role: 'user', totp_enabled: false },
})
const store = useAuthStore()
await store.login('alice@example.com', 'password')
// accessToken must be in memory, NOT localStorage
expect(fakeLocalStorage.setItem).not.toHaveBeenCalled()
})
it('login() never writes accessToken to sessionStorage', async () => {
api.login.mockResolvedValue({
access_token: 'test-access-token',
user: { id: '1', handle: 'alice', email: 'alice@example.com', role: 'user', totp_enabled: false },
})
const store = useAuthStore()
await store.login('alice@example.com', 'password')
expect(fakeSessionStorage.setItem).not.toHaveBeenCalled()
})
it('login() stores accessToken in memory ref (not null)', async () => {
api.login.mockResolvedValue({
access_token: 'eyJhbGciOiJIUzI1NiJ9.test',
user: { id: '1', handle: 'alice', email: 'alice@example.com', role: 'user', totp_enabled: false },
})
const store = useAuthStore()
await store.login('alice@example.com', 'password')
expect(store.accessToken).toBe('eyJhbGciOiJIUzI1NiJ9.test')
})
it('logout() clears accessToken from memory without writing to any storage', async () => {
api.login.mockResolvedValue({
access_token: 'some-token',
user: { id: '1', handle: 'bob', email: 'bob@example.com', role: 'user', totp_enabled: false },
})
api.logout.mockResolvedValue(null)
const store = useAuthStore()
await store.login('bob@example.com', 'pass')
expect(store.accessToken).toBe('some-token')
await store.logout()
expect(store.accessToken).toBeNull()
// No storage writes during logout either
expect(fakeLocalStorage.setItem).not.toHaveBeenCalledWith(
expect.stringMatching(/token|auth|access/i),
expect.anything()
)
})
})
// ── login() passes totp_code to API ──────────────────────────────────────────
describe('useAuthStore — login() forwards TOTP/backup codes', () => {
it('login() with options.totpCode sends totp_code in API request body', async () => {
api.login.mockResolvedValue({
access_token: 'tok',
user: { id: '1', handle: 'u', email: 'u@x.com', role: 'user', totp_enabled: true },
})
const store = useAuthStore()
await store.login('u@x.com', 'pass', { totpCode: '123456' })
expect(api.login).toHaveBeenCalledWith(
expect.objectContaining({ totp_code: '123456' })
)
})
it('login() with options.backupCode sends backup_code in API request body', async () => {
api.login.mockResolvedValue({
access_token: 'tok',
user: { id: '1', handle: 'u', email: 'u@x.com', role: 'user', totp_enabled: true },
})
const store = useAuthStore()
await store.login('u@x.com', 'pass', { backupCode: 'ABC12345' })
expect(api.login).toHaveBeenCalledWith(
expect.objectContaining({ backup_code: 'ABC12345' })
)
})
it('login() without options sends null for both totp_code and backup_code', async () => {
api.login.mockResolvedValue({
access_token: 'tok',
user: { id: '1', handle: 'u', email: 'u@x.com', role: 'user', totp_enabled: false },
})
const store = useAuthStore()
await store.login('u@x.com', 'pass')
expect(api.login).toHaveBeenCalledWith(
expect.objectContaining({
totp_code: null,
backup_code: null,
})
)
})
it('login() returns { requires_totp: true } when server demands TOTP', async () => {
api.login.mockResolvedValue({ requires_totp: true })
const store = useAuthStore()
const result = await store.login('u@x.com', 'pass')
expect(result).toEqual({ requires_totp: true })
// accessToken must remain null — no tokens were issued
expect(store.accessToken).toBeNull()
})
it('login() returns { requires_password_change: true, user_id } when forced change', async () => {
api.login.mockResolvedValue({ requires_password_change: true, user_id: 'uid-99' })
const store = useAuthStore()
const result = await store.login('u@x.com', 'pass')
expect(result).toEqual({ requires_password_change: true, user_id: 'uid-99' })
expect(store.accessToken).toBeNull()
})
})
// ── login() field mapping edge cases ─────────────────────────────────────────
describe('useAuthStore — login() API field mapping', () => {
it('sends email and password as-is', async () => {
api.login.mockResolvedValue({
access_token: 'tok',
user: { id: '1', handle: 'u', email: 'test@example.com', role: 'user', totp_enabled: false },
})
const store = useAuthStore()
await store.login('test@example.com', 'S3cr3tP@ss!')
expect(api.login).toHaveBeenCalledWith(
expect.objectContaining({
email: 'test@example.com',
password: 'S3cr3tP@ss!',
})
)
})
it('sets user in state after successful login', async () => {
const userData = { id: 'user-1', handle: 'alice', email: 'alice@example.com', role: 'user', totp_enabled: false }
api.login.mockResolvedValue({ access_token: 'tok', user: userData })
const store = useAuthStore()
await store.login('alice@example.com', 'pass')
expect(store.user).toEqual(userData)
})
})