import { beforeEach, describe, expect, it, vi } from 'vitest' import { transcribeAudio } from '../src/transcribe' const session = { supabaseUrl: 'https://project.supabase.co', anonKey: 'anon-key', token: 'user-token', } function payload(transcript = '안녕하세요') { return { transcript, confidence: 0.99, language_code: 'ko', duration_seconds: 2.5, provider: 'groq', } } describe('transcribeAudio', () => { beforeEach(() => vi.unstubAllGlobals()) it('uses only the authenticated Supabase quota gateway', async () => { const fetchMock = vi.fn(async () => ({ ok: true, status: 200, json: async () => payload() })) vi.stubGlobal('fetch', fetchMock) const result = await transcribeAudio({ ...session, audio: new Blob(['audio'], { type: 'audio/webm' }), language: 'ko', }) const [url, options] = fetchMock.mock.calls[0] expect(url).toBe('https://project.supabase.co/functions/v1/stt-proxy') expect(options.headers).toEqual({ Authorization: 'Bearer user-token', apikey: 'anon-key' }) expect(options.body).toBeInstanceOf(FormData) expect((options.body as FormData).get('language_code')).toBe('ko') expect(result).toEqual({ text: '안녕하세요', confidence: 0.99, language: 'ko', durationSeconds: 2.5, provider: 'groq', latencyMs: expect.any(Number), }) }) it('converts binary and base64 audio to multipart without a JSON bypass', async () => { const fetchMock = vi.fn(async () => ({ ok: true, status: 200, json: async () => payload('binary') })) vi.stubGlobal('fetch', fetchMock) await transcribeAudio({ ...session, audio: Uint8Array.from([0, 1, 2, 253, 254, 255]) }) await transcribeAudio({ ...session, audio: 'AAEC/f7/' }) expect(fetchMock).toHaveBeenCalledTimes(2) for (const [, options] of fetchMock.mock.calls) { expect(options.body).toBeInstanceOf(FormData) expect((options.body as FormData).get('audio')).toBeInstanceOf(Blob) } }) it('requires token, anon key and a safe gateway origin before network access', async () => { const fetchMock = vi.fn() vi.stubGlobal('fetch', fetchMock) await expect(transcribeAudio({ ...session, token: '', audio: new Blob(['x']) })) .rejects.toThrow('authenticated Supabase session') await expect(transcribeAudio({ ...session, supabaseUrl: 'http://public.example.com', audio: new Blob(['x']) })) .rejects.toThrow('configuration is invalid') expect(fetchMock).not.toHaveBeenCalled() }) it('does not expose upstream bodies and rejects malformed success payloads', async () => { vi.stubGlobal('fetch', vi.fn(async () => ({ ok: false, status: 503, text: async () => 'secret upstream body' }))) await expect(transcribeAudio({ ...session, audio: new Blob(['x']) })) .rejects.toThrow('STT gateway request failed (503).') vi.stubGlobal('fetch', vi.fn(async () => ({ ok: true, status: 200, json: async () => ({ transcript: 'x' }) }))) await expect(transcribeAudio({ ...session, audio: new Blob(['x']) })) .rejects.toThrow('invalid response') }) })