import { test } from "node:test"; import assert from "node:assert/strict"; import { deriveChannelSets } from "./channelSets"; import { emptyRoster, mergeRoster, type Roster } from "./rosterStore"; // Run with: // pnpm --filter yt-dlp-transcript-common exec tsx --test common/controller/channelSets.test.ts const NOW = "2026-08-06T00:00:00.000Z"; function rosterOf(ids: ReadonlyArray): Roster { return mergeRoster( emptyRoster(), ids.map((id) => ({ id, url: `https://www.youtube.com/watch?v=${id}` })), NOW, "listing", ); } function derive(input: { roster: ReadonlyArray; listed: ReadonlyArray; onDisk: ReadonlyArray; settled?: ReadonlyArray; }) { return deriveChannelSets({ roster: rosterOf(input.roster), listedIds: new Set(input.listed), onDiskIds: new Set(input.onDisk), ...(input.settled ? { settledIds: new Set(input.settled) } : {}), }); } test("a video in the roster and on disk but not listed is missingDownloaded only", () => { const sets = derive({ roster: ["a"], listed: [], onDisk: ["a"] }); assert.deepEqual(sets.missingDownloaded, ["a"]); assert.deepEqual(sets.missingNeverFetched, []); assert.deepEqual(sets.orphaned, []); assert.deepEqual(sets.undownloaded, []); assert.deepEqual(sets.missing, ["a"]); }); test("a roster-only video is missingNeverFetched only — the category that did not exist before", () => { const sets = derive({ roster: ["a"], listed: [], onDisk: [] }); assert.deepEqual(sets.missingNeverFetched, ["a"]); assert.deepEqual(sets.missingDownloaded, []); assert.deepEqual(sets.orphaned, []); assert.deepEqual(sets.missing, ["a"]); }); test("a disk-only video not in the roster is orphaned", () => { const sets = derive({ roster: [], listed: [], onDisk: ["a"] }); assert.deepEqual(sets.orphaned, ["a"]); // Still maybe-missing: it IS on disk and the listing does not have it, which // is exactly what maybe-missing.json has always meant. A roster that hasn't // caught up must never be able to un-flag a deletion. assert.deepEqual(sets.missingDownloaded, ["a"]); assert.deepEqual(sets.missingNeverFetched, []); }); test("a listed video on disk is in neither missing set", () => { const sets = derive({ roster: ["a"], listed: ["a"], onDisk: ["a"] }); assert.deepEqual(sets.listed, ["a"]); assert.deepEqual(sets.undownloaded, []); assert.deepEqual(sets.missing, []); assert.deepEqual(sets.missingDownloaded, []); assert.deepEqual(sets.missingNeverFetched, []); assert.deepEqual(sets.orphaned, []); }); test("a listed video with no dir is undownloaded — the work-list, and not missing", () => { const sets = derive({ roster: ["a"], listed: ["a"], onDisk: [] }); assert.deepEqual(sets.undownloaded, ["a"]); assert.deepEqual(sets.missing, []); }); test("every set at once, on a channel with one of each", () => { const sets = derive({ // gone-fetched left the listing but we have it; gone-never was listed once // and never downloaded; orphan was imported by hand before the roster. roster: ["listed-have", "listed-want", "gone-fetched", "gone-never"], listed: ["listed-have", "listed-want"], onDisk: ["listed-have", "gone-fetched", "orphan"], }); assert.deepEqual(sets.listed, ["listed-have", "listed-want"]); assert.deepEqual(sets.undownloaded, ["listed-want"]); assert.deepEqual(sets.missingDownloaded, ["gone-fetched", "orphan"]); assert.deepEqual(sets.missingNeverFetched, ["gone-never"]); assert.deepEqual(sets.orphaned, ["orphan"]); assert.deepEqual(sets.missing, ["gone-fetched", "gone-never", "orphan"]); }); test("missingDownloaded reproduces the old on-disk-minus-listing diff exactly", () => { // The pre-roster rule, which maybe-missing.json's readers still depend on. const onDisk = ["a", "b", "c", "d"]; const listed = ["b", "d", "e"]; const expected = onDisk.filter((id) => !listed.includes(id)).sort(); // Whatever the roster says — full, partial, or empty — the answer is the same. for (const roster of [onDisk, listed, [], ["a"]]) { const sets = derive({ roster, listed, onDisk }); assert.deepEqual(sets.missingDownloaded, expected); } }); test("an empty channel derives empty sets rather than throwing", () => { const sets = derive({ roster: [], listed: [], onDisk: [] }); assert.deepEqual(sets, { listed: [], undownloaded: [], missing: [], missingDownloaded: [], missingNeverFetched: [], orphaned: [], }); }); test("a settled video is neither never-fetched nor undownloaded", () => { // BOTH SETS ARE DEFINED BY THE ABSENCE OF A DIRECTORY, and a video the // download filter settled has none — a title-filter rejection deletes its own // prefetch dir. So without subtracting the settled set, a filtered-out video // that later leaves the listing reads as "we were told about this, never got // it, and it is gone", which is the loudest alarm this system raises and the // sweep prints a recovery prompt for it. const sets = derive({ roster: ["keep", "drop"], listed: ["keep"], onDisk: [], settled: ["drop"], }); assert.deepEqual(sets.missingNeverFetched, []); assert.deepEqual(sets.undownloaded, ["keep"]); // And the control: the same corpus with nothing settled still raises it, so // the subtraction has not made the category unreachable. const unsettled = derive({ roster: ["keep", "drop"], listed: ["keep"], onDisk: [], }); assert.deepEqual(unsettled.missingNeverFetched, ["drop"]); }); test("an omitted settled set changes nothing, for every channel without a filter", () => { const withEmpty = derive({ roster: ["a", "b"], listed: ["a"], onDisk: [], settled: [], }); const without = derive({ roster: ["a", "b"], listed: ["a"], onDisk: [] }); assert.deepEqual(withEmpty, without); });