import test from "node:test"; import assert from "node:assert/strict"; import { mkdir, readFile, stat, writeFile } from "node:fs/promises"; import path from "node:path"; import { setupOpsCorpus, callPost, callGet } from "../_testCorpus"; // Run with: // pnpm -C editor exec tsx --test "app/api/ops/build-cues/route.test.ts" // // Cues without an index (release 19 A7): `build-cues` writes // transcript.cues.json for the named videos only, and `GET transcript` reads a // video's cues off disk — from a fresh cues.json, from what normalize would // write, or from the VTT alone — writing nothing. const corpus = await setupOpsCorpus(null); await corpus.writeChannelConfig("demo-yt", { url: "https://www.youtube.com/@demo" }); await corpus.writeChannelConfig("other-yt", { url: "https://www.youtube.com/@other" }); const DATA = path.join(corpus.transcripts, "channels", "demo-yt", "data"); const VTT = (text: string) => `WEBVTT\n\n00:00:01.000 --> 00:00:03.000\n${text}\n\n00:00:04.000 --> 00:00:06.000\nsecond line\n`; async function video(dir: string, opts: { meta?: boolean; text: string }) { await mkdir(dir, { recursive: true }); if (opts.meta) { await writeFile( path.join(dir, "metadata.info.json"), JSON.stringify({ id: path.basename(dir), title: `Title ${path.basename(dir)}`, upload_date: "20240102", duration: 6 }), ); } await writeFile(path.join(dir, "transcript.en.vtt"), VTT(opts.text)); } await video(path.join(DATA, "vid00000001"), { meta: true, text: "first video" }); await video(path.join(DATA, "vid00000002"), { meta: true, text: "second video" }); await video(path.join(DATA, "bare0000001"), { text: "no metadata here" }); await video(path.join(corpus.transcripts, "channels", "other-yt", "data", "vid00000002"), { meta: true, text: "a copy" }); const { POST } = await import("./route"); const { GET } = await import("../transcript/route"); const { getRegistry } = await import("yt-dlp-transcript-common/jobs/registry"); test.after(() => corpus.cleanup()); const exists = (p: string) => stat(p).then(() => true, () => false); test("build-cues refusals: keys, ids, a stray id, a channel that does not exist", async () => { const cases: [Record, RegExp][] = [ [{ slug: "demo-yt", id: "x" }, /unknown key\(s\): id/], [{ slug: "demo-yt", ids: [] }, /"ids" must be a non-empty array/], [{ slug: "demo-yt", ids: ["../x"] }, /not a video id/], [{ slug: "demo-yt", ids: ["vid00000001", "nope"] }, /1 of "ids" not held by demo-yt: nope/], [{ slug: "no-such", ids: ["a"] }, /Channel "no-such" not found/], [{ slug: "demo-yt", force: "yes" }, /"force" must be a boolean/], ]; for (const [body, re] of cases) { const r = await callPost(POST, body); assert.equal(r.status, 400, JSON.stringify(body)); assert.match(r.body.error ?? "", re, JSON.stringify(body)); } }); test("transcript reads cues with no index: built from the VTT + metadata, or the VTT alone; nothing written", async () => { const url = (q: string) => `http://localhost/api/ops/transcript?${q}`; const built = await callGet(GET, url("id=vid00000001&slug=demo-yt")); assert.equal(built.status, 200, JSON.stringify(built.body)); assert.equal(built.body.source, "built"); assert.equal(built.body.cuesJson, "missing"); assert.equal(built.body.title, "Title vid00000001"); assert.equal(built.body.file, "transcript.en.vtt"); assert.deepEqual((built.body.cues as { text: string }[]).map((c) => c.text), ["first video", "second line"]); assert.equal(await exists(path.join(DATA, "vid00000001", "transcript.cues.json")), false); const bare = await callGet(GET, url("id=bare0000001")); assert.equal(bare.status, 200, JSON.stringify(bare.body)); assert.equal(bare.body.slug, "demo-yt"); assert.equal(bare.body.source, "vtt"); assert.equal((bare.body.cues as unknown[]).length, 2); const two = await callGet(GET, url("id=vid00000002")); assert.equal(two.status, 409); assert.match(two.body.error ?? "", /2 channels hold a video "vid00000002" \(demo-yt, other-yt\) — name the channel/); assert.equal((await callGet(GET, url("id=nothing"))).status, 404); assert.match((await callGet(GET, url("slug=demo-yt"))).body.error ?? "", /"id" is required/); assert.match((await callGet(GET, url("id=x&track=en"))).body.error ?? "", /unknown query key\(s\): track/); assert.equal((await callGet(GET, url("id=x"), {}, {})).status, 401); }); test("build-cues writes cues.json for the named ids only; transcript then reads it as fresh", async () => { const r = await callPost(POST, { slug: "demo-yt", ids: ["vid00000001"] }); assert.equal(r.status, 200, JSON.stringify(r.body)); const jobId = r.body.jobId as string; for (let i = 0; i < 200; i++) { const s = getRegistry().get(jobId)?.status; if (s === "done" || s === "failed") break; await new Promise((res) => setTimeout(res, 50)); } assert.equal(getRegistry().get(jobId)?.status, "done"); const cues = JSON.parse(await readFile(path.join(DATA, "vid00000001", "transcript.cues.json"), "utf8")); assert.equal(cues.cues.length, 2); assert.equal(await exists(path.join(DATA, "vid00000002", "transcript.cues.json")), false); const fresh = await callGet(GET, "http://localhost/api/ops/transcript?id=vid00000001&slug=demo-yt"); assert.equal(fresh.body.source, "cues.json"); assert.equal(fresh.body.cuesJson, "fresh"); });