import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import type { Paths } from "../lib/paths"; import { computeKeepWindow, computeKeptVideoIds, isInKeepWindow, } from "./keptVideos"; // Run with: // pnpm --filter yt-dlp-transcript-common exec tsx --test common/controller/keptVideos.test.ts // Seed channels//data//metadata.info.json for each [id, uploadDate]. // A null uploadDate omits the metadata file so the dir-name fallback is exercised. async function seedChannel( channelsDir: string, slug: string, videos: ReadonlyArray<[string, string | null]>, ): Promise { for (const [id, uploadDate] of videos) { const dir = path.join(channelsDir, slug, "data", id); await mkdir(dir, { recursive: true }); if (uploadDate !== null) { await writeFile( path.join(dir, "metadata.info.json"), JSON.stringify({ id, upload_date: uploadDate }), ); } } } async function withPaths(fn: (paths: Paths) => Promise): Promise { const dir = await mkdtemp(path.join(tmpdir(), "ttb-kept-")); const paths = { channelsDir: path.join(dir, "channels") } as Paths; try { await fn(paths); } finally { await rm(dir, { recursive: true, force: true }); } } test("keeps the newest N by upload_date", async () => { await withPaths(async (paths) => { await seedChannel(paths.channelsDir, "ch", [ ["a", "20240101"], ["b", "20240301"], ["c", "20240201"], ["d", "20231231"], ]); const kept = await computeKeptVideoIds({ paths, channelSlug: "ch", keepLatest: 2, }); assert.deepEqual([...kept].sort(), ["b", "c"]); }); }); test("keepLatest <= 0 keeps nothing", async () => { await withPaths(async (paths) => { await seedChannel(paths.channelsDir, "ch", [["a", "20240101"]]); assert.equal( (await computeKeptVideoIds({ paths, channelSlug: "ch", keepLatest: 0 })) .size, 0, ); }); }); test("keepLatest larger than the catalog keeps all", async () => { await withPaths(async (paths) => { await seedChannel(paths.channelsDir, "ch", [ ["a", "20240101"], ["b", "20240201"], ]); const kept = await computeKeptVideoIds({ paths, channelSlug: "ch", keepLatest: 10, }); assert.deepEqual([...kept].sort(), ["a", "b"]); }); }); test("falls back to a YYYYMMDD_ dir-name prefix when metadata is absent", async () => { await withPaths(async (paths) => { await seedChannel(paths.channelsDir, "ch", [ ["20240301_newest", null], ["20240101_older", null], ["nodate", null], ]); const kept = await computeKeptVideoIds({ paths, channelSlug: "ch", keepLatest: 1, }); assert.deepEqual([...kept], ["20240301_newest"]); }); }); test("missing channel data dir yields an empty set", async () => { await withPaths(async (paths) => { assert.equal( ( await computeKeptVideoIds({ paths, channelSlug: "ghost", keepLatest: 5, }) ).size, 0, ); }); }); test("keep window: an under-full channel admits any candidate", async () => { await withPaths(async (paths) => { await seedChannel(paths.channelsDir, "ch", [["a", "20240101"]]); const w = await computeKeepWindow({ paths, channelSlug: "ch", keepLatest: 3, }); assert.equal(w.full, false); assert.equal(w.cutoffKey, null); // A brand-new (not-yet-on-disk) video is admitted regardless of its date. assert.equal(isInKeepWindow("20200101", w), true); }); }); test("keep window: a full channel admits only candidates >= the cutoff", async () => { await withPaths(async (paths) => { await seedChannel(paths.channelsDir, "ch", [ ["a", "20240101"], ["b", "20240301"], ["c", "20240201"], ]); const w = await computeKeepWindow({ paths, channelSlug: "ch", keepLatest: 2, }); assert.equal(w.full, true); // Newest two are 20240301, 20240201 -> the 2nd-newest (cutoff) is 20240201. assert.equal(w.cutoffKey, "20240201"); // A newer upload displaces the boundary video -> kept. assert.equal(isInKeepWindow("20240401", w), true); // A tie with the cutoff is kept. assert.equal(isInKeepWindow("20240201", w), true); // An older upload stays out of the window. assert.equal(isInKeepWindow("20240101", w), false); }); }); test("keep window: keepLatest <= 0 is inert", async () => { await withPaths(async (paths) => { await seedChannel(paths.channelsDir, "ch", [["a", "20240101"]]); const w = await computeKeepWindow({ paths, channelSlug: "ch", keepLatest: 0, }); assert.equal(isInKeepWindow("99999999", w), false); }); });