import test from "node:test"; import assert from "node:assert/strict"; import { readdir, readFile } from "node:fs/promises"; import path from "node:path"; import { TOKEN, callGet, callPost, setupOpsCorpus } from "./_testCorpus"; // Run with: // pnpm -C editor exec tsx --test "app/api/ops/_door.test.ts" // // THE DOOR EVERY OPS ROUTE SHARES: the token gate's three answers, unknown // keys, and the traversing slug — checked against the routes themselves, in // the request/response alone. Moved from e2e/ops-api.spec.ts (release 19, // A2b): none of it needs a browser or a job, and none of it writes. // // The 503 used to need /api/test/worker-token to switch the variable off // inside the one test server; here it is this process's own environment, // read per call by getWorkerToken(). const corpus = await setupOpsCorpus("title-filter-channel"); const route = async (name: string) => (await import(`./${name}/route`)) as { POST?: Parameters[0]; GET?: Parameters[0]; }; test.after(() => corpus.cleanup()); test("the token gate answers 401 for a missing and for a wrong bearer", async () => { const refresh = (await route("refresh-report")).POST!; const channel = (await route("channel/[slug]")).GET!; const cases: Record[] = [{}, { authorization: "Bearer wrong" }]; for (const headers of cases) { const post = await callPost(refresh, { all: true }, headers); assert.equal(post.status, 401, JSON.stringify(headers)); // The READ side is gated by the same token, not merely the write side. const get = await callGet(channel, "http://localhost/api/ops/channel/anything", { slug: "anything" }, headers); assert.equal(get.status, 401, JSON.stringify(headers)); } }); // UNSET IS OFF, on the ops door and on the worker door alike — one branch, // shared, and neither surface may decide for itself that "no token configured" // means "let them in". test("with no token configured every guarded route answers 503", async () => { const tags = (await route("tags")).GET!; const health = (await import("../worker/health/route")).GET as unknown as Parameters[0]; const saved = process.env.WORKER_TOKEN; delete process.env.WORKER_TOKEN; try { // The ops door, with the RIGHT token: it is the surface being off that // answers, not the credential being wrong. const off = await callGet(tags); assert.equal(off.status, 503); assert.match(String(off.body.error), /set WORKER_TOKEN to enable/); // And the LAN worker door, which shares the branch. const worker = await callGet(health); assert.equal(worker.status, 503); assert.match(String(worker.body.error), /set WORKER_TOKEN to enable/); // With no token configured a MISSING header is still 503, not 401: there // is nothing to be unauthorized against. assert.equal((await callGet(tags, undefined, {}, {})).status, 503); } finally { process.env.WORKER_TOKEN = saved; } assert.equal((await callGet(tags)).status, 200); assert.equal(saved, TOKEN); }); test("an unknown body key is a 400 that names the accepted keys", async () => { // A misspelled key would otherwise get a cheerful { ok: true } and a channel // that did not change. const sync = await callPost((await route("sync")).POST!, { slug: "x", fullSweep: true }); assert.equal(sync.status, 400); assert.equal(sync.body.ok, false); assert.match(String(sync.body.error), /unknown key\(s\): fullSweep/); assert.match(String(sync.body.error), /full/); // So is a nested one, on the route whose body carries an object. const patch = await callPost((await route("channel-config")).POST!, { slug: "x", patch: { downloadFilterExcluded: "rerun" }, }); assert.equal(patch.status, 400); assert.match(String(patch.body.error), /downloadFilterExcluded/); }); test("a traversing slug is refused at the door, on every route that takes one", async () => { const channelsDir = path.join(corpus.transcripts, "channels"); const before = (await readdir(channelsDir)).sort(); assert.deepEqual(before, ["test-filter"]); // EVERY SLUG BELOW REACHES A path.join UNDER channelsDir, and the readers // swallow their own errors — so an unchecked traversing segment would fail // SILENTLY (an empty config read as "channel not found") rather than loudly, // and any future writer on that path would land outside the corpus. reqSlug // is one check for all of them; this is the assertion that it is wired to // each. const cases: [string, Record][] = [ ["metadata-scan", { slug: "../../escape" }], ["refresh-metadata", { slug: "../../escape", id: "abc123" }], ["sync", { slug: "../../escape" }], ["download-missing", { slug: "../../escape" }], ["import-video", { slug: "../../escape", url: "https://example.com/v" }], ["retry-bucket", { slug: "../../escape", bucket: "noTranscript" }], ["refresh-report", { slug: "../../escape" }], ["channel-config", { slug: "../../escape", patch: { cookieMode: "always" } }], ["channel-priority", { slugs: ["../../escape"], tier: "paused" }], ["relocate", { slugs: ["../../escape"], root: "/tmp/ops-api-never" }], ["relocate-back", { slugs: ["../../escape"] }], ["fetch-posts", { slug: "../../escape", older: true }], ["capture-posts", { slug: "../../escape", ids: ["1"] }], ["persist-videos", { items: [{ slug: "../../escape", id: "abc123" }] }], ]; for (const [action, data] of cases) { const { status, body } = await callPost((await route(action)).POST!, data); assert.equal(status, 400, action); assert.match(String(body.error), /is not a valid channel slug/, action); } // The read route takes its slug as a path SEGMENT. A one-segment name // CHANNEL_SLUG_RE still refuses — a leading dot, how a dotfile beside the // channels dir would be named at — reaches the handler and is refused there. const read = await callGet( (await route("channel/[slug]")).GET!, "http://localhost/api/ops/channel/.escape", { slug: ".escape" }, ); assert.equal(read.status, 400); assert.match(String(read.body.error), /is not a valid channel slug/); // NOTHING WAS TOUCHED: the corpus still holds exactly the fixture channel, // and the fixture's own config is byte-identical. assert.deepEqual((await readdir(channelsDir)).sort(), before); assert.deepEqual( JSON.parse( await readFile(path.join(channelsDir, "test-filter", "config.json"), "utf8"), ), { handling: "youtube", name: "Test Title Filter", url: "https://www.youtube.com/@example/videos", downloadFilter: { include: "guest" }, }, ); assert.deepEqual(await corpus.listJobIds(), []); });