import { test } from "node:test"; import assert from "node:assert/strict"; import type { ChannelSnapshot } from "../../controller/channelSnapshot"; import type { ChannelConfig } from "../../lib/channelConfig"; import type { Operation, OperationSnapshotEntry, } from "../../lib/operations"; import { computeChannelFlow, type FlowStationId } from "./channelFlow"; import { computeStageStatuses, normalizeBuckets } from "./stageStatus"; // Run from this directory (the [slug] segment is a glob to node's test runner): // cd "editor/app/channels/[slug]/lib" && ../../../../../node_modules/.bin/tsx --test channelFlow.test.ts const CONFIG: ChannelConfig = { handling: "transcribe", url: "https://x/y" }; function snapshotOf(patch: Partial = {}): ChannelSnapshot { return { generatedAt: "2026-08-01T00:00:00.000Z", totals: { videos: 100, transcribed: 40, downloaded: 60 }, buckets: normalizeBuckets(undefined), undownloadedIds: [], ...patch, }; } // A registry entry is a big object with three async methods on it; none of them // are reachable from computeChannelFlow, which only ever reads id/label/hints. function kind(patch: Partial & { id: string }): Operation { return { label: patch.id, hint: "", ...patch } as Operation; } // A snapshot entry AS WRITTEN TO DISK. The cast is the point of the helper: // OperationCounts declares deferred/blocked/partial required, but every snapshot // currently on disk predates them, which is why every read site carries `?? 0`. // Omitting them here is how these tests exercise the real files. function entry(patch: Partial): OperationSnapshotEntry { return { ids: [], missing: 0, stale: 0, missingInput: 0, ...patch } as OperationSnapshotEntry; } function flowOf( snapshot: ChannelSnapshot, opts: { laneOperations?: Operation[]; playlistCount?: number | null; failedVideoIds?: string[]; } = {}, ) { const failedVideoIds = opts.failedVideoIds ?? []; return computeChannelFlow({ snapshot, stages: computeStageStatuses({ snapshot, failedVideoIds, config: CONFIG, runningJobs: [], }), config: CONFIG, failedVideoIds, laneOperations: opts.laneOperations ?? [kind({ id: "diarization" })], playlistCount: opts.playlistCount ?? null, }); } function station( flow: ReturnType, id: FlowStationId, ) { const s = flow.stations.find((st) => st.id === id); assert.ok(s, `expected a ${id} station`); return s; } test("a pre-`eligible` snapshot reports unknown coverage, not zero", () => { // The shape a third of the snapshots on disk are still in: per-kind counts // written before `eligible` existed. A 0 here renders as "nothing digested" // on a channel that may be fully digested. const flow = flowOf( snapshotOf({ backfill: { digest: entry({ missing: 5 }), diarization: entry({ missing: 3 }), }, }), ); assert.equal(station(flow, "digest").through, null); assert.equal(station(flow, "digest").denominator, null); assert.equal(station(flow, "digest").coverage, null); assert.equal(station(flow, "speakers").through, null); assert.equal(station(flow, "speakers").coverage, null); }); test("the lane station does NOT sum its operations — it reads the lead one", () => { // THE BUG THIS STATION USED TO BE. `through` and `denominator` were the sum // across every kind on the lane, which on the live corpus added diarization's // coverage (one audio pass per video, 4 done of 11,338) to attribution-text's // (~1 model call per transcript CHUNK, 1 done of 11,338) and printed the // result under a label that named the queue. Two different populations in two // different units, added, and no screen said so. // // The numeral now belongs to exactly ONE operation — the first in dependency // order — and the rest state themselves separately in the station foot. const flow = flowOf( snapshotOf({ backfill: { diarization: entry({ eligible: 10 }), // Same lane, a different population. Nothing may fold it in. "attribution-diarized": entry({ eligible: 1000, missing: 400 }), }, }), { laneOperations: [ kind({ id: "diarization" }), kind({ id: "attribution-diarized" }), ], }, ); const lane = station(flow, "speakers"); assert.equal(lane.through, 10); assert.equal(lane.denominator, 10); // The sum would be 1,010. Asserting the negative is the point. assert.notEqual(lane.denominator, 1010); // Both operations are carried, each with its own band and its own // denominator, so nothing is hidden by not being summed. assert.deepEqual( lane.operations.map((o) => o.id), ["diarization", "attribution-diarized"], ); assert.equal(lane.operations[1].band.eligible, 1000); assert.equal(lane.operations[1].band.reachable, 400); }); test("the lane station is named after its operations, not its queue key", () => { // "Backfill" is a scheduler key. An operator cannot arm, pause or run "a // backfill" — they can run speaker work. The label is DERIVED from the group // its kinds declare, so a lane holding a mix degrades to the generic name // rather than advertising one member's. const speakers = flowOf(snapshotOf(), { laneOperations: [ kind({ id: "diarization" }), kind({ id: "attribution-text" }), ], }); assert.equal(station(speakers, "speakers").label, "Speakers"); const mixed = flowOf(snapshotOf(), { laneOperations: [kind({ id: "diarization" }), kind({ id: "digest" })], }); assert.equal(station(mixed, "speakers").label, "Derived data"); // Nothing enabled: the generic name, and no operations to state. const off = flowOf(snapshotOf(), { laneOperations: [] }); assert.equal(station(off, "speakers").label, "Derived data"); assert.deepEqual(station(off, "speakers").operations, []); }); test("an unknown `eligible` on the lead operation still renders unknown, not zero", () => { // Invariant 2, at the one station whose denominator moved. A snapshot written // before `eligible` existed has work counts and no denominator, and the // station must say "—" rather than 0%. const flow = flowOf( snapshotOf({ backfill: { diarization: entry({ missing: 3 }) } }), { laneOperations: [kind({ id: "diarization" })] }, ); const lane = station(flow, "speakers"); assert.equal(lane.through, null); assert.equal(lane.denominator, null); assert.equal(lane.coverage, null); }); test("coverage is a real ratio once the snapshot can say", () => { const flow = flowOf(snapshotOf(), { playlistCount: 125 }); assert.equal(station(flow, "playlist").through, 100); assert.equal(station(flow, "playlist").denominator, 125); assert.equal(station(flow, "playlist").coverage, 0.8); assert.equal(station(flow, "download").coverage, 0.6); // transcribed / downloaded, not / videos: the denominator is the eligible // population, and an undownloaded video is not eligible for transcription. assert.equal( station(flow, "transcribe").coverage, 40 / 60, ); }); test("a lane that is switched off reads neutral, never ok and never amber", () => { const snapshot = snapshotOf({ backfill: { diarization: entry({ missing: 7, eligible: 50, ids: ["a", "b"] }), }, }); const off = flowOf(snapshot, { laneOperations: [] }); assert.equal(station(off, "speakers").tone, "neutral"); // …and the work it recorded is not offered as something to press, because // nothing would run it. assert.equal(off.gaps.find((g) => g.to === "speakers")?.reachable, 0); // NOT the pre-rename literal. This assertion passed vacuously the moment the // stage id changed — a notEqual against a value the union can no longer hold // is always true — so it is spelled with the live id and would fail if the // disabled lane were ever offered as the next action again. assert.notEqual(off.next?.stage, "speakers"); const on = flowOf(snapshot, { laneOperations: [kind({ id: "diarization" })] }); assert.equal(on.gaps.find((g) => g.to === "speakers")?.reachable, 7); }); test("deferred, blocked and missing-input never enter a gap's reachable count", () => { const flow = flowOf( snapshotOf({ backfill: { digest: entry({ missing: 2, stale: 1, partial: 1, missingInput: 900, deferred: 40, blocked: 1631, eligible: 3000, ids: ["a", "b", "c", "d"], }), diarization: entry({ missing: 3, missingInput: 500, deferred: 11, blocked: 70, eligible: 1000, ids: ["x", "y", "z"], }), }, }), ); const toDigest = flow.gaps.find((g) => g.to === "digest"); const toBackfill = flow.gaps.find((g) => g.to === "speakers"); // missing + stale + partial, and nothing else. assert.equal(toDigest?.reachable, 4); assert.equal(toBackfill?.reachable, 3); // The excluded populations are present — on the other axis. const sidingCount = ( gapTo: FlowStationId, label: string, ): number | undefined => flow.gaps .find((g) => g.to === gapTo) ?.sidings.find((s) => s.label === label)?.count; assert.equal(sidingCount("digest", "waiting on a transcript"), 1631); assert.equal(sidingCount("digest", "deferred"), 40); assert.equal(sidingCount("speakers", "needs media re-acquired"), 500); assert.equal(sidingCount("speakers", "deferred"), 11); assert.equal(sidingCount("speakers", "waiting on an earlier backfill"), 70); // The invariant stated as the sum nobody should be able to write: a gap's // reachable count is not the total of everything hanging under it. for (const gap of flow.gaps) { const sidingTotal = gap.sidings.reduce((n, s) => n + s.count, 0); if (sidingTotal > 0) { assert.notEqual(gap.reachable, gap.reachable + sidingTotal); } } }); test("the digest station reads the registry entry", () => { // The operation registry's entry is the one definition of the digest work // list. `eligible` is absent here, so the station reports the work and still // refuses to say how many are done. const flow = flowOf( snapshotOf({ backfill: { digest: entry({ missing: 3, ids: ["a", "b", "c"] }) }, }), ); assert.equal(flow.gaps.find((g) => g.to === "digest")?.reachable, 3); // …but it still cannot say how many are done. assert.equal(station(flow, "digest").through, null); }); test("the bottleneck is the biggest gap; the next action is the furthest upstream one", () => { const flow = flowOf( snapshotOf({ undownloadedIds: ["a", "b"], backfill: { digest: entry({ missing: 1675, eligible: 1797 }), }, }), ); // The eye goes to the 1,675-video digest shortfall… assert.equal(flow.bottleneck, "transcribe"); // …but the button offers the two downloads, because a line clears from the // front and the digest gap shrinks on its own as the upstream one does. assert.equal(flow.next?.stage, "download"); assert.equal(flow.next?.count, 2); }); test("an idle, clean, reported channel offers no action at all", () => { const flow = flowOf( snapshotOf({ totals: { videos: 10, transcribed: 10, downloaded: 10 } }), ); assert.equal(flow.next, null); assert.equal(flow.bottleneck, null); }); test("a channel with no report offers nothing here — NoReportYet owns that", () => { // Every count is zero because nothing has looked yet, not because the work is // done. The page says so in the NoReportYet banner, which carries the only // "Refresh report" button; putting a second one here duplicates the affordance // and makes the name ambiguous. const flow = flowOf( snapshotOf({ generatedAt: "", totals: { videos: 0, transcribed: 0, downloaded: 0 }, }), ); assert.equal(flow.next, null); }); test("every optional snapshot field survives being absent", () => { // The render-path crash this guards: `.toLocaleString()` on an undefined // count. Nothing here is defaulted defensively — it is defaulted because // snapshots on disk genuinely predate each field. const bare = { generatedAt: "", totals: { videos: 0, transcribed: 0, downloaded: 0 }, buckets: {}, undownloadedIds: [], } as unknown as ChannelSnapshot; const flow = flowOf(bare); for (const s of flow.stations) { assert.ok(s.through === null || Number.isFinite(s.through)); assert.ok(s.coverage === null || Number.isFinite(s.coverage)); } for (const g of flow.gaps) { assert.ok(Number.isFinite(g.reachable)); for (const sd of g.sidings) assert.ok(Number.isFinite(sd.count)); } });