import test from "node:test"; import assert from "node:assert/strict"; import { execFileSync } from "node:child_process"; import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; // Run with: // pnpm -C editor exec tsx --test "app/channels/*/videos/fetchWindowsAction.test.ts" // (a wildcard: node's --test reads `[slug]` as a glob class) // // The batch fetch over videos whose source is in the saved-video store // (release 21 D2): a window a container holds joins the `saved-video` group on // `clips:saved-video` — no platform's queue, hold or cooldown — and its job // cuts it; a pointer whose container is missing is unresolved with the media // guard's sentence; a window past the container's end joins its platform's // group as before. YouTube is in a cooldown throughout, so its group is // refused at the door and nothing here ever reaches the network. const ROOT = await mkdtemp(path.join(os.tmpdir(), "fetch-windows-saved-")); const SLUG = "demo-channel"; process.env.TRANSCRIPTS_DIR = ROOT; process.env.SETTINGS_FILE = path.join(ROOT, "settings.json"); delete process.env.FFPROBE_BIN; delete process.env.FFMPEG_BIN; // The temp corpus sits on whatever disk os.tmpdir() is; the floor is not what // is under test. await writeFile(process.env.SETTINGS_FILE, JSON.stringify({ minFreeDiskGB: 0 })); await mkdir(path.join(ROOT, "channels", SLUG), { recursive: true }); await writeFile( path.join(ROOT, "channels", SLUG, "config.json"), JSON.stringify({ name: "Demo", handling: "youtube", url: "https://www.youtube.com/@demo" }), ); async function pointTo(id: string, withFile: boolean): Promise { const videoDir = path.join(ROOT, "channels", SLUG, "data", id); const store = path.join(ROOT, "saved-videos", SLUG, id); await mkdir(videoDir, { recursive: true }); await mkdir(store, { recursive: true }); await writeFile( path.join(videoDir, "metadata.info.json"), JSON.stringify({ id, webpage_url: `https://www.youtube.com/watch?v=${id}` }), ); if (withFile) { execFileSync("ffmpeg", [ "-nostdin", "-v", "error", "-y", "-f", "lavfi", "-i", "testsrc=size=160x90:rate=10:duration=8", "-f", "lavfi", "-i", "sine=frequency=440:duration=8", "-c:v", "libx264", "-preset", "ultrafast", "-pix_fmt", "yuv420p", "-c:a", "aac", "-shortest", path.join(store, "source-media.mp4"), ]); } await writeFile( path.join(videoDir, "saved-video.json"), JSON.stringify({ storedAt: "", dir: store, file: "source-media.mp4", bytes: 1, keepReason: "pin" }), ); return videoDir; } const HELD = await pointTo("held1", true); await pointTo("gone1", false); const { fetchWindowsAction } = await import("./fetchWindowsAction"); const { getRegistry } = await import("yt-dlp-transcript-common/jobs/registry"); const { recordDownloadBackoff } = await import("yt-dlp-transcript-common/jobs/downloadBackoff"); const { getPaths } = await import("yt-dlp-transcript-common/lib/paths"); await recordDownloadBackoff("youtube", getPaths()); test.after(() => rm(ROOT, { recursive: true, force: true })); const items = [ { slug: SLUG, id: "held1", from: 1, to: 3, clipId: "r#1" }, { slug: SLUG, id: "held1", from: 6, to: 12, clipId: "r#2" }, // past the container's end { slug: SLUG, id: "gone1", from: 1, to: 3, clipId: "r#3" }, ]; test("a dry run groups held windows on clips:saved-video, refuses the unreadable, and sends the rest to their platform", async () => { const r = await fetchWindowsAction({ items, requestedBy: "test", dryRun: true }); assert.ok(r.ok); if (!r.ok) return; assert.deepEqual( r.groups.map((g) => [g.platform, g.queueKey, g.items.map((i) => i.clipId)]).sort(), [ ["saved-video", "clips:saved-video", ["r#1"]], ["youtube", "clips:youtube", ["r#2"]], ], ); assert.equal(r.unresolved.length, 1); assert.equal(r.unresolved[0].item.id, "gone1"); assert.match(r.unresolved[0].error, /^Channel "demo-channel": media is not reachable — its saved video .* is not there/); }); test("a real run cuts the held window in a saved-video job while the cooling platform is refused", async () => { const r = await fetchWindowsAction({ items, requestedBy: "test", manifest: "m" }); assert.ok(r.ok, JSON.stringify(r)); if (!r.ok) return; assert.deepEqual( r.jobs.map((j) => [j.platform, j.queueKey, j.items]), [["saved-video", "clips:saved-video", 1]], ); assert.deepEqual(r.refused.map((x) => x.platform), ["youtube"]); const jobId = r.jobs[0].jobId; const reg = getRegistry(); for (let i = 0; i < 200; i++) { const s = reg.get(jobId)?.status; if (s !== "queued" && s !== "running") break; await new Promise((res) => setTimeout(res, 50)); } assert.equal(reg.get(jobId)?.status, "done", `job ${jobId}: ${reg.get(jobId)?.status}`); assert.deepEqual((await readdir(path.join(HELD, "clips"))).sort(), ["1.00-3.00.json", "1.00-3.00.mp4"]); const sidecar = JSON.parse(await readFile(path.join(HELD, "clips", "1.00-3.00.json"), "utf8")); assert.equal(sidecar.source, "saved-video"); assert.equal(sidecar.manifest, "m"); assert.equal(sidecar.clipId, "r#1"); // Sent again, the cut window is cached and joins no job. const again = await fetchWindowsAction({ items: [items[0]], requestedBy: "test", dryRun: true }); assert.ok(again.ok); if (!again.ok) return; assert.deepEqual(again.cached.map((i) => i.clipId), ["r#1"]); assert.deepEqual(again.groups, []); }); test("a held window a queued saved-video job will already cut is answered in inFlight (A5's dedupe)", async () => { const reg = getRegistry(); const make = (id: string, params: Record) => { const record = { id, kind: "fetch-windows", queueKey: "clips:saved-video", channelSlug: SLUG, status: "queued" as const, queuedAt: Date.now(), logPath: "/dev/null", spec: { kind: "fetch-windows", slug: SLUG, params }, }; reg.register(record); reg.enqueue(record, { start: () => {}, onCancel: () => {} }); }; make("HOLDER", { items: [{ slug: SLUG, id: "other", from: 0, to: 1 }] }); make("CUT4", { items: [{ slug: SLUG, id: "held1", from: 4, to: 7 }] }); try { const r = await fetchWindowsAction({ items: [{ slug: SLUG, id: "held1", from: 4.5, to: 6.5 }], requestedBy: "test", dryRun: true, }); assert.ok(r.ok); if (!r.ok) return; assert.deepEqual(r.inFlight.map((f) => f.jobId), ["CUT4"]); assert.deepEqual(r.groups, []); } finally { reg.cancel("CUT4"); reg.forceRelease("HOLDER"); } });