// apps/hq/test/auth.test.ts import { describe, it, expect } from 'vitest' import { openDb } from '../src/db' import { createStaff, login, verifySession } from '../src/auth' describe('hq auth', () => { it('logs in with correct password, rejects wrong one', async () => { const db = openDb(':memory:') await createStaff(db, { email: 'admin@tecnostac.com', displayName: 'Owner', role: 'owner', password: 'let-me-in-9' }) expect(await login(db, 'admin@tecnostac.com', 'wrong')).toMatchObject({ ok: false, reason: 'invalid' }) const ok = await login(db, 'admin@tecnostac.com', 'let-me-in-9') expect(ok.ok).toBe(true) const staff = await verifySession(db, ok.ok ? ok.token : '') expect(staff).toMatchObject({ role: 'owner' }) }) it('rejects passwords under 8 chars at creation', async () => { const db = openDb(':memory:') await expect( createStaff(db, { email: 'a@b.c', displayName: 'X', role: 'staff', password: 'short' }), ).rejects.toThrow(/8/) }) it('locks the account after repeated failures, then a wrong-but-formerly-valid attempt is barred (D24)', async () => { const db = openDb(':memory:') await createStaff(db, { email: 'lock@t.in', displayName: 'L', role: 'staff', password: 'correct-horse-9' }) for (let i = 0; i < 8; i++) expect((await login(db, 'lock@t.in', 'nope')).ok).toBe(false) // Now locked — even the CORRECT password is refused with reason 'locked'. expect(await login(db, 'lock@t.in', 'correct-horse-9')).toMatchObject({ ok: false, reason: 'locked' }) }) it('a successful login clears the failure counter (D24)', async () => { const db = openDb(':memory:') await createStaff(db, { email: 'reset@t.in', displayName: 'R', role: 'staff', password: 'correct-horse-9' }) for (let i = 0; i < 3; i++) await login(db, 'reset@t.in', 'nope') expect((await login(db, 'reset@t.in', 'correct-horse-9')).ok).toBe(true) const row = await db.get<{ failed_count: number; locked_until: string | null }>( `SELECT failed_count, locked_until FROM staff_user WHERE email='reset@t.in'`) expect(row).toMatchObject({ failed_count: 0, locked_until: null }) }) it('an unknown email still returns invalid (constant-time, no enumeration) (D24)', async () => { const db = openDb(':memory:') expect(await login(db, 'ghost@nowhere.in', 'whatever')).toMatchObject({ ok: false, reason: 'invalid' }) }) })