import { test } from "node:test"; import assert from "node:assert/strict"; import { REPORT_FEED_FILENAMES, REPORT_FEED_FORMATS, REPORT_FEED_MIME } from "../report/views"; import { HUB_CORS_PATHS, SITE_CORS_PATHS, SITE_TYPED_PATHS, contractCorsPaths, renderHeadersFile, } from "./headers"; // The whole generated site block, as a snapshot. `_headers` is a wire artifact: // a line lost here is a cross-origin reader that stops working on the CDN and // nowhere else (local `serve` blankets `**/*.json` with CORS). So the file is // asserted in full — an edit has to be deliberate enough to update this string. const SITE_HEADERS = `# Generated by compose-site.ts — do not edit by hand. /site.json Access-Control-Allow-Origin: * /search-aliases.json Access-Control-Allow-Origin: * /duplicates.json Access-Control-Allow-Origin: * /tags.json Access-Control-Allow-Origin: * /summaries/* Access-Control-Allow-Origin: * /subs/* Access-Control-Allow-Origin: * /transcripts/* Access-Control-Allow-Origin: * /posts/* Access-Control-Allow-Origin: * /digests/* Access-Control-Allow-Origin: * /stats/* Access-Control-Allow-Origin: * /archives/* Access-Control-Allow-Origin: * /corpus.json Access-Control-Allow-Origin: * /llms.txt Access-Control-Allow-Origin: * /robots.txt Access-Control-Allow-Origin: * /sitemap.xml Access-Control-Allow-Origin: * `; const HUB_HEADERS = `# Generated by compose-hub.ts — do not edit by hand. /hub-sites.json Access-Control-Allow-Origin: * /site.json Access-Control-Allow-Origin: * /summaries/* Access-Control-Allow-Origin: * /subs/* Access-Control-Allow-Origin: * /transcripts/* Access-Control-Allow-Origin: * /stats/* Access-Control-Allow-Origin: * /corpus.json Access-Control-Allow-Origin: * /llms.txt Access-Control-Allow-Origin: * /robots.txt Access-Control-Allow-Origin: * `; test("renderHeadersFile: the site block, in full", () => { assert.equal(renderHeadersFile("compose-site.ts"), SITE_HEADERS); assert.equal( renderHeadersFile("compose-site.ts", SITE_CORS_PATHS), SITE_HEADERS, ); }); test("renderHeadersFile: the hub block, in full", () => { assert.equal(renderHeadersFile("compose-hub.ts", HUB_CORS_PATHS), HUB_HEADERS); }); // The tombstone blocks (release 18): withdrawn X posts are served no-store. A // site's /posts/* CORS rule already covers them, so its no-store rules carry no // second CORS header (a repeated header is APPENDED: "*, *"); the hub lists no // posts tree, so its /posts/* rule carries both. const SITE_TOMBSTONE_BLOCK = `# Withdrawn content (tombstones): never stored at the edge. /posts/manifest.json Cache-Control: no-store /posts/jer-x/* Cache-Control: no-store `; const HUB_TOMBSTONE_BLOCK = `# Withdrawn content (tombstones): never stored at the edge. /posts/* Cache-Control: no-store Access-Control-Allow-Origin: * `; test("renderHeadersFile: a site's no-store block follows its CORS lines, with no second CORS header", () => { assert.equal( renderHeadersFile("compose-site.ts", SITE_CORS_PATHS, { noStore: ["/posts/manifest.json", "/posts/jer-x/*"], }), SITE_HEADERS + SITE_TOMBSTONE_BLOCK, ); // No paths, no block: the file is byte-identical to the one without opts. assert.equal(renderHeadersFile("compose-site.ts", SITE_CORS_PATHS, { noStore: [] }), SITE_HEADERS); }); test("renderHeadersFile: the hub's /posts/* no-store rule carries its own CORS", () => { assert.equal( renderHeadersFile("compose-hub.ts", HUB_CORS_PATHS, { noStore: ["/posts/*"] }), HUB_HEADERS + HUB_TOMBSTONE_BLOCK, ); }); test("no rendered file sets a header twice for one path", () => { // Every pair of rules that both match a path must not both set the same // header: wrangler appends the second value. const files = [ renderHeadersFile("s", SITE_CORS_PATHS, { noStore: ["/posts/manifest.json", "/posts/a/*"] }), renderHeadersFile("h", HUB_CORS_PATHS, { noStore: ["/posts/*"] }), ]; for (const file of files) { const rules: { path: string; headers: string[] }[] = []; for (const line of file.split("\n")) { if (line.startsWith("/")) rules.push({ path: line, headers: [] }); else if (line.startsWith(" ")) rules[rules.length - 1].headers.push(line.trim().split(":")[0]); } const covers = (rule: string, p: string) => rule.endsWith("*") ? p.startsWith(rule.slice(0, -1)) : rule === p; for (const probe of ["/posts/manifest.json", "/posts/a/page-0000.json", "/corpus.json"]) { const set = rules.filter((r) => covers(r.path, probe)).flatMap((r) => r.headers); assert.equal(new Set(set).size, set.length, `${probe}: ${set.join(", ")}`); } } }); test("the two paths that used to be served without CORS are declared", () => { // The wire change. A cross-origin viewer could read every other tree and got // a CORS failure on exactly these two. assert.ok(SITE_CORS_PATHS.includes("/digests/*")); assert.ok(SITE_CORS_PATHS.includes("/duplicates.json")); }); test("every contract layer and root file has a CORS line", () => { for (const p of contractCorsPaths()) { assert.ok( SITE_CORS_PATHS.includes(p), `${p} is on the wire but has no _headers entry`, ); } }); test("the hub surface is the site surface plus its own pool file", () => { for (const p of HUB_CORS_PATHS) { if (p === "/hub-sites.json") continue; assert.ok( SITE_CORS_PATHS.includes(p), `${p} is declared by the hub but not by a site`, ); } }); test("every rendered entry is one path line and one indented header", () => { const lines = renderHeadersFile("x.ts").split("\n").slice(0, -1); assert.equal(lines[0], "# Generated by x.ts — do not edit by hand."); const body = lines.slice(1); assert.equal(body.length, SITE_CORS_PATHS.length * 2); for (let i = 0; i < body.length; i += 2) { assert.ok(body[i].startsWith("/")); assert.equal(body[i + 1], " Access-Control-Allow-Origin: *"); } }); // A report's timeline feeds (lib/report/feeds.ts) are served with their own // media type, and readable cross-origin (no site CORS rule covers reports/). const SITE_TYPES_BLOCK = `# Served with their own media type. /reports/:report/feed.xml Content-Type: application/rss+xml; charset=utf-8 Access-Control-Allow-Origin: * /reports/:report/feed.json Content-Type: application/feed+json; charset=utf-8 Access-Control-Allow-Origin: * `; test("renderHeadersFile: a site's typed paths follow its CORS lines, before the no-store block", () => { assert.equal(renderHeadersFile("compose-site.ts", SITE_CORS_PATHS, { types: SITE_TYPED_PATHS }), SITE_HEADERS + SITE_TYPES_BLOCK); assert.equal( renderHeadersFile("compose-site.ts", SITE_CORS_PATHS, { types: SITE_TYPED_PATHS, noStore: ["/posts/manifest.json", "/posts/jer-x/*"] }), SITE_HEADERS + SITE_TYPES_BLOCK + SITE_TOMBSTONE_BLOCK, ); // No types, no block. assert.equal(renderHeadersFile("compose-site.ts", SITE_CORS_PATHS, { types: [] }), SITE_HEADERS); }); test("the typed paths are the report feeds' files and media types", () => { assert.deepEqual( SITE_TYPED_PATHS, REPORT_FEED_FORMATS.map((f) => ({ path: `/reports/:report/${REPORT_FEED_FILENAMES[f]}`, type: `${REPORT_FEED_MIME[f]}; charset=utf-8` })), ); });