import test from "node:test"; import assert from "node:assert/strict"; import { writeFile } from "node:fs/promises"; import path from "node:path"; import { callGet, callPost, setupOpsCorpus } from "./_testCorpus"; import { redactSecrets } from "./_read"; // Run with: // pnpm -C editor exec tsx --test "app/api/ops/_read.test.ts" // // The read side (release 19, A3): `get settings|storage|sites|workers| // auto-queue|scheduler|cleanup ` against a temp corpus — each behind the // token, each the payload its page already builds, no secret in any of them — // and the lane control route's new gate. const corpus = await setupOpsCorpus("one-youtube-channel-with-data"); await corpus.writeSettings({ minFreeDiskGB: 0, workers: [ { id: "lan-box", name: "LAN box", kind: "remote", enabled: false, priority: 1, remote: { baseUrl: "http://lan-box.local:3001", token: "never-printed" }, }, ], }); await corpus.writeSite("demo-site", { siteUrl: "https://demo.example", channels: [{ slug: "test-youtube", groupId: "default" }], }); await writeFile( path.join(corpus.transcripts, "channels", "test-youtube", "snapshot.json"), JSON.stringify({ generatedAt: "2026-10-01T00:00:00.000Z", buckets: { downloadedNoTranscript: ["a", "b"], downloadedAutoSubsOnly: [] }, cleanupBytes: { transcribedWithAudio: 1000, multipleAudioFormats: 10, foreignAudio: 5 }, }), ); type Get = Parameters[0]; const route = async (name: string) => (await import(`./${name}/route`)).GET as Get; test.after(() => corpus.cleanup()); test("every read route is behind the token", async () => { for (const name of ["settings", "storage", "sites", "workers", "auto-queue", "scheduler"]) { const res = await callGet(await route(name), undefined, {}, {}); assert.equal(res.status, 401, name); } const cleanup = await callGet(await route("cleanup/[slug]"), undefined, { slug: "test-youtube" }, {}); assert.equal(cleanup.status, 401); }); test("get settings is the editor's read, with every secret redacted; ?key= answers one block", async () => { const get = await route("settings"); const all = await callGet(get); assert.equal(all.status, 200); const settings = all.body.settings as Record; assert.equal(settings.minFreeDiskGB, 0); // A default the file never wrote is there: this is getSettings, not the file. assert.ok("autoQueue" in settings); assert.ok(!JSON.stringify(all.body).includes("never-printed")); const workers = settings.workers as { id: string; remote?: { token?: string } }[]; assert.equal(workers.find((w) => w.id === "lan-box")?.remote?.token, ""); const one = await callGet(get, "http://localhost/api/ops/settings?key=minFreeDiskGB"); assert.deepEqual(one.body, { ok: true, key: "minFreeDiskGB", value: 0 }); const nope = await callGet(get, "http://localhost/api/ops/settings?key=minFreeDisk"); assert.equal(nope.status, 400); assert.match(String(nope.body.error), /no settings key "minFreeDisk" — known: .*minFreeDiskGB/); const stray = await callGet(get, "http://localhost/api/ops/settings?block=x"); assert.equal(stray.status, 400); assert.match(String(stray.body.error), /unknown query key\(s\): block — accepted: key/); }); test("redactSecrets replaces secret-named strings at any depth and leaves the rest", () => { assert.deepEqual( redactSecrets({ a: { apiKey: "k", token: "", n: 1 }, list: [{ password: "p", name: "x" }], maxTokens: 5 }), { a: { apiKey: "", token: "", n: 1 }, list: [{ password: "", name: "x" }], maxTokens: 5 }, ); }); test("get sites lists each site from its site.json", async () => { const res = await callGet(await route("sites")); assert.equal(res.status, 200); assert.deepEqual(res.body.sites, [ { siteId: "demo-site", title: "demo-site", siteUrl: "https://demo.example", cloudflareProject: null, audience: "public", listed: true, search: true, publish: null, channels: ["test-youtube"], }, ]); }); test("get cleanup is the channel's ledger row, counts for the id lists", async () => { const get = await route("cleanup/[slug]"); const res = await callGet(get, undefined, { slug: "test-youtube" }); assert.equal(res.status, 200); assert.equal(res.body.slug, "test-youtube"); assert.equal(res.body.transcribedBytes, 1000); assert.equal(res.body.extraFormatsBytes, 10); assert.equal(res.body.foreignBytes, 5); assert.equal(res.body.included, true); assert.equal(res.body.measured, false); assert.equal(res.body.downloadedNoTranscript, 2); assert.equal(res.body.reportedAt, "2026-10-01T00:00:00.000Z"); assert.equal(res.body.channel, undefined); assert.equal(res.body.snapshot, undefined); const missing = await callGet(get, undefined, { slug: "no-such" }); assert.equal(missing.status, 404); const bad = await callGet(get, undefined, { slug: ".escape" }); assert.equal(bad.status, 400); }); test("get workers, auto-queue, scheduler and storage answer their pages' payloads", async () => { const workers = await callGet(await route("workers")); assert.equal(workers.status, 200, JSON.stringify(workers.body)); assert.ok(!JSON.stringify(workers.body).includes("never-printed")); const lanes = await callGet(await route("auto-queue")); assert.equal(lanes.status, 200, JSON.stringify(lanes.body)); assert.ok("transcription" in (lanes.body.autoQueue as Record)); const scheduler = await callGet(await route("scheduler")); assert.equal(scheduler.status, 200, JSON.stringify(scheduler.body)); const storage = await callGet(await route("storage")); assert.equal(storage.status, 200, JSON.stringify(storage.body)); }); test("/api/auto-queue/control is behind the ops token now", async () => { const { POST } = await import("../auto-queue/control/route"); const anon = await callPost(POST, { kind: "download", action: "stop" }, {}); assert.equal(anon.status, 401); const wrong = await callPost(POST, { kind: "download", action: "stop" }, { authorization: "Bearer nope" }); assert.equal(wrong.status, 401); // With the token it is the route it always was. const bad = await callPost(POST, { kind: "transcode", action: "stop" }); assert.equal(bad.status, 400); assert.match(String(bad.body.error), /kind must be one of/); });