import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { afterEach, describe, it } from "node:test"; import { runInNewContext } from "node:vm"; import { DEFAULT_STT_MODEL, OPENROUTER_STT_MODEL, STT_MAX_AUDIO_BYTES, dictationWav } from "../src/core/stt.ts"; import { sanitizeUserSettings } from "../src/core/settings.ts"; import { resolveSettings } from "../src/core/settings-resolution.ts"; import { parseGeneralSettingsBody } from "../src/server/settings-body.ts"; import { openRouterTranscription } from "../src/server/stt.ts"; const originalFetch = globalThis.fetch; afterEach(() => { globalThis.fetch = originalFetch; }); const frames = [new Uint8Array([0, 128, 255, 127]), new Uint8Array([1, 0, 255, 255])]; function request(language = "multi", extra: RequestInit = {}) { return new Request(`http://localhost/api/stt/transcribe?model=${OPENROUTER_STT_MODEL}&language=${language}`, { method: "POST", headers: { "Content-Type": "audio/wav" }, body: dictationWav(frames), ...extra, }); } describe("STT configuration and lossless capture", () => { it("round-trips both STT models without changing languages, hints or update preferences", () => { for (const sttModel of [DEFAULT_STT_MODEL, OPENROUTER_STT_MODEL]) { const general = { sttModel, sttLanguage: "multi" as const, sttPreferredLanguages: ["en" as const, "ru" as const], language: "ru" as const, autoUpdate: false }; assert.deepEqual(parseGeneralSettingsBody(general), { ok: true, settings: general }); const saved = sanitizeUserSettings({ version: 1, general }); assert.deepEqual(saved?.general, general); assert.deepEqual(resolveSettings(saved).general, general); } assert.deepEqual(sanitizeUserSettings({ version: 1, general: { sttModel: "other" } })?.general, {}); assert.equal(parseGeneralSettingsBody({ sttModel: "other" }).ok, false); assert.equal(resolveSettings(null).general.sttModel, undefined, "old records retain the Deepgram default"); }); it("keeps every PCM sample in order, including short final frames", () => { const wav = Buffer.from(dictationWav(frames)); assert.equal(wav.toString("ascii", 0, 4), "RIFF"); assert.equal(wav.readUInt32LE(4), wav.length - 8); assert.equal(wav.readUInt32LE(24), 16000); assert.equal(wav.readUInt16LE(22), 1); assert.equal(wav.readUInt16LE(34), 16); assert.equal(wav.readUInt32LE(40), 8); assert.deepEqual([...wav.subarray(44)], frames.flatMap((frame) => [...frame])); assert.throws(() => dictationWav([new Uint8Array(1)]), /Invalid dictation audio/); assert.throws(() => dictationWav([new Uint8Array(STT_MAX_AUDIO_BYTES)]), /25 MB/); }); it("drains the worklet's incomplete last frame exactly once", () => { let Processor: new () => { process: (inputs: Float32Array[][]) => void; port: { onmessage: (event: unknown) => void } }; const messages: { type: string; buffer?: Int16Array }[] = []; runInNewContext(readFileSync(new URL("../public/worklets/pcm-capture.js", import.meta.url), "utf8"), { AudioWorkletProcessor: class { port = { postMessage: (message: { type: string; buffer?: Int16Array }) => messages.push(message) }; }, registerProcessor: (_name: string, processor: typeof Processor) => { Processor = processor; }, }); const processor = new Processor!(); processor.process([[new Float32Array([0, 1, -1])]]); assert.equal(messages.filter((message) => message.type === "audio").length, 0); processor.port.onmessage({ data: { type: "flush" } }); assert.deepEqual(Array.from(messages.find((message) => message.type === "audio")!.buffer!), [0, 32767, -32768]); assert.equal(messages.at(-1)?.type, "flushed"); processor.port.onmessage({ data: { type: "flush" } }); assert.equal(messages.filter((message) => message.type === "audio").length, 1); }); }); describe("OpenRouter whole-recording transcription", () => { for (const language of ["en", "ru", "multi"]) it(`sends one complete WAV using server auth and the correct language (${language})`, async () => { let calls = 0; globalThis.fetch = async (input, init) => { calls++; assert.equal(String(input), "https://openrouter.ai/api/v1/audio/transcriptions"); assert.equal(new Headers(init?.headers).get("Authorization"), "Bearer test-only-key"); const body = JSON.parse(String(init?.body)); assert.equal(body.model, OPENROUTER_STT_MODEL); assert.equal(body.response_format, "json"); assert.equal(body.input_audio.format, "wav"); assert.deepEqual(Buffer.from(body.input_audio.data, "base64"), Buffer.from(dictationWav(frames))); assert.deepEqual(Object.keys(body).sort(), (language === "multi" ? ["model", "input_audio", "response_format"] : ["model", "input_audio", "response_format", "language"]).sort()); assert.equal(body.language, language === "multi" ? undefined : language); return Response.json({ text: "Привет, hello!", usage: { cost: 0.01 } }); }; const response = await openRouterTranscription(request(language), "test-only-key"); assert.equal(response.status, 200); assert.deepEqual(await response.json(), { text: "Привет, hello!" }); assert.equal(calls, 1); }); it("refuses missing keys, unknown models and invalid languages without any provider request", async () => { globalThis.fetch = async () => { throw new Error("must not reach provider"); }; assert.equal((await openRouterTranscription(request(), "")).status, 503); assert.equal((await openRouterTranscription(new Request("http://localhost?model=other&language=en", { method: "POST" }), "test")).status, 400); assert.equal((await openRouterTranscription(request("en,ru"), "test")).status, 400); }); it("rejects invalid audio and enforces the upload limit before contacting OpenRouter", async () => { globalThis.fetch = async () => { throw new Error("must not reach provider"); }; assert.equal((await openRouterTranscription(request("multi", { body: "not wav" }), "test")).status, 400); assert.equal((await openRouterTranscription(request("multi", { headers: { "Content-Type": "audio/wav", "Content-Length": String(STT_MAX_AUDIO_BYTES + 1) } }), "test")).status, 413); const damaged = dictationWav(frames); damaged[24] = 0; assert.equal((await openRouterTranscription(request("multi", { body: damaged }), "test")).status, 400); const stream = new ReadableStream({ start(controller) { controller.enqueue(new Uint8Array(STT_MAX_AUDIO_BYTES + 1)); controller.close(); } }); const streamingRequest = request("multi", { body: stream, duplex: "half" } as RequestInit); assert.equal((await openRouterTranscription(streamingRequest, "test")).status, 413); }); for (const status of [401, 403, 402, 429, 500]) it(`handles provider status ${status} without leaking diagnostics or retrying`, async () => { let calls = 0; globalThis.fetch = async () => { calls++; return new Response("test-only-key PRIVATE_AUDIO", { status }); }; const response = await openRouterTranscription(request(), "test-only-key"); assert.equal(response.status, status === 429 ? 429 : 502); assert.doesNotMatch(await response.text(), /test-only-key|PRIVATE_AUDIO/); assert.equal(calls, 1); }); it("accepts an empty transcript for silence, but rejects malformed provider output", async () => { globalThis.fetch = async () => Response.json({ text: "" }); assert.deepEqual(await (await openRouterTranscription(request(), "test")).json(), { text: "" }); globalThis.fetch = async () => Response.json({ text: null }); assert.equal((await openRouterTranscription(request(), "test")).status, 502); }); it("reports timeouts and respects cancellation", async () => { globalThis.fetch = async () => { throw new DOMException("timeout", "TimeoutError"); }; assert.equal((await openRouterTranscription(request(), "test")).status, 504); const controller = new AbortController(); controller.abort(); assert.equal((await openRouterTranscription(request("multi", { signal: controller.signal }), "test")).status, 499); }); it("propagates cancellation to an in-flight provider request", async () => { let started: () => void; const ready = new Promise((resolve) => { started = resolve; }); globalThis.fetch = async (_input, init) => new Promise((_resolve, reject) => { init!.signal!.addEventListener("abort", () => reject(new DOMException("cancelled", "AbortError")), { once: true }); started(); }); const controller = new AbortController(); const pending = openRouterTranscription(request("multi", { signal: controller.signal }), "test"); await ready; controller.abort(); assert.equal((await pending).status, 499); }); });