import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdtemp, readFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { OPERATIONS, getOperation, } from "./operations"; import { ATTRIBUTION_FILENAME, type AttributionRecord, } from "./attribution"; import { loadAttribution, writeAttribution } from "./attribution-server"; import { DIGEST_FILENAME, type DigestProvenance } from "./digest"; import { loadDigest, writeDigestSection } from "./digest-server"; import { loadDiarization } from "./diarization-server"; import { CUES_JSON_FILENAME, DIARIZATION_FILENAME, META_FILENAME, WHISPER_FILENAME, type VideoFiles, } from "./videoStatus"; // Run with: pnpm --filter yt-dlp-transcript-common exec tsx --test lib/backfillUnit.test.ts // // The unit-executor contract: every lane kind declares what a unit needs // (inputs), what it produces (outputs), and how its output lands back on the // primary (applyResult — through the GUARDED writers, never a raw copy). The // two guards that must hold whatever a unit returns: the text-only lane can // never overwrite a diarized attribution record, and applying one digest // section never clobbers the other. function files(entries: string[]): VideoFiles { return { hasMeta: entries.includes(META_FILENAME), hasYtVtt: entries.includes("transcript.en.vtt"), ytVttFile: entries.includes("transcript.en.vtt") ? "transcript.en.vtt" : null, hasNonCanonicalVtt: false, hasWhisper: entries.includes(WHISPER_FILENAME), hasCuesJson: entries.includes(CUES_JSON_FILENAME), hasDiarization: entries.includes(DIARIZATION_FILENAME), isUntranscribable: false, audioFiles: entries.filter((e) => e.startsWith("audio.")), partAudioFiles: [], entries, }; } const FULL_DIR = files([ META_FILENAME, WHISPER_FILENAME, CUES_JSON_FILENAME, DIARIZATION_FILENAME, ATTRIBUTION_FILENAME, DIGEST_FILENAME, "audio.mp3", ]); test("every lane kind declares the full unit contract", () => { for (const kind of OPERATIONS) { assert.equal(typeof kind.inputs, "function", `${kind.id} inputs`); assert.ok(kind.outputs.length > 0, `${kind.id} outputs`); assert.equal(typeof kind.applyResult, "function", `${kind.id} applyResult`); // Outputs are the SIDECAR the kind writes — never the shipped transcript // or metadata, which applying would silently rewrite on the primary. for (const out of kind.outputs) { assert.ok( out !== CUES_JSON_FILENAME && out !== META_FILENAME && out !== WHISPER_FILENAME, `${kind.id} output ${out} collides with a shipped input`, ); } } }); test("every transcript-derived kind ships transcript.cues.json LAST", () => { // The executor materializes inputs in order and isCuesJsonFresh compares // mtimes: a cues file written before its metadata/raw transcript reads as // stale and the unit silently does nothing. for (const id of ["attribution-text", "attribution-diarized", "digest"]) { const inputs = getOperation(id)!.inputs(FULL_DIR); assert.equal( inputs[inputs.length - 1], CUES_JSON_FILENAME, `${id} must list cues last, got: ${inputs.join(", ")}`, ); assert.ok(inputs.includes(META_FILENAME), `${id} ships metadata`); assert.ok(inputs.includes(WHISPER_FILENAME), `${id} ships the raw transcript`); } }); test("the diarized attribution unit ships diarization.json; diarization ships audio", () => { assert.ok( getOperation("attribution-diarized")! .inputs(FULL_DIR) .includes(DIARIZATION_FILENAME), ); assert.ok(getOperation("diarization")!.inputs(FULL_DIR).includes("audio.mp3")); }); function attributionPayload(method: "text-only" | "diarized"): string { const record: AttributionRecord = { videoId: "vid", generatedAt: "2026-08-24T00:00:00.000Z", speakers: [{ index: 0, label: "Host", seconds: 10 }], segments: [{ start: 0, end: 10, speaker: 0 }], provenance: { method, appId: "ollama-direct", model: "m", modelRequested: "m", promptVersion: 2, generatedAt: "2026-08-24T00:00:00.000Z", durationMs: 1, }, }; return JSON.stringify(record); } test("applyResult refuses a text-only record over a diarized one (the downgrade rule)", async () => { const dir = await mkdtemp(path.join(tmpdir(), "unit-attr-")); await writeAttribution( dir, JSON.parse(attributionPayload("diarized")) as AttributionRecord, ); const outcome = await getOperation("attribution-text")!.applyResult(dir, { [ATTRIBUTION_FILENAME]: attributionPayload("text-only"), }); assert.equal(outcome, "refused"); assert.equal( (await loadAttribution(dir))?.provenance.method, "diarized", "the diarized record must survive", ); }); test("applyResult upgrades a text-only record to a diarized one", async () => { const dir = await mkdtemp(path.join(tmpdir(), "unit-attr-")); await writeAttribution( dir, JSON.parse(attributionPayload("text-only")) as AttributionRecord, ); const outcome = await getOperation("attribution-diarized")!.applyResult( dir, { [ATTRIBUTION_FILENAME]: attributionPayload("diarized") }, ); assert.equal(outcome, "applied"); assert.equal((await loadAttribution(dir))?.provenance.method, "diarized"); }); test("applyResult rejects a malformed attribution payload", async () => { const dir = await mkdtemp(path.join(tmpdir(), "unit-attr-")); const kind = getOperation("attribution-text")!; assert.equal(await kind.applyResult(dir, {}), "invalid"); assert.equal( await kind.applyResult(dir, { [ATTRIBUTION_FILENAME]: "not json" }), "invalid", ); assert.equal( await kind.applyResult(dir, { [ATTRIBUTION_FILENAME]: "{}" }), "invalid", ); }); function digestProvenance(model: string): DigestProvenance { return { appId: "ollama-direct", model, promptVersion: 2, contextHash: "none", generatedAt: "2026-08-24T00:00:00.000Z", } as DigestProvenance; } test("applying one digest section preserves the other (guarded read-modify-write)", async () => { const dir = await mkdtemp(path.join(tmpdir(), "unit-digest-")); // The primary already holds a tags section… await writeDigestSection(dir, { section: "tags", items: [], provenance: digestProvenance("local-model"), warnings: [], }); // …and a unit returns a record carrying only chapters. const payload = JSON.stringify({ sections: { chapters: { provenance: digestProvenance("m"), items: [] }, }, }); const outcome = await getOperation("digest")!.applyResult(dir, { [DIGEST_FILENAME]: payload, }); assert.equal(outcome, "applied"); const record = await loadDigest(dir); assert.ok(record?.sections?.chapters, "the unit's section landed"); assert.ok( record?.sections?.tags, "the section the primary wrote must survive the apply", ); assert.equal(record?.sections?.tags?.provenance.model, "local-model"); }); test("diarization results apply verbatim (single-writer sidecar) after a shape check", async () => { const dir = await mkdtemp(path.join(tmpdir(), "unit-diar-")); const kind = getOperation("diarization")!; assert.equal( await kind.applyResult(dir, { [DIARIZATION_FILENAME]: "{}" }), "invalid", ); const outcome = await kind.applyResult(dir, { [DIARIZATION_FILENAME]: JSON.stringify({ videoId: "vid", generatedAt: "2026-08-24T00:00:00.000Z", speakers: 1, turns: [{ start: 0, end: 5, speaker: 0 }], }), }); assert.equal(outcome, "applied"); const raw = await readFile(path.join(dir, DIARIZATION_FILENAME), "utf8"); assert.equal((JSON.parse(raw) as { speakers: number }).speakers, 1); // And the server-side loader accepts what applyResult wrote. assert.notEqual(await loadDiarization(dir), null); });