import { createSttProxyHandler, runSttChain, type SttQuotaReservationSnapshot, } from './handler.ts' import type { SttProvider, SttProviderInput } from './providers.ts' import type { SttOutcome } from '../_shared/stt-contract.ts' function assert(condition: boolean, message: string): asserts condition { if (!condition) throw new Error(message) } function assertEquals(actual: unknown, expected: unknown, message: string): void { const a = JSON.stringify(actual) const e = JSON.stringify(expected) if (a !== e) throw new Error(`${message}: expected ${e}, got ${a}`) } const OK: SttOutcome = { kind: 'ok', result: { transcript: '안녕하세요', confidence: 0.98, language_code: 'ko', duration_seconds: 1, provider: 'groq', }, } function fakeProvider(id: string, outcome: SttOutcome | (() => Promise)): SttProvider & { calls: number } { const provider = { id, calls: 0, transcribe(_input: SttProviderInput): Promise { provider.calls += 1 return typeof outcome === 'function' ? outcome() : Promise.resolve(outcome) }, } return provider } const retryable = (failure: `http_${number}` | 'timeout' | 'network_error', options: { billed?: boolean unavailable?: boolean } = {}): SttOutcome => ({ kind: 'retryable', failure, billed: options.billed ?? false, unavailable: options.unavailable ?? false, }) const INPUT: SttProviderInput = { audio: new Blob([new Uint8Array([1, 2, 3])], { type: 'audio/wav' }), fileName: 'audio.wav', contentType: 'audio/wav', languageCode: 'ko', hints: { keyterms: [], prompt: '' }, } // --- runSttChain (pure fallback policy) --------------------------------------- Deno.test('an empty transcript is terminal: the next paid provider is never called', async () => { const groq = fakeProvider('groq', { kind: 'no_speech' }) const openai = fakeProvider('openai', OK) const deepgram = fakeProvider('deepgram', OK) const chain = await runSttChain([groq, openai, deepgram], INPUT) assertEquals(chain.kind, 'no_speech', 'empty transcript result') assert(chain.billed, 'a 2xx answer is billed') assertEquals([openai.calls, deepgram.calls], [0, 0], 'no fallthrough after a 2xx answer') }) Deno.test('a 5xx or 429 falls through to the next provider', async () => { const gateway = fakeProvider('gateway', retryable('http_500')) const groq = fakeProvider('groq', retryable('http_429')) const openai = fakeProvider('openai', OK) const chain = await runSttChain([gateway, groq, openai], INPUT) assertEquals(chain.kind, 'ok', 'third provider answers') assertEquals(chain.attempts, [ { provider: 'gateway', failure: 'http_500' }, { provider: 'groq', failure: 'http_429' }, ], 'attempts carry fixed codes only') }) Deno.test('a billed but malformed answer falls through yet marks the chain billed', async () => { const chain = await runSttChain([ fakeProvider('groq', retryable('http_502')), fakeProvider('openai', { kind: 'retryable', failure: 'invalid_response', billed: true, unavailable: false }), fakeProvider('deepgram', retryable('timeout')), ], INPUT) assert(chain.kind === 'failed', 'no usable result') assert(chain.billed, 'openai billed the audio') assert(!chain.unavailable, 'real failures are 502') }) Deno.test('no providers, or only gateway 503, is reported as unavailable', async () => { const none = await runSttChain([], INPUT) assert(none.kind === 'failed' && none.unavailable, 'nothing configured → 503') const only503 = await runSttChain([fakeProvider('gateway', retryable('http_503', { unavailable: true }))], INPUT) assert(only503.kind === 'failed' && only503.unavailable, 'gateway 503 only → 503') const mixed = await runSttChain([ fakeProvider('gateway', retryable('http_503', { unavailable: true })), fakeProvider('groq', retryable('http_500')), ], INPUT) assert(mixed.kind === 'failed' && !mixed.unavailable, 'a real failure makes it 502') }) Deno.test('a provider that throws is contained as a retryable error', async () => { const chain = await runSttChain([ fakeProvider('groq', () => Promise.reject(new TypeError('error sending request for url (https://nas.internal/x)'))), fakeProvider('openai', OK), ], INPUT) assertEquals(chain.kind, 'ok', 'fallback still runs') assertEquals(chain.attempts, [{ provider: 'groq', failure: 'error' }], 'no raw message in attempts') }) // --- createSttProxyHandler ---------------------------------------------------- interface Harness { handler: (req: Request) => Promise finalizeCalls: Array<{ reservationId: string; succeeded: boolean }> events: string[] } function harness(providers: SttProvider[], options: { reservation?: Partial finalizeStatus?: 'completed' | 'released' } = {}): Harness { const finalizeCalls: Harness['finalizeCalls'] = [] const events: string[] = [] const tracked = providers.map((provider) => ({ id: provider.id, transcribe(input: SttProviderInput): Promise { events.push(`provider:${provider.id}`) return provider.transcribe(input) }, })) const handler = createSttProxyHandler({ authenticate: () => Promise.resolve({ ok: true, user: { id: 'user-1' } }), quota: { reserve(_userId, reservationId) { events.push('reserve') return Promise.resolve({ allowed: true, reservationId, status: 'reserved', current: 1, limit: 10, period: 'daily', tier: 'free', overageCredits: 0, ...options.reservation, }) }, finalize(reservationId, succeeded) { events.push(`finalize:${succeeded}`) finalizeCalls.push({ reservationId, succeeded }) return Promise.resolve(options.finalizeStatus ?? (succeeded ? 'completed' : 'released')) }, }, dictionary: { hintsFor: () => Promise.resolve({ keyterms: [], prompt: '' }) }, providers: () => tracked, logger: { warn: () => undefined, error: () => undefined }, newReservationId: () => 'res-1', }) return { handler, finalizeCalls, events } } function sttRequest(): Request { const form = new FormData() form.append('audio', new Blob([new Uint8Array([1, 2, 3, 4])], { type: 'audio/ogg' }), 'a.ogg') form.append('language_code', 'ko') return new Request('http://localhost/stt-proxy', { method: 'POST', body: form }) } Deno.test('silent audio consumes the reservation and answers stt_no_speech without fan-out', async () => { const groq = fakeProvider('groq', { kind: 'no_speech' }) const openai = fakeProvider('openai', OK) const deepgram = fakeProvider('deepgram', OK) const h = harness([groq, openai, deepgram]) const response = await h.handler(sttRequest()) assertEquals(response.status, 422, 'no speech status') assertEquals((await response.json()).error, 'stt_no_speech', 'fixed error code') assertEquals(h.finalizeCalls, [{ reservationId: 'res-1', succeeded: true }], 'reservation consumed, not refunded') assertEquals([openai.calls, deepgram.calls], [0, 0], 'no further paid providers') }) Deno.test('a success reserves before providers and finalizes before answering', async () => { const h = harness([fakeProvider('groq', OK)]) const response = await h.handler(sttRequest()) assertEquals(response.status, 200, 'success status') assertEquals((await response.json()).transcript, '안녕하세요', 'transcript returned') assertEquals(h.events, ['reserve', 'provider:groq', 'finalize:true'], 'quota ordering') }) Deno.test('a finalize that is not completed fails closed', async () => { const h = harness([fakeProvider('groq', OK)], { finalizeStatus: 'released' }) const response = await h.handler(sttRequest()) assertEquals(response.status, 500, 'reclaimed reservation is an internal error') assertEquals((await response.json()).error, 'internal_error', 'sanitized error') }) Deno.test('failures nobody billed refund the reservation and return 502', async () => { const h = harness([fakeProvider('gateway', retryable('network_error')), fakeProvider('groq', retryable('http_500'))]) const response = await h.handler(sttRequest()) assertEquals(response.status, 502, 'upstream failure status') const body = await response.json() assertEquals(body, { error: 'stt_upstream_failed', attempts: [{ provider: 'gateway', failure: 'network_error' }, { provider: 'groq', failure: 'http_500' }], }, 'fixed body') assertEquals(h.finalizeCalls, [{ reservationId: 'res-1', succeeded: false }], 'refunded') }) Deno.test('a failure after a billed 2xx answer still consumes the reservation', async () => { const h = harness([ fakeProvider('groq', { kind: 'retryable', failure: 'invalid_response', billed: true, unavailable: false }), fakeProvider('openai', retryable('http_500')), ]) const response = await h.handler(sttRequest()) assertEquals(response.status, 502, 'still a failure') assertEquals(h.finalizeCalls, [{ reservationId: 'res-1', succeeded: true }], 'billed work is not refunded') }) Deno.test('no configured provider answers 503 stt_provider_unavailable and refunds', async () => { const h = harness([]) const response = await h.handler(sttRequest()) assertEquals(response.status, 503, 'unavailable status') assertEquals((await response.json()).error, 'stt_provider_unavailable', 'fixed code') assertEquals(h.finalizeCalls, [{ reservationId: 'res-1', succeeded: false }], 'refunded') }) Deno.test('a denied reservation answers 429 before any provider work', async () => { const groq = fakeProvider('groq', OK) const h = harness([groq], { reservation: { allowed: false, reservationId: null, status: 'denied' } }) const response = await h.handler(sttRequest()) assertEquals(response.status, 429, 'quota status') assertEquals((await response.json()).error, 'quota_exceeded', 'quota error code') assertEquals(groq.calls, 0, 'no provider work when denied') assertEquals(h.finalizeCalls, [], 'nothing to finalize') }) Deno.test('invalid input is rejected before reserving quota', async () => { const h = harness([fakeProvider('groq', OK)]) const form = new FormData() form.append('audio', new Blob(['not audio'], { type: 'text/plain' }), 'a.txt') const response = await h.handler(new Request('http://localhost/stt-proxy', { method: 'POST', body: form })) assertEquals(response.status, 415, 'unsupported type') assertEquals(h.events, [], 'no quota or provider work') })