import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdir, mkdtemp, rm, stat, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import type { Paths } from "./paths"; import { parseSavedVideoPointer, savedVideoDir, savedVideoFormatFromLine, savedVideoRoot, } from "./savedVideo"; import { dropSavedVideo, loadSavedVideo, persistSourceVideo, resolveSavedVideo, unpersistSavedVideo, } from "./savedVideo-server"; // Run with: // pnpm --filter yt-dlp-transcript-common exec tsx --test lib/savedVideo.test.ts function fakePaths(savedVideosDir: string): Paths { return { savedVideosDir } as Paths; } async function withTmp(fn: (root: string) => Promise): Promise { const dir = await mkdtemp(path.join(tmpdir(), "ttb-saved-")); try { await fn(dir); } finally { await rm(dir, { recursive: true, force: true }); } } test("savedVideoRoot prefers the per-channel override, else the global", () => { const paths = fakePaths("/global/store"); assert.equal(savedVideoRoot(paths, undefined), "/global/store"); assert.equal(savedVideoRoot(paths, { savedVideosDir: " " }), "/global/store"); assert.equal( savedVideoRoot(paths, { savedVideosDir: "/chan/store" }), "/chan/store", ); }); test("savedVideoDir composes //", () => { const paths = fakePaths("/global/store"); assert.equal( savedVideoDir(paths, undefined, "chan", "vid1"), path.join("/global/store", "chan", "vid1"), ); }); test("parseSavedVideoPointer rejects malformed and reads keepReason", () => { assert.equal(parseSavedVideoPointer(null), null); assert.equal(parseSavedVideoPointer({ dir: "/a" }), null); // no file/bytes const ok = parseSavedVideoPointer({ storedAt: "t", dir: "/a", file: "source-media.mp4", bytes: 10, keepReason: "keep-latest", sha256: "abc", }); assert.deepEqual(ok, { storedAt: "t", dir: "/a", file: "source-media.mp4", bytes: 10, keepReason: "keep-latest", sha256: "abc", }); // An unknown keepReason is dropped rather than carried through. const noReason = parseSavedVideoPointer({ dir: "/a", file: "f.mp4", bytes: 1, keepReason: "bogus", }); assert.equal(noReason?.keepReason, undefined); }); test("format: an old pointer parses as before, a new one carries its format", () => { const base = { storedAt: "t", dir: "/a", file: "source-media.mp4", bytes: 10 }; // Every pointer written before the field: no `format` key at all. const old = parseSavedVideoPointer(base); assert.deepEqual(old, base); assert.equal(old && "format" in old, false); // The new shape round-trips. assert.deepEqual( parseSavedVideoPointer({ ...base, format: { preset: "video_720", height: 720, vcodec: "avc1.4d401f" }, })?.format, { preset: "video_720", height: 720, vcodec: "avc1.4d401f" }, ); // Height and vcodec are optional; junk in them is dropped, not carried. assert.deepEqual( parseSavedVideoPointer({ ...base, format: { preset: "original", height: "720", vcodec: "" }, })?.format, { preset: "original" }, ); // A format with no valid preset is dropped whole; the pointer still parses. const bad = parseSavedVideoPointer({ ...base, format: { preset: "4k", height: 2160 } }); assert.ok(bad); assert.equal(bad && "format" in bad, false); }); test("savedVideoFormatFromLine reads the printed format line", () => { assert.deepEqual(savedVideoFormatFromLine("720 avc1.4d401f 136+140", "video_720"), { preset: "video_720", height: 720, vcodec: "avc1.4d401f", }); // yt-dlp prints NA for a field it does not have. assert.deepEqual(savedVideoFormatFromLine("NA NA original", "original"), { preset: "original", }); // No line: the asked-for preset alone. assert.deepEqual(savedVideoFormatFromLine(null, "video_720"), { preset: "video_720" }); assert.deepEqual(savedVideoFormatFromLine("", "original"), { preset: "original" }); }); test("persist writes the format it is given onto the pointer", async () => { await withTmp(async (root) => { const videoDir = path.join(root, "data", "vid1"); const storeDir = path.join(root, "store", "chan", "vid1"); await mkdir(videoDir, { recursive: true }); await writeFile(path.join(videoDir, "source-media.mp4"), "video-bytes"); const format = { preset: "video_720" as const, height: 480, vcodec: "avc1.4d401e" }; const pointer = await persistSourceVideo({ videoDir, sourceFilename: "source-media.mp4", storeDir, keepReason: "override", format, }); assert.deepEqual(pointer.format, format); assert.deepEqual((await loadSavedVideo(videoDir))?.format, format); }); }); test("persist moves the container into the store and writes a pointer", async () => { await withTmp(async (root) => { const videoDir = path.join(root, "data", "vid1"); const storeDir = path.join(root, "store", "chan", "vid1"); await mkdir(videoDir, { recursive: true }); await writeFile(path.join(videoDir, "source-media.mp4"), "video-bytes"); const pointer = await persistSourceVideo({ videoDir, sourceFilename: "source-media.mp4", storeDir, keepReason: "keep-latest", }); assert.equal(pointer.file, "source-media.mp4"); assert.equal(pointer.dir, storeDir); assert.equal(pointer.keepReason, "keep-latest"); assert.equal(pointer.bytes, "video-bytes".length); // Container moved OUT of the data dir, into the store. await assert.rejects(stat(path.join(videoDir, "source-media.mp4"))); await stat(path.join(storeDir, "source-media.mp4")); // Pointer readable + resolvable. const loaded = await loadSavedVideo(videoDir); assert.equal(loaded?.keepReason, "keep-latest"); assert.equal( await resolveSavedVideo(videoDir), path.join(storeDir, "source-media.mp4"), ); }); }); test("unpersist returns the container to the data dir and drops the pointer", async () => { await withTmp(async (root) => { const videoDir = path.join(root, "data", "vid1"); const storeDir = path.join(root, "store", "chan", "vid1"); await mkdir(videoDir, { recursive: true }); await writeFile(path.join(videoDir, "source-media.webm"), "abc"); await persistSourceVideo({ videoDir, sourceFilename: "source-media.webm", storeDir, keepReason: "override", }); assert.equal(await unpersistSavedVideo(videoDir), true); await stat(path.join(videoDir, "source-media.webm")); assert.equal(await loadSavedVideo(videoDir), null); assert.equal(await resolveSavedVideo(videoDir), null); // Reversing again is a no-op. assert.equal(await unpersistSavedVideo(videoDir), false); }); }); test("drop deletes the stored container and reports bytes freed", async () => { await withTmp(async (root) => { const videoDir = path.join(root, "data", "vid1"); const storeDir = path.join(root, "store", "chan", "vid1"); await mkdir(videoDir, { recursive: true }); await writeFile(path.join(videoDir, "source-media.mkv"), "0123456789"); await persistSourceVideo({ videoDir, sourceFilename: "source-media.mkv", storeDir, keepReason: "keep-latest", }); const freed = await dropSavedVideo(videoDir); assert.equal(freed, 10); assert.equal(await loadSavedVideo(videoDir), null); await assert.rejects(stat(path.join(storeDir, "source-media.mkv"))); assert.equal(await dropSavedVideo(videoDir), 0); // nothing left }); });