// The one publish module that reads post captures copies the CITED ones and // nothing else — the other half of the guard in the post capture tests. // (The capture directory is reached through the module's own helper, so this // file does not name it and needs no exception of its own.) // // Run with: pnpm --filter yt-dlp-transcript-common exec tsx --test publish/citedPostCaptures.test.ts import { after, test } from "node:test"; import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { citedCaptureSourceDir, copyCitedPostCaptures, pngSize } from "./citedPostCaptures"; const ROOT = await mkdtemp(path.join(tmpdir(), "cited-captures-")); after(() => rm(ROOT, { recursive: true, force: true })); // A PNG header for a w×h image (the bytes after IHDR's size do not matter here). function png(w: number, h: number): Buffer { const b = Buffer.alloc(33); b.writeUInt32BE(0x89504e47, 0); b.writeUInt32BE(0x0d0a1a0a, 4); b.writeUInt32BE(13, 8); b.write("IHDR", 12, "latin1"); b.writeUInt32BE(w, 16); b.writeUInt32BE(h, 20); return b; } async function capture(channelsDir: string, channel: string, id: string, files: Record) { const dir = citedCaptureSourceDir(channelsDir, channel, id); await mkdir(dir, { recursive: true }); for (const [name, body] of Object.entries(files)) await writeFile(path.join(dir, name), body); } async function tree(dir: string): Promise { const out: string[] = []; const walk = async (d: string, rel: string) => { for (const e of await readdir(d, { withFileTypes: true }).catch(() => [])) { const r = rel ? `${rel}/${e.name}` : e.name; if (e.isDirectory()) await walk(path.join(d, e.name), r); else out.push(r); } }; await walk(dir, ""); return out.sort(); } test("copies only the cited posts' screenshot and media — never the record, the article or an uncited post", async () => { const channelsDir = path.join(ROOT, "a", "channels"); const destRoot = path.join(ROOT, "a", "report-media"); await capture(channelsDir, "demo-x", "111", { "shot.png": png(600, 400), "capture.json": "{}", "111_1.jpg": "jpeg one", "article.md": "an article", "article-img-1.jpg": "inline", "111_2.mp4.part": "partial", }); await capture(channelsDir, "demo-x", "222", { "shot.png": png(10, 10), "222_1.jpg": "not cited" }); await capture(channelsDir, "other-x", "333", { "shot.png": png(10, 10) }); const r = await copyCitedPostCaptures({ channelsDir, destRoot, posts: [{ channel: "demo-x", id: "111" }] }); assert.deepEqual(r.missing, []); assert.deepEqual(await tree(destRoot), ["posts/demo-x/111/111_1.jpg", "posts/demo-x/111/shot.png"]); assert.equal(r.copied.length, 1); const c = r.copied[0]; assert.equal(c.shot.file, "posts/demo-x/111/shot.png"); assert.equal(c.shot.width, 600); assert.equal(c.shot.height, 400); assert.deepEqual(c.media, [ { file: "posts/demo-x/111/111_1.jpg", bytes: "jpeg one".length, sha256: createHash("sha256").update("jpeg one").digest("hex"), }, ]); }); test("a later run with a different citation set leaves exactly that set", async () => { const channelsDir = path.join(ROOT, "b", "channels"); const destRoot = path.join(ROOT, "b", "report-media"); await capture(channelsDir, "demo-x", "111", { "shot.png": png(1, 1), "111_1.jpg": "x" }); await capture(channelsDir, "demo-x", "222", { "shot.png": png(1, 1) }); await capture(channelsDir, "other-x", "333", { "shot.png": png(1, 1) }); await copyCitedPostCaptures({ channelsDir, destRoot, posts: [ { channel: "demo-x", id: "111" }, { channel: "other-x", id: "333" }, ], }); // The capture lost its media since; 111 is no longer cited at all. await rm(path.join(citedCaptureSourceDir(channelsDir, "demo-x", "111"), "111_1.jpg")); await writeFile(path.join(destRoot, "posts", "stray.txt"), "left by hand"); const r = await copyCitedPostCaptures({ channelsDir, destRoot, posts: [{ channel: "demo-x", id: "222" }] }); assert.equal(r.copied.length, 1); assert.deepEqual(await tree(destRoot), ["posts/demo-x/222/shot.png"]); }); test("a cited post with no screenshot, or an id that is not one, is missing — and nothing is copied for it", async () => { const channelsDir = path.join(ROOT, "c", "channels"); const destRoot = path.join(ROOT, "c", "report-media"); await capture(channelsDir, "demo-x", "111", { "111_1.jpg": "media only" }); const r = await copyCitedPostCaptures({ channelsDir, destRoot, posts: [ { channel: "demo-x", id: "111" }, { channel: "demo-x", id: "999" }, { channel: "demo-x", id: "../escape" }, ], }); assert.deepEqual(r.copied, []); assert.deepEqual( r.missing.map((m) => m.id), ["111", "999", "../escape"], ); assert.match(r.missing[2].message, /is not a post id/); assert.deepEqual(await tree(destRoot), []); }); test("pngSize: the IHDR size, or null for anything else", async () => { const f = path.join(ROOT, "x.png"); await writeFile(f, png(1280, 720)); assert.deepEqual(await pngSize(f), { width: 1280, height: 720 }); await writeFile(f, "not a png at all, but long enough"); assert.equal(await pngSize(f), null); assert.equal(await pngSize(path.join(ROOT, "absent.png")), null); });