import { Prisma } from '../../../generated/prisma'; import { PrismaService } from '../../prisma/prisma.service'; import { claimVoiceCaptures, deferVoiceClaim, releaseStaleVoiceClaims, settleVoiceFailure, settleVoiceSuccess, VoiceClaim, } from './voice-claims'; function fakeDb() { const queryRaw = jest.fn().mockResolvedValue([]); const executeRaw = jest.fn().mockResolvedValue(1); return { db: { $queryRaw: queryRaw, $executeRaw: executeRaw, } as unknown as PrismaService, queryRaw, executeRaw, }; } const claim: VoiceClaim = { id: 'c1', claimToken: 't1', attempts: 0, ownerUserId: 'u1', colonyId: 'k1', audioMime: 'audio/webm', durationSec: 30, }; function sqlOf(call: unknown): Prisma.Sql { return (call as unknown[])[0] as Prisma.Sql; } describe('voice claims (SQL shape)', () => { it('claims with SKIP LOCKED against the database clock', async () => { const { db, queryRaw } = fakeDb(); await claimVoiceCaptures(db, 'transcribe', { limit: 5, maxTotalSec: 600 }); const text = sqlOf(queryRaw.mock.calls[0]).sql; expect(text).toContain('FOR UPDATE SKIP LOCKED'); expect(text).toContain('now()'); expect(text).toContain('gen_random_uuid()'); }); it.each([0, -1, 1.5])('refuses the claim limit %p', async (limit) => { const { db } = fakeDb(); await expect(claimVoiceCaptures(db, 'extract', { limit })).rejects.toThrow( RangeError, ); }); it('refuses an unknown phase', async () => { const { db } = fakeDb(); await expect( claimVoiceCaptures(db, 'drop' as never, { limit: 1 }), ).rejects.toThrow(/Unknown voice phase/); }); it('settles by compare-and-set on status and claim token', async () => { const { db, executeRaw } = fakeDb(); await expect(settleVoiceSuccess(db, 'transcribe', claim)).resolves.toBe( true, ); const { sql, values } = sqlOf(executeRaw.mock.calls[0]); expect(sql).toContain('"claimToken"'); expect(values).toEqual( expect.arrayContaining(['c1', 't1', 'TRANSCRIBING']), ); }); it('reports a lost claim when no row changed', async () => { const { db, executeRaw } = fakeDb(); executeRaw.mockResolvedValue(0); await expect(settleVoiceSuccess(db, 'extract', claim)).resolves.toBe(false); await expect(deferVoiceClaim(db, 'extract', claim, 1000)).resolves.toBe( false, ); }); it('decides retry or failed from the attempts before', async () => { const { db } = fakeDb(); await expect( settleVoiceFailure( db, 'transcribe', { ...claim, attempts: 1 }, 'TRANSCRIBE_FAILED', ), ).resolves.toBe('retry'); await expect( settleVoiceFailure( db, 'transcribe', { ...claim, attempts: 2 }, 'TRANSCRIBE_FAILED', ), ).resolves.toBe('failed'); }); it('reports a lost failure settle', async () => { const { db, executeRaw } = fakeDb(); executeRaw.mockResolvedValue(0); await expect( settleVoiceFailure(db, 'extract', claim, 'EXTRACT_RETRY'), ).resolves.toBe('lost'); }); it('turns the last EXTRACT_RETRY into EXTRACT_FAILED', async () => { const { db, executeRaw } = fakeDb(); await settleVoiceFailure( db, 'extract', { ...claim, attempts: 2 }, 'EXTRACT_RETRY', ); const { values } = sqlOf(executeRaw.mock.calls[0]); expect(values).toContain('EXTRACT_FAILED'); expect(values).not.toContain('EXTRACT_RETRY'); }); it.each([-1, 1.5])('refuses the defer delay %p', async (delay) => { const { db } = fakeDb(); await expect(deferVoiceClaim(db, 'extract', claim, delay)).rejects.toThrow( RangeError, ); }); it('releases stale claims with the phase stale window and attempt limit', async () => { const { db, queryRaw } = fakeDb(); await releaseStaleVoiceClaims(db, 'extract'); const { values } = sqlOf(queryRaw.mock.calls[0]); expect(values).toContain(300_000); expect(values).toContain(3); }); });