import test from "node:test"; import assert from "node:assert/strict"; import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { makeZip } from "../../../../../common/testing/makeZip"; // Run with: // pnpm -C editor exec tsx --test "app/api/ops/attach-media/route.test.ts" // // The body's shape, the refusals that come before any job, and one dry run // over a synthetic zip, answered from a temp corpus. Nothing is attached. const ROOT = await mkdtemp(path.join(os.tmpdir(), "attach-media-route-")); // Set before the route (and getPaths, which caches) is first imported. process.env.WORKER_TOKEN = "test-token"; process.env.TRANSCRIPTS_DIR = ROOT; process.env.SETTINGS_FILE = path.join(ROOT, "settings.json"); const { POST } = await import("./route"); const { getRegistry } = await import("yt-dlp-transcript-common/jobs/registry"); const { getPaths } = await import("yt-dlp-transcript-common/lib/paths"); test.after(() => rm(ROOT, { recursive: true, force: true })); const CH = path.join(ROOT, "channels"); await mkdir(path.join(CH, "tism", "data", "aaaaaaaaaaa"), { recursive: true }); await writeFile(path.join(CH, "tism", "config.json"), JSON.stringify({ handling: "youtube", name: "tism", platform: "youtube" })); await writeFile(path.join(CH, "tism", "data", "aaaaaaaaaaa", "metadata.info.json"), JSON.stringify({ id: "aaaaaaaaaaa" })); const ZIP = path.join(ROOT, "archive.zip"); await writeFile( ZIP, makeZip([ { name: "YouTube/001 - A - (aaaaaaaaaaa)/A-aaaaaaaaaaa.mp4", data: Buffer.alloc(1000, 1) }, { name: "YouTube/002 [LOST] - B - (bbbbbbbbbbb)/source.txt", data: "x" }, { name: "YouTube/003 - C - (ccccccccccc)/ccccccccccc.mp4", data: Buffer.alloc(500, 2) }, ]), ); async function post( body: Record, ): Promise<{ status: number; json: { ok?: boolean; error?: string; jobId?: string } }> { const res = await POST( new Request("http://localhost/api/ops/attach-media", { method: "POST", headers: { authorization: "Bearer test-token", "content-type": "application/json" }, body: JSON.stringify(body), }), ); return { status: res.status, json: (await res.json()) as { ok?: boolean; error?: string; jobId?: string } }; } test("refusals before any job: keys, slug, source, items, match, channel", async () => { const cases: [Record, RegExp][] = [ [{ slug: "tism" }, /"source" is required/], [{ source: ZIP }, /"slug" is required/], [{ slug: "../x", source: ZIP }, /not a valid channel slug/], [{ slug: "tism", source: ZIP, force: true }, /unknown key\(s\): force/], [{ slug: "tism", source: "relative/archive.zip" }, /must be an absolute path/], [{ slug: "tism", source: ZIP, items: [] }, /non-empty array/], [{ slug: "tism", source: ZIP, items: [{ id: "..", path: "x" }] }, /not a video id/], [{ slug: "tism", source: ZIP, items: [{ id: "a", path: "x", size: 1 }] }, /unknown key\(s\): size/], [{ slug: "tism", source: ZIP, items: [{ id: "a", path: "x" }], match: "x" }, /"items" or "match", not both/], [{ slug: "tism", source: ZIP, match: "(" }, /not a valid regex/], [{ slug: "tism", source: ZIP, dryRun: "yes" }, /"dryRun" must be a boolean/], [{ slug: "no-such", source: ZIP }, /Channel "no-such" not found/], ]; for (const [body, re] of cases) { const r = await post(body); assert.equal(r.status, 400, JSON.stringify(body)); assert.match(r.json.error!, re, JSON.stringify(body)); assert.equal(r.json.jobId, undefined); } }); test("a dry run is a job whose log classifies the archive and writes nothing", async () => { const r = await post({ slug: "tism", source: ZIP, dryRun: true }); assert.equal(r.status, 200, JSON.stringify(r.json)); assert.ok(r.json.jobId); for (let i = 0; i < 100; i++) { const status = getRegistry().get(r.json.jobId!)?.status; if (status === "done" || status === "failed") break; await new Promise((res) => setTimeout(res, 50)); } assert.equal(getRegistry().get(r.json.jobId!)?.status, "done"); const log = await readFile(path.join(getPaths().jobsDir, `${r.json.jobId}.log`), "utf8"); const summary = JSON.parse(log.split("\n").find((l) => l.startsWith("summary: "))!.slice("summary: ".length)); assert.equal(summary.dryRun, true); assert.equal(summary.counts.attach, 1); assert.deepEqual(summary.notHeld, ["ccccccccccc"]); assert.equal(summary.lost, 1); assert.deepEqual(await readdir(path.join(CH, "tism", "data", "aaaaaaaaaaa")), ["metadata.info.json"]); });