import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { CONTRACT } from "./contract"; import { RemoteSource, coerceReportIndex, corpusScopeOf } from "./reader"; import { LocalSource } from "./reader-fs"; import { HubSource } from "./reader-hub"; import { REPORT_INDEX_FORMAT, REPORT_PAGE_FORMAT, REPORT_VIEWS_VERSION, type ReportIndexView, type ReportPageView, } from "../report/views"; // Spec 5 for the three readers: a CITED site (corpus.json `site.scope: // "cited"`) reads as an empty corpus without an error and names its reports; a // full site with reports reads its channels exactly as before. const ORIGIN = "https://reports.example"; const CITED_CORPUS = { spec: CONTRACT.corpusSpec, kind: "site", site: { id: "demo-reports", title: "Demo reports", url: ORIGIN, scope: "cited", audience: "public" }, totals: { channels: 0, videos: 0 }, channels: [], reports: { index: `${ORIGIN}/reports/index.json`, count: 1 }, }; const FULL_CORPUS = { spec: CONTRACT.corpusSpec, kind: "site", site: { id: "demo-full", title: "Demo full", url: ORIGIN }, channels: [{ slug: "demo-channel", name: "Demo Channel", videoCount: 1 }], reports: { index: `${ORIGIN}/reports/index.json`, count: 1 }, }; const INDEX: ReportIndexView = { format: REPORT_INDEX_FORMAT, version: REPORT_VIEWS_VERSION, reports: [ { id: "demo-report", kind: "factcheck", title: "A demo report", href: "/reports/demo-report/", citationCount: 1, claimCount: 1, tally: [{ verdict: "CORROBORATED", count: 1 }], }, ], }; const PAGE = { format: REPORT_PAGE_FORMAT, version: REPORT_VIEWS_VERSION, id: "demo-report", kind: "factcheck", title: "A demo report", sources: {}, verdicts: {}, citations: {}, sections: [{ id: "s1", title: "One", claims: [] }], } as unknown as ReportPageView; function installFetch(body: Record, fetches: string[] = []): () => void { const real = globalThis.fetch; globalThis.fetch = (async (input: RequestInfo | URL) => { const url = String(input); fetches.push(url); if (!(url in body)) return new Response("not found", { status: 404, statusText: "Not Found" }); return new Response(JSON.stringify(body[url]), { status: 200 }); }) as typeof fetch; return () => { globalThis.fetch = real; }; } test("corpusScopeOf: only an explicit cited scope is cited; a pre-spec-5 corpus is full", () => { assert.equal(corpusScopeOf(CITED_CORPUS), "cited"); assert.equal(corpusScopeOf(FULL_CORPUS), "full"); assert.equal(corpusScopeOf({ site: { scope: "other" } }), "full"); assert.equal(corpusScopeOf(null), "full"); }); test("coerceReportIndex keeps the entries of an index and nothing else", () => { assert.deepEqual(coerceReportIndex(INDEX).map((e) => e.id), ["demo-report"]); assert.deepEqual(coerceReportIndex({ reports: INDEX.reports }), [], "no format: not an index"); assert.deepEqual(coerceReportIndex(null), []); assert.deepEqual( coerceReportIndex({ ...INDEX, reports: [{ id: 1 }, null, ...INDEX.reports] }).map((e) => e.id), ["demo-report"], ); }); test("remote: a cited site is an empty corpus with its reports, read without an error", async () => { const fetches: string[] = []; const restore = installFetch( { [`${ORIGIN}/corpus.json`]: CITED_CORPUS, [`${ORIGIN}/reports/index.json`]: INDEX, [`${ORIGIN}/reports/demo-report/page.json`]: PAGE, }, fetches, ); try { const r = new RemoteSource(ORIGIN); assert.deepEqual(await r.listChannels(), []); assert.equal(await r.scope(), "cited"); assert.deepEqual((await r.reports()).map((e) => e.title), ["A demo report"]); assert.equal((await r.reportPage("demo-report"))?.title, "A demo report"); assert.equal(await r.reportPage("nope"), null, "an unknown report is null, not a throw"); // The empty reads a tool makes on any source degrade, they do not throw. assert.deepEqual((await r.loadGroups()).groups, []); assert.equal((await r.videoIndex()).size, 0); await r.reports(); assert.equal(fetches.filter((u) => u.endsWith("/reports/index.json")).length, 1, "the index is cached"); } finally { restore(); } }); test("remote: a report id is one path segment — it cannot leave /reports/", async () => { const fetches: string[] = []; const restore = installFetch({ [`${ORIGIN}/corpus.json`]: CITED_CORPUS }, fetches); try { assert.equal(await new RemoteSource(ORIGIN).reportPage("../corpus"), null); assert.deepEqual(fetches, [`${ORIGIN}/reports/..%2Fcorpus/page.json`]); } finally { restore(); } }); test("remote: a full site with reports reads its channels as before; one without has no reports", async () => { const restore = installFetch({ [`${ORIGIN}/corpus.json`]: FULL_CORPUS, [`${ORIGIN}/reports/index.json`]: INDEX, }); try { const r = new RemoteSource(ORIGIN); assert.deepEqual((await r.listChannels()).map((c) => c.slug), ["demo-channel"]); assert.equal(await r.scope(), "full"); assert.equal((await r.reports()).length, 1); } finally { restore(); } const restore2 = installFetch({ [`${ORIGIN}/corpus.json`]: { ...FULL_CORPUS, spec: 4, reports: undefined } }); try { const r = new RemoteSource(ORIGIN); assert.equal(await r.scope(), "full"); assert.deepEqual(await r.reports(), [], "a 404 index is no reports"); } finally { restore2(); } }); test("hub: a cited member federates no channels and fails nothing", async () => { const HUB = "https://hub.example"; const FULL = "https://full.example"; const restore = installFetch({ [`${HUB}/corpus.json`]: { kind: "hub", sites: [ { siteId: "demo-reports", title: "Demo reports", url: ORIGIN }, { siteId: "demo-full", title: "Demo full", url: FULL }, ], }, [`${ORIGIN}/corpus.json`]: CITED_CORPUS, [`${FULL}/corpus.json`]: { ...FULL_CORPUS, site: { ...FULL_CORPUS.site, url: FULL } }, }); try { const hub = new HubSource(HUB); const channels = await hub.listChannels(); assert.deepEqual(channels.map((c) => c.key), ["demo-full/demo-channel"]); assert.equal((await hub.videoIndex()).size, 0); } finally { restore(); } }); async function writeDir(files: Record): Promise { const dir = await mkdtemp(path.join(os.tmpdir(), "reader-reports-")); for (const [rel, body] of Object.entries(files)) { const file = path.join(dir, rel); await mkdir(path.dirname(file), { recursive: true }); await writeFile(file, JSON.stringify(body)); } return dir; } test("local: a cited dir never falls back to a stale transcripts/ listing", async () => { const dir = await writeDir({ "corpus.json": CITED_CORPUS, "reports/index.json": INDEX, "reports/demo-report/page.json": PAGE, // A stale channel tree a cited build must not be read as. "transcripts/demo-channel/manifest.json": { version: 1, pageCount: 0, slugToPage: {} }, }); try { const l = new LocalSource(dir); assert.deepEqual(await l.listChannels(), []); assert.equal(await l.scope(), "cited"); assert.equal(l.publicOrigin(), ORIGIN); assert.deepEqual((await l.reports()).map((e) => e.id), ["demo-report"]); assert.equal((await l.reportPage("demo-report"))?.id, "demo-report"); assert.equal(await l.reportPage("missing"), null); assert.equal(await l.reportPage("../corpus.json"), null, "not a report id"); } finally { await rm(dir, { recursive: true, force: true }); } }); test("local: a full dir with no reports reads as before", async () => { const dir = await writeDir({ "corpus.json": { ...FULL_CORPUS, reports: undefined } }); try { const l = new LocalSource(dir); assert.deepEqual((await l.listChannels()).map((c) => c.slug), ["demo-channel"]); assert.equal(await l.scope(), "full"); assert.deepEqual(await l.reports(), []); } finally { await rm(dir, { recursive: true, force: true }); } }); test("local: a page.json that is not a report page is an error, not an absence", async () => { const dir = await writeDir({ "corpus.json": CITED_CORPUS, "reports/demo-report/page.json": { hello: 1 } }); try { await assert.rejects(() => new LocalSource(dir).reportPage("demo-report"), /not a report page view/); } finally { await rm(dir, { recursive: true, force: true }); } });