import test from "node:test"; import assert from "node:assert/strict"; import { setupOpsCorpus, callPost } from "../_testCorpus"; // Run with: // pnpm -C editor exec tsx --test "app/api/ops/import-archive-org/route.test.ts" // // The three body shapes (item / items / query) and every refusal that comes // before a job — none of them asks archive.org anything, so no network is // touched and no job is queued. The batch itself (held, restricted, the // inter-item gap, the 401 storm) is common/controller/archiveOrgImport.test.ts. const corpus = await setupOpsCorpus(null); await corpus.writeChannelConfig("demo-archive", { handling: "transcribe", platform: "archiveorg" }); const { POST } = await import("./route"); test.after(() => corpus.cleanup()); test("refusals before any job: keys, modes, items, limit, regex, identifiers, channel", async () => { const many = Array.from({ length: 501 }, (_, i) => `item-${i}`); const cases: [Record, RegExp][] = [ [{ slug: "demo-archive", items: ["a"], force: true }, /unknown key\(s\): force/], [{ slug: "demo-archive" }, /exactly one of "item", "items" \(a list\) or "query"/], [{ slug: "demo-archive", item: "a", query: "x" }, /exactly one of "item", "items"/], [{ slug: "demo-archive", items: ["a"], query: "x" }, /exactly one of "item", "items"/], [{ slug: "demo-archive", item: "a" }, /exactly one of "files" \(a list\) or "match"/], [{ slug: "demo-archive", item: "a", files: ["x.mp4"], match: "." }, /exactly one of "files"/], [{ slug: "demo-archive", item: "../a", match: "." }, /not an archive.org identifier/], [{ slug: "demo-archive", items: [] }, /"items" must be a non-empty array/], [{ slug: "demo-archive", items: many }, /holds 501 items; at most 500/], [{ slug: "demo-archive", items: [{ item: "a", size: 1 }] }, /"items\[0\]" has unknown key\(s\): size/], [{ slug: "demo-archive", items: [{ files: ["x.mp4"] }] }, /"items\[0\].item" is required/], [{ slug: "demo-archive", items: [{ item: "a", files: [] }] }, /"items\[0\].files" must be a non-empty array/], [{ slug: "demo-archive", items: [{ item: "a", files: ["x"], match: "." }] }, /item "a": "files" or "match", not both/], [{ slug: "demo-archive", items: ["ok-1", "bad id"] }, /"bad id" is not an archive.org identifier/], [{ slug: "demo-archive", items: [{ item: "a", match: "(" }] }, /item "a": "match" is not a valid regex/], [{ slug: "demo-archive", items: ["a"], files: ["x.mp4"] }, /with "items", give each its own/], [{ slug: "demo-archive", items: ["a"], limit: 5 }, /"limit" caps a "query"/], [{ slug: "demo-archive", query: "x", limit: 501 }, /"limit" is at most 500/], [{ slug: "demo-archive", query: "x", limit: 0 }, /"limit" must be a whole number above zero/], [{ slug: "demo-archive", query: " " }, /"query" must be a non-empty string/], [{ slug: "demo-archive", query: "x", match: "(" }, /"match" is not a valid regex/], [{ slug: "demo-archive", query: "x", dryRun: "yes" }, /"dryRun" must be a boolean/], [{ slug: "no-such", items: ["a"] }, /Channel "no-such" not found/], ]; for (const [body, re] of cases) { const r = await callPost(POST, body); assert.equal(r.status, 400, JSON.stringify(body)); assert.match(r.body.error ?? "", re, JSON.stringify(body)); assert.equal(r.body.jobId, undefined); } assert.deepEqual(await corpus.listJobIds(), []); });