import { BatchTranscriptionAdapter } from "../src/services/transcription/adapters/BatchTranscriptionAdapter"; import { flushMicrotasks } from "./helpers/testUtils"; const createResolvedConnectionContext = () => ({ realtimeUrl: "wss://scribe.sofya.health/api/realtime?existing=true&transcription_language=portuguese&translation_language=english&x-external-id=session-123", batchBaseUrl: "https://scribe.sofya.health/", batchDefaultUrl: "https://scribe.sofya.health/api/transcriber", authToken: "jwt-token", externalId: "session-123", transcriptionLanguage: "portuguese", translationLanguage: "english", headers: { "x-client-id": "sdk-demo", "Content-Type": "application/json", }, }); describe("BatchTranscriptionAdapter", () => { afterEach(() => { delete (global as { fetch?: typeof fetch }).fetch; }); it("uploads one wav file on stop, inherits request context, and returns the parsed payload", async () => { const parseResponse = jest.fn().mockResolvedValue({ transcript: "final" }); const adapter = new BatchTranscriptionAdapter({ language: "pt-BR", translation_lang: "en-US", batch: { parseResponse, }, resolvedConnectionContext: createResolvedConnectionContext(), } as any); const fetchMock = jest.fn().mockResolvedValue({ ok: true, status: 200, headers: new Headers(), }); (global as { fetch?: typeof fetch }).fetch = fetchMock as unknown as typeof fetch; await adapter.startTranscription({} as MediaStream); const durableAudioSession = (adapter as any).durableAudioSession; jest .spyOn(durableAudioSession, "buildFinalUpload") .mockResolvedValue(new Blob([new Uint8Array([1, 2, 3])], { type: "audio/wav" })); const stopEvent = jest.fn(); const recognized = jest.fn(); adapter.on("stopped", stopEvent); adapter.on("recognized", recognized); const payload = (await adapter.stopTranscription()) as unknown as { transcript: string; }; const [requestUrl, requestInit] = fetchMock.mock.calls[0]; const requestHeaders = requestInit.headers as Record; const requestBody = requestInit.body as FormData; const uploadedFile = requestBody.get("file") as File; expect(payload).toEqual({ transcript: "final" }); expect(fetchMock).toHaveBeenCalledWith( "https://scribe.sofya.health/api/transcriber?existing=true&transcription_language=portuguese&translation_language=english&x-external-id=session-123", expect.objectContaining({ method: "POST", body: expect.any(FormData), }) ); expect(requestUrl).toBe( "https://scribe.sofya.health/api/transcriber?existing=true&transcription_language=portuguese&translation_language=english&x-external-id=session-123" ); expect(requestHeaders).toEqual({ authorization: "Bearer jwt-token", "x-client-id": "sdk-demo", }); expect(uploadedFile).toBeInstanceOf(Blob); expect(uploadedFile.name).toBe("consultation.wav"); expect(uploadedFile.type).toBe("audio/wav"); expect(parseResponse).toHaveBeenCalledWith( expect.objectContaining({ ok: true, status: 200, }) ); expect(stopEvent).toHaveBeenCalledTimes(1); expect(recognized).not.toHaveBeenCalled(); }); it("pauses and resumes local capture without needing a websocket", async () => { const adapter = new BatchTranscriptionAdapter({ language: "en-US", resolvedConnectionContext: createResolvedConnectionContext(), } as any); await adapter.startTranscription({} as MediaStream); adapter.pauseTranscription(); expect((adapter as any).recordState).toBe("PAUSED"); adapter.resumeTranscription(); expect((adapter as any).recordState).toBe("RECORDING"); expect((adapter as any).websocket).toBeUndefined(); }); it("rejects stop when the batch request fails", async () => { const adapter = new BatchTranscriptionAdapter({ language: "en-US", resolvedConnectionContext: createResolvedConnectionContext(), } as any); const fetchMock = jest.fn().mockResolvedValue({ ok: false, status: 403, headers: new Headers(), }); (global as { fetch?: typeof fetch }).fetch = fetchMock as unknown as typeof fetch; await adapter.startTranscription({} as MediaStream); jest .spyOn((adapter as any).durableAudioSession, "buildFinalUpload") .mockResolvedValue(new Blob([new Uint8Array([1, 2, 3])], { type: "audio/wav" })); await expect(adapter.stopTranscription()).rejects.toMatchObject({ code: "HTTP_ERROR", status: 403, }); await flushMicrotasks(); }); });