import test from "node:test"; import assert from "node:assert/strict"; import { mkdir, mkdtemp, 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/api/channels/*/videos/*/files/*/route.test.ts" // // The media file route's byte ranges, in-process. The ABORT regression // (`Controller is already closed` when a scrubbing browser cancels range // requests) stays e2e in media-file-abort.spec.ts: a burst of cancelled // bodies run against this handler in-process passes even with the naive // wrapper that caused it — the race is in the server's response pipeline, not // in the handler — so only the HTTP round trip is a real test of it. The guard // itself is unit-tested in common/lib/safeStreamController.test.ts. const ROOT = await mkdtemp(path.join(os.tmpdir(), "media-file-route-")); const CORPUS = path.join(ROOT, "transcripts"); const VIDEO_DIR = path.join(CORPUS, "channels", "chan", "data", "vidA"); await mkdir(VIDEO_DIR, { recursive: true }); await writeFile(path.join(VIDEO_DIR, "audio.m4a"), Buffer.alloc(1024 * 1024, 7)); // Set before anything that caches getPaths() is first imported. process.env.TRANSCRIPTS_DIR = CORPUS; process.env.SETTINGS_FILE = path.join(ROOT, "settings.json"); const { GET } = await import("./route"); test.after(() => rm(ROOT, { recursive: true, force: true })); function get(range?: string) { return GET( new Request("http://localhost/api/channels/chan/videos/vidA/files/audio.m4a", { headers: range ? { range } : {}, }), { params: Promise.resolve({ slug: "chan", id: "vidA", name: "audio.m4a" }) }, ); } test("a range request is a 206 with the bytes asked for", async () => { const res = await get("bytes=100-4195"); assert.equal(res.status, 206); assert.equal(res.headers.get("content-range"), "bytes 100-4195/1048576"); const body = Buffer.from(await res.arrayBuffer()); assert.equal(body.length, 4096); }); test("a suffix range is the last N bytes; no or a bad range is the whole file", async () => { const suffix = await get("bytes=-100"); assert.equal(suffix.status, 206); assert.equal(suffix.headers.get("content-range"), "bytes 1048476-1048575/1048576"); assert.equal((await suffix.arrayBuffer()).byteLength, 100); for (const range of [undefined, "bytes=5-2", "bytes=0-9999999", "lines=1-2"]) { const res = await get(range); assert.equal(res.status, 200, String(range)); assert.equal(res.headers.get("content-length"), "1048576"); await res.body!.cancel(); } }); test("a path that leaves the video dir is refused; a missing file is a 404", async () => { const bad = await GET(new Request("http://localhost/x"), { params: Promise.resolve({ slug: "chan", id: "..%2F..", name: "audio.m4a" }), }); assert.equal(bad.status, 400); const missing = await GET(new Request("http://localhost/x"), { params: Promise.resolve({ slug: "chan", id: "vidA", name: "nope.m4a" }), }); assert.equal(missing.status, 404); });