import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; // Run with: // pnpm --filter yt-dlp-transcript-common exec tsx --test controller/channelCoverage.test.ts // // Coverage by date (release 19 A9): the recorded date wins when the channel's // title rule yields one (release 20 D1's coverageDate), a big metadata file is // read at both ends, gaps are the spans past gapDays. Every id is invented. const ROOT = mkdtempSync(path.join(os.tmpdir(), "coverage-")); process.env.TRANSCRIPTS_DIR = ROOT; process.env.SETTINGS_FILE = path.join(ROOT, "settings.json"); writeFileSync(process.env.SETTINGS_FILE, "{}\n"); const { channelCoverage, readDateAndTitle, summarizeCoverage } = await import("./channelCoverage"); const { getPaths } = await import("../lib/paths"); function channel(slug: string, config: Record, videos: Record | string | null>) { const dir = path.join(ROOT, "channels", slug); mkdirSync(path.join(dir, "data"), { recursive: true }); writeFileSync(path.join(dir, "config.json"), JSON.stringify({ handling: "youtube", name: slug, ...config })); for (const [id, meta] of Object.entries(videos)) { mkdirSync(path.join(dir, "data", id), { recursive: true }); if (meta === null) continue; writeFileSync(path.join(dir, "data", id, "metadata.info.json"), typeof meta === "string" ? meta : JSON.stringify(meta)); } } test("gaps are the spans longer than gapDays, oldest first; months and years counted", () => { const s = summarizeCoverage( [ { id: "a", date: "20240101", uploadDate: "20240101" }, { id: "b", date: "20240115", uploadDate: "20240115" }, { id: "c", date: "20240401", uploadDate: "20240401" }, { id: "d", date: "20250101", uploadDate: "20250101" }, ], { gapDays: 30 }, ); assert.equal(s.first, "20240101"); assert.equal(s.last, "20250101"); assert.deepEqual(s.byYear, { "2024": 3, "2025": 1 }); assert.deepEqual(s.byMonth, { "2024-01": 2, "2024-04": 1, "2025-01": 1 }); assert.deepEqual(s.gaps, [ { after: "20240115", before: "20240401", days: 77 }, { after: "20240401", before: "20250101", days: 275 }, ]); }); test("a big metadata file is read at both ends: title at the head, upload_date at the tail", async () => { const dir = path.join(ROOT, "big"); mkdirSync(dir, { recursive: true }); const formats = Array.from({ length: 6000 }, (_, i) => ({ format_id: String(i), url: `https://example.invalid/${"x".repeat(40)}` })); writeFileSync( path.join(dir, "metadata.info.json"), JSON.stringify({ id: "big", title: "Stream — März 5 \"live\"", formats, upload_date: "20240306" }), ); assert.deepEqual(await readDateAndTitle(dir), { uploadDate: "20240306", title: 'Stream — März 5 "live"' }); assert.deepEqual(await readDateAndTitle(path.join(ROOT, "nowhere")), {}); }); test("a mirror's videos are dated by the title rule; the rest by upload; no metadata is undated", async () => { channel( "vod-mirror", { recordedDate: { titlePattern: "(?\\d{4})-(?\\d{2})-(?\\d{2})" } }, { m1: { title: "Stream 2023-05-01", upload_date: "20240110" }, m2: { title: "Stream 2023-05-03", upload_date: "20240110" }, m3: { title: "No date in this one", upload_date: "20240110" }, // A title date after the upload is not a recording date. m4: { title: "Preview of 2030-01-01", upload_date: "20240111" }, bare: null, }, ); const c = await channelCoverage(getPaths(), "vod-mirror", { gapDays: 60, list: true }); assert.equal(c.titlePattern, "(?\\d{4})-(?\\d{2})-(?\\d{2})"); assert.equal(c.held, 5); assert.equal(c.dated, 4); assert.equal(c.byRecordedDate, 2); assert.equal(c.byUploadDate, 2); assert.deepEqual(c.undated, ["bare"]); assert.equal(c.first, "20230501"); assert.deepEqual(c.gaps, [{ after: "20230503", before: "20240110", days: 252 }]); assert.deepEqual( c.videos?.map((v) => [v.id, v.date]), [["m1", "20230501"], ["m2", "20230503"], ["m3", "20240110"], ["m4", "20240111"]], ); const window = await channelCoverage(getPaths(), "vod-mirror", { from: "20240101" }); assert.deepEqual(window.byYear, { "2024": 2 }); assert.equal(window.videos, undefined); await assert.rejects(channelCoverage(getPaths(), "no-such"), /Channel "no-such" not found/); });