import test from "node:test"; import assert from "node:assert/strict"; import http from "node:http"; import type { AddressInfo } from "node:net"; import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; // Run with: // pnpm -C editor exec tsx --test "app/api/worker/unit/route.test.ts" // // The unit-executor protocol (/api/worker/unit) — the generalisation of the // remote-transcription protocol to backfill kinds — driven through its four // route handlers in-process, in a temp corpus. (It was e2e, worker-unit.spec.ts, // and needed nothing of the server but these handlers.) Three angles: // 1. Auth + the door guard (only backfill KINDS are accepted — download and // transcription are refused, which is what keeps download politeness // single-machine). // 2. A full round trip: an attribution-text unit whose model calls land on an // ollama STUB started here — proving the scratch-corpus materialization // (cues written last passes the mtime freshness gate), the config // injection, and the result pull, with no real model anywhere. // 3. Cleanup: DELETE removes the scratch and the result 404s. const ROOT = await mkdtemp(path.join(os.tmpdir(), "worker-unit-route-")); const CORPUS = path.join(ROOT, "transcripts"); await mkdir(path.join(CORPUS, "channels"), { recursive: true }); const SETTINGS_FILE = path.join(ROOT, "settings.json"); await writeFile(SETTINGS_FILE, JSON.stringify({ workers: [] })); const TOKEN = "test-worker-token"; // Set before anything that caches getPaths() or the token is first imported. process.env.WORKER_TOKEN = TOKEN; process.env.TRANSCRIPTS_DIR = CORPUS; process.env.SETTINGS_FILE = SETTINGS_FILE; const { POST } = await import("./route"); const { DELETE } = await import("./[id]/route"); const { GET: eventsGET } = await import("./[id]/events/route"); const { GET: resultGET } = await import("./[id]/result/route"); test.after(() => rm(ROOT, { recursive: true, force: true })); const AUTH = { authorization: `Bearer ${TOKEN}` }; const BASE = "http://localhost/api/worker/unit"; function post(body: unknown, headers: Record = AUTH) { return POST( new Request(BASE, { method: "POST", headers: { ...headers, "content-type": "application/json" }, body: JSON.stringify(body), }), ); } function byId(id: string) { return { params: Promise.resolve({ id }) }; } test("the unit endpoint enforces the bearer token and refuses non-kinds", async () => { const noAuth = await post( { op: "attribution-text", channelSlug: "c", videoId: "v", files: {} }, {}, ); assert.equal(noAuth.status, 401); // download/transcription are ExternalOperations, not backfill kinds — the // executor refuses them at the door. for (const op of ["download", "transcription", "nonsense"]) { const refused = await post({ op, channelSlug: "c", videoId: "v", files: {}, target: {}, }); assert.equal(refused.status, 400, op); } }); test("an attribution unit round-trips against a scratch corpus and a stub ollama", async () => { // A fake ollama the EXECUTOR's injected appConfig.baseUrl points at. The // /api/chat reply names one speaker, in the schema the turn prompt pins. const stub = http.createServer((req, res) => { res.setHeader("content-type", "application/json"); if (req.url?.startsWith("/api/tags")) { res.end(JSON.stringify({ models: [{ name: "stub-model" }] })); return; } // Drain the request, then answer as ollama would. req.resume(); req.on("end", () => { res.end( JSON.stringify({ model: "stub-model", message: { content: JSON.stringify({ turns: [{ start: "00:00:01", speaker: "Host" }], }), }, }), ); }); }); await new Promise((resolve) => stub.listen(0, "127.0.0.1", resolve)); const stubUrl = `http://127.0.0.1:${(stub.address() as AddressInfo).port}`; try { const cues = { version: 1, id: "unitvid1", title: "Unit test video", channel: "unit-chan", duration: 9, cues: [ { start: 0, end: 4, text: "hello there" }, { start: 4, end: 9, text: "general kenobi" }, ], }; const b64 = (s: string) => Buffer.from(s).toString("base64"); const res = await post({ op: "attribution-text", channelSlug: "unit-chan", videoId: "unitvid1", files: { "metadata.info.json": b64( JSON.stringify({ id: "unitvid1", title: "Unit test video", duration: 9 }), ), "transcript.json": b64(JSON.stringify({ transcription: [] })), // Materialized LAST by the executor whatever this map's order is — // the mtime freshness gate depends on it. "transcript.cues.json": b64(JSON.stringify(cues)), }, target: {}, config: { // The primary's identity, injected. Without this the executor's // default settings (attribution disabled) would fail the job loudly. attribution: { enabled: true, appId: "ollama-direct", model: "stub-model", diarizedEnabled: false, textOnlyEnabled: true, promptVersion: 2, }, appConfig: { model: "stub-model", baseUrl: stubUrl, numCtx: 8192 }, context: { hash: "none" }, }, }); assert.equal(res.status, 202); const { remoteJobId } = (await res.json()) as { remoteJobId: string }; assert.ok(remoteJobId); const deadline = Date.now() + 30_000; let status = ""; while (Date.now() < deadline) { const ev = await eventsGET( new Request(`${BASE}/${remoteJobId}/events`, { headers: AUTH }), byId(remoteJobId), ); status = ((await ev.json()) as { status: string }).status; if (status === "done" || status === "error") break; await new Promise((r) => setTimeout(r, 100)); } assert.equal(status, "done"); const result = await resultGET( new Request(`${BASE}/${remoteJobId}/result`, { headers: AUTH }), byId(remoteJobId), ); assert.equal(result.status, 200); const body = (await result.json()) as { outcome: string; files: Record; }; assert.equal(body.outcome, "done"); const record = JSON.parse(body.files["attribution.json"]) as { speakers: Array<{ label: string }>; provenance: { method: string; model: string }; }; assert.equal(record.speakers[0]?.label, "Host"); assert.equal(record.provenance.method, "text-only"); assert.equal(record.provenance.model, "stub-model"); // Cleanup removes the scratch corpus; the result then 404s. const del = await DELETE( new Request(`${BASE}/${remoteJobId}`, { method: "DELETE", headers: AUTH }), byId(remoteJobId), ); assert.equal(del.status, 200); const gone = await resultGET( new Request(`${BASE}/${remoteJobId}/result`, { headers: AUTH }), byId(remoteJobId), ); assert.equal(gone.status, 404); } finally { await new Promise((resolve) => stub.close(() => resolve())); } });