// The per-video registry reader. // // Run with: node_modules/.bin/tsx --test common/controller/videoOperations.test.ts // // Its own file because it needs a SETTINGS SEAM: the digest entry's // resolveTarget reads settings and the channel's context note from disk (via // resolveDigestTarget), and getPaths() memoizes its first answer at module // scope — so TRANSCRIPTS_DIR and SETTINGS_FILE must be set before anything can // import the module under test. Same arrangement as laneForOperation.test.ts. import { mkdtempSync, writeFileSync } from "node:fs"; import { mkdir, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { test, after } from "node:test"; import assert from "node:assert/strict"; // Set BEFORE anything can call getPaths(). node:test runs each file in its own // process, so this is scoped to this file alone. const ROOT = mkdtempSync(path.join(os.tmpdir(), "video-operations-")); process.env.TRANSCRIPTS_DIR = ROOT; const SETTINGS_FILE = path.join(ROOT, "settings.json"); process.env.SETTINGS_FILE = SETTINGS_FILE; // getSettings() re-reads the file on every call, so a case can flip a feature // between assertions — but the FIRST write has to land before any import that // might read it. function writeSettings(settings: Record): void { writeFileSync(SETTINGS_FILE, JSON.stringify(settings), "utf8"); } // Both speaker features armed and the digest lane pointed at the default app. // The diarization model paths are full paths on purpose: the freshness target // compares BASENAMES, and a sidecar recording "seg-1.onnx" has to read fresh // against a setting of "/models/seg-1.onnx". function settingsOn(over: Record = {}): Record { return { diarization: { enabled: true, segModel: "/models/seg-1.onnx", embModel: "/models/emb-1.onnx", }, attribution: { enabled: true, diarizedEnabled: true, textOnlyEnabled: true, }, ...over, }; } writeSettings(settingsOn()); const { inspectVideoOperations, orderForVideoPage, shownOnVideoPage } = await import("./videoOperations"); const { countOperationWork } = await import("./operationBatch"); const { getPaths } = await import("../lib/paths"); const { getSettings } = await import("../lib/settings"); after(() => rm(ROOT, { recursive: true, force: true })); const SLUG = "chan"; // A video dir described by what is on disk. Written in mtime order — // metadata and the raw transcript first, the cues sidecar LAST — because // isCuesJsonFresh compares mtimes and a cues file older than its raw // transcript reads as superseded (which is the `deferred` case below). async function seed( videoId: string, opts: { transcript?: boolean; cues?: boolean; diarization?: boolean; attribution?: string; } = {}, ): Promise { const dir = path.join(ROOT, "channels", SLUG, "data", videoId); await mkdir(dir, { recursive: true }); await writeFile( path.join(dir, "metadata.info.json"), JSON.stringify({ id: videoId, duration: 600 }), ); if (opts.transcript !== false) { await writeFile( path.join(dir, "transcript.json"), JSON.stringify({ transcription: [{ text: "hi" }] }), ); } if (opts.diarization) { await writeFile( path.join(dir, "diarization.json"), JSON.stringify({ videoId, generatedAt: "2026-08-07T00:00:00.000Z", speakers: 2, turns: [{ start: 0, end: 4, speaker: 0 }], engine: { engine: "sherpa-onnx", segmentationModel: "seg-1.onnx", embeddingModel: "emb-1.onnx", threshold: 0.9, }, }), ); } if (opts.attribution !== undefined) { await writeFile(path.join(dir, "attribution.json"), opts.attribution); } if (opts.cues !== false) { await writeFile( path.join(dir, "transcript.cues.json"), JSON.stringify({ cues: [] }), ); } return dir; } function inspect(videoId: string) { return inspectVideoOperations({ paths: getPaths(), channelSlug: SLUG, videoId, settings: getSettings(), }); } function stateOf( views: Awaited>, id: string, ): string | undefined { return views.find((v) => v.id === id)?.state; } test("one view per registry entry, in OPERATIONS order", async () => { writeSettings(settingsOn()); await seed("vid-order"); const views = await inspect("vid-order"); assert.deepEqual( views.map((v) => v.id), ["diarization", "attribution-diarized", "attribution-text", "digest"], ); // The label and the settings block come off the entry, not off a table kept // beside it — that is what makes a fifth operation need no page. assert.equal( views.find((v) => v.id === "diarization")?.label, "Speaker diarization", ); assert.equal( views.find((v) => v.id === "attribution-text")?.settingsBlock, "attribution", ); }); test("a diarized video: the capture is present, both naming lanes are missing", async () => { writeSettings(settingsOn()); await seed("vid-diarized", { diarization: true }); const views = await inspect("vid-diarized"); assert.equal(stateOf(views, "diarization"), "present"); // `missing`, not `stale`: the record this lane is responsible for was never // made. Both are reachable work and they mean different things. assert.equal(stateOf(views, "attribution-diarized"), "missing"); assert.equal(stateOf(views, "attribution-text"), "missing"); // outputs track the listing this page already read. const diarizationView = views.find((v) => v.id === "diarization"); assert.equal(diarizationView?.outputs[0]?.name, "diarization.json"); assert.equal(diarizationView?.outputs[0]?.present, true); assert.equal( views.find((v) => v.id === "attribution-text")?.outputs[0]?.present, false, ); }); test("an undiarized video: the diarized naming lane is blocked, and says on what", async () => { writeSettings(settingsOn()); await seed("vid-undiarized"); const views = await inspect("vid-undiarized"); const view = views.find((v) => v.id === "attribution-diarized"); assert.equal(view?.state, "blocked"); // BLOCKED names a thing this system produces — the surface can say what the // video is waiting FOR rather than just that it is stuck. assert.equal(view?.dependsOn[0]?.id, "diarization"); assert.equal(view?.dependsOn[0]?.label, "Speaker diarization"); }); test("an untranscribed video is not-applicable to the speaker lanes and blocks the digest", async () => { writeSettings(settingsOn()); await seed("vid-raw", { transcript: false, cues: false }); const views = await inspect("vid-raw"); assert.equal(stateOf(views, "diarization"), "not-applicable"); assert.equal(stateOf(views, "attribution-diarized"), "not-applicable"); assert.equal(stateOf(views, "attribution-text"), "not-applicable"); // Blocked, not not-applicable: it is waiting on the transcription operation, // which is a thing this system produces. assert.equal(stateOf(views, "digest"), "blocked"); }); test("no normalized transcript: the digest is deferred, and carries its own reason", async () => { writeSettings(settingsOn()); await seed("vid-nocues", { cues: false }); const views = await inspect("vid-nocues"); const digest = views.find((v) => v.id === "digest"); assert.equal(digest?.state, "deferred"); // The kind's own words, verbatim — a plural fragment completing "N videos // are …". No singular twin: one copy of a sentence cannot drift from itself. assert.match(digest?.deferredHint ?? "", /normalized transcript/); }); test("a digested-able video with no record reads missing", async () => { writeSettings(settingsOn()); await seed("vid-nodigest"); assert.equal(stateOf(await inspect("vid-nodigest"), "digest"), "missing"); }); test("shownOnVideoPage: off with nothing on disk hides; off with a sidecar shows", async () => { // Capture switched off. The video with no sidecar has nothing to show; the // one with a sidecar has a record that cost audio nobody has any more, and // hiding it is how that becomes invisible. writeSettings(settingsOn({ diarization: { enabled: false } })); await seed("vid-off-empty"); await seed("vid-off-record", { diarization: true }); const empty = (await inspect("vid-off-empty")).find( (v) => v.id === "diarization", ); assert.equal(empty?.enabled, false); assert.equal(shownOnVideoPage(empty!), false); const withRecord = (await inspect("vid-off-record")).find( (v) => v.id === "diarization", ); assert.equal(withRecord?.enabled, false); assert.equal(shownOnVideoPage(withRecord!), true); }); // Lives here rather than in operationBatch.test.ts because countOperationWork // reads settings from disk and this file already owns the settings seam. test("countOperationWork sizes an ids-scoped run to those ids alone", async () => { // `enabled` and `weight` used to live on this block and are BOTH deleted — // `weight` by slice 1.3, `enabled` (the lane's inverted pause) by S0-pause. // Left in, they would have read as nothing at all while looking like the // switch that makes the lane run. What arms the lane's OPERATIONS is // `settingsOn`'s diarization/attribution above; counting never asks the // lane's gate, so there is no `autoQueue.backfill.held` to spell here either // (a test that did need the lane running would say exactly that). writeSettings(settingsOn({ backfill: { concurrency: 1 } })); const idA = "vid-count-a"; const idB = "vid-count-b"; await seed(idA); await seed(idB); const paths = getPaths(); // Both are reachable for the text lane — its input is the cue stream, which // every transcribed video has. const whole = await countOperationWork("backfill", paths, SLUG, { operationIds: ["attribution-text"], }); assert.ok(whole.reachable >= 2); // Scoped, the progress target must count what THIS run will pull. const scoped = await countOperationWork("backfill", paths, SLUG, { operationIds: ["attribution-text"], ids: [idB], }); assert.equal(scoped.reachable, 1); }); test("orderForVideoPage puts the digest group ahead of the speakers group", async () => { writeSettings(settingsOn()); await seed("vid-sort", { diarization: true }); const ordered = orderForVideoPage(await inspect("vid-sort")); assert.deepEqual( ordered.map((v) => v.id), ["digest", "diarization", "attribution-diarized", "attribution-text"], ); });