import { describe, expect, it, vi } from 'vitest'; import { buildApp } from '../../src/app.js'; import { loadConfig } from '../../src/config.js'; const base = { DATABASE_URL: 'postgres://localhost/staff', STANDALONE_TENANT_ID: '01J00000000000000000000000', STANDALONE_ACTOR_ID: '01J00000000000000000000001', LOG_LEVEL: 'silent', NODE_ENV: 'test', } as const; const managerToken = 'm'.repeat(64); const terminalToken = 't'.repeat(64); const tokenConfig = { STANDALONE_MANAGER_TOKEN: managerToken, STANDALONE_TERMINAL_TOKEN: terminalToken, } as const; function fakeDatabase(rows: Array>>) { const calls: Array<[string, readonly unknown[] | undefined]> = []; const query = async (sql: string, parameters?: readonly unknown[]) => { calls.push([sql, parameters]); return { rows: rows.shift() ?? [], rowCount: 1 }; }; return { calls, database: { ping: vi.fn(async () => undefined), close: vi.fn(async () => undefined), tenantTransaction: async ( _tenant: string, _actor: string, run: (client: { query: typeof query }) => Promise, ) => run({ query }), } as never, }; } describe('attendance portal', () => { it('requires authentication when standalone tokens are configured', async () => { const { database } = fakeDatabase([]); const app = await buildApp({ config: loadConfig({ ...base, ...tokenConfig, STANDALONE_ACCESS: 'manager' }), database, }); const response = await app.inject('/api/v1/staff/portal'); expect(response.statusCode).toBe(401); await app.close(); }); it('creates an HttpOnly manager session and accepts its secure cookie', async () => { const { database } = fakeDatabase([[], [], []]); const app = await buildApp({ config: loadConfig({ ...base, ...tokenConfig, STANDALONE_ACCESS: 'manager' }), database, }); const login = await app.inject({ method: 'POST', url: '/api/v1/staff/session', payload: { access_token: managerToken }, }); const setCookie = login.headers['set-cookie']; expect(login.statusCode).toBe(200); expect(setCookie).toContain('__Host-krona_staff_session='); expect(setCookie).toContain('HttpOnly'); expect(setCookie).toContain('Secure'); expect(setCookie).toContain('SameSite=Strict'); const response = await app.inject({ url: '/api/v1/staff/portal', headers: { cookie: String(setCookie).split(';')[0] }, }); expect(response.statusCode).toBe(200); expect(response.json<{ viewer: { role: string } }>().viewer.role).toBe('manager'); await app.close(); }); it('does not allow the terminal key to create a browser session', async () => { const { database } = fakeDatabase([]); const app = await buildApp({ config: loadConfig({ ...base, ...tokenConfig, STANDALONE_ACCESS: 'manager' }), database, }); const response = await app.inject({ method: 'POST', url: '/api/v1/staff/session', payload: { access_token: terminalToken }, }); expect(response.statusCode).toBe(403); expect(response.headers['set-cookie']).toBeUndefined(); await app.close(); }); it('scopes an employee portal to the linked Core user', async () => { const { database, calls } = fakeDatabase([[], [], []]); const app = await buildApp({ config: loadConfig({ ...base, STANDALONE_ACCESS: 'employee' }), database }); const response = await app.inject('/api/v1/staff/portal'); expect(response.statusCode).toBe(200); const body = response.json<{ viewer: { role: string } }>(); expect(body.viewer.role).toBe('employee'); expect(calls).toHaveLength(3); expect(calls.every((call) => call[1]?.[0] === false && call[1]?.[1] === base.STANDALONE_ACTOR_ID)).toBe(true); await app.close(); }); it('writes a terminal event and its outbox notification atomically', async () => { const eventId = '01J00000000000000000000002'; const employeeId = '01J00000000000000000000003'; const { database, calls } = fakeDatabase([[], [{ id: eventId }], []]); const app = await buildApp({ config: loadConfig({ ...base, STANDALONE_ACCESS: 'terminal' }), database }); const response = await app.inject({ method: 'POST', url: '/api/v1/staff/attendance/events', payload: { event_id: eventId, employee_id: employeeId, event_type: 'check_in', occurred_at: '2026-09-24T01:00:00.000Z', source_event_id: eventId }, }); expect(response.statusCode).toBe(201); expect(calls).toHaveLength(3); expect(calls[0]?.[0]).toContain('staff_employees'); expect(calls[2]?.[0]).toContain('staff_outbox'); expect(calls[2]?.[1]?.[2]).toBe('attendance.clock.in'); await app.close(); }); it('does not give an employee manager access to the timesheet', async () => { const { database } = fakeDatabase([]); const app = await buildApp({ config: loadConfig({ ...base, STANDALONE_ACCESS: 'employee' }), database }); const response = await app.inject({ method: 'PUT', url: '/api/v1/staff/timesheets/01J00000000000000000000003/2026-09-24', payload: { minutes_worked: 480, status: 'approved' } }); expect(response.statusCode).toBe(403); await app.close(); }); });