import { describe, it, expect, beforeEach } from 'vitest'; import type { AuditInput } from '../db/browser-session-repo.js'; import { assertProfileOwner } from './browser-session-auth.js'; interface FakeRepo { audit: (input: AuditInput) => void; rows: AuditInput[]; } function makeFakeRepo(): FakeRepo { const rows: AuditInput[] = []; return { rows, audit(input: AuditInput) { rows.push(input); }, }; } describe('assertProfileOwner — fail-closed owner enforcement', () => { let fake: FakeRepo; beforeEach(() => { fake = makeFakeRepo(); }); it('passes when job.ownerId equals profile.ownerId', () => { expect(() => assertProfileOwner( { id: 7, ownerId: 'user-a' }, { id: 'job-1', ownerId: 'user-a' }, fake, ), ).not.toThrow(); expect(fake.rows.length).toBe(0); }); it('throws and audits when job.ownerId is null (legacy / dev-mode jobs)', () => { expect(() => assertProfileOwner( { id: 7, ownerId: 'user-a' }, { id: 'job-1', ownerId: null }, fake, ), ).toThrow('Browser session profile owner mismatch'); expect(fake.rows).toHaveLength(1); expect(fake.rows[0]).toMatchObject({ actorUserId: null, ownerId: 'user-a', profileId: 7, action: 'use', result: 'error', jobId: 'job-1', }); expect(fake.rows[0]!.reason).toContain('job.owner=null'); expect(fake.rows[0]!.reason).toContain('profile.owner=user-a'); }); it('throws and audits when job.ownerId is undefined', () => { expect(() => assertProfileOwner( { id: 7, ownerId: 'user-a' }, { id: 'job-2', ownerId: undefined }, fake, ), ).toThrow('Browser session profile owner mismatch'); expect(fake.rows).toHaveLength(1); expect(fake.rows[0]!.reason).toContain('job.owner=null'); }); it('throws and audits when job.ownerId is empty string', () => { expect(() => assertProfileOwner( { id: 7, ownerId: 'user-a' }, { id: 'job-3', ownerId: '' }, fake, ), ).toThrow('Browser session profile owner mismatch'); expect(fake.rows).toHaveLength(1); }); it('throws and audits when job.ownerId differs from profile.ownerId', () => { expect(() => assertProfileOwner( { id: 9, ownerId: 'user-a' }, { id: 'job-4', ownerId: 'user-b' }, fake, ), ).toThrow('Browser session profile owner mismatch'); expect(fake.rows).toHaveLength(1); expect(fake.rows[0]!.reason).toContain('job.owner=user-b'); expect(fake.rows[0]!.reason).toContain('profile.owner=user-a'); expect(fake.rows[0]!.actorUserId).toBe('user-b'); }); });