Public: anyone can read this; the crew can change it.
The forge's own signed-out pages go through webspec() in a test, with today's findings named #3047
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/3043-the-forge-s-own-signed-out-pages-go-thro → main 2 commits fork 4 threads · 4 unresolved
src/release/webspec.ts +4 −2
| @@ -150,8 +150,10 @@ export function headTargets(html: string, pageUrl: string): { urls: string[]; ba | ||
| 150 | 150 | const bad: string[] = []; |
| 151 | 151 | for (const h of hrefs) { |
| 152 | 152 | try { |
| 153 | const u = new URL(h, pageUrl).href; | |
| 154 | if (!urls.includes(u)) urls.push(u); | |
| 153 | const u = new URL(h, pageUrl); | |
| 154 | // A data: icon carries its own bytes; there is nothing to fetch. | |
| 155 | if (u.protocol === "data:") continue; | |
| 156 | if (!urls.includes(u.href)) urls.push(u.href); | |
| 155 | 157 | } catch { |
| 156 | 158 | bad.push(h); |
| 157 | 159 | } |
test/forge-webspec.test.ts added +59
| @@ -0,0 +1,59 @@ | ||
| 1 | /** | |
| 2 | * The forge's own signed-out pages through webspec() (#3043, part of #2915): | |
| 3 | * no socket, each request handed to forgeHttp(app).handle. Every finding that | |
| 4 | * stands today is named in KNOWN with its section and why; a new one fails. | |
| 5 | */ | |
| 6 | import { afterAll, beforeAll, expect, test } from "bun:test"; | |
| 7 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 8 | import { tmpdir } from "node:os"; | |
| 9 | import { join } from "node:path"; | |
| 10 | import { createApp, type App } from "../src/app.ts"; | |
| 11 | import { forgeHttp, type ForgeHttp } from "../src/http/server.ts"; | |
| 12 | import { webspec } from "../src/release/webspec.ts"; | |
| 13 | import { quietLogs } from "./helpers/globals.ts"; | |
| 14 | ||
| 15 | quietLogs(); | |
| 16 | ||
| 17 | const PATHS = ["/", "/signin", "/signup", "/explore"]; | |
| 18 | ||
| 19 | /** | |
| 20 | * `check path` -> [section, why it stands]. Every one is expected on every | |
| 21 | * page it names; one fixed fails the test, one new fails it too. "compression" | |
| 22 | * is an edge finding (set by the CDN in front of the forge): listed, not hidden. | |
| 23 | */ | |
| 24 | const KNOWN: Record<string, [string, string]> = {}; | |
| 25 | const WHY: Record<string, [string, string]> = { | |
| 26 | canonical: ["https://specification.website/#canonical", "no page emits <link rel=canonical>; one origin serves each page, so nothing duplicates yet"], | |
| 27 | "color-scheme": ["https://specification.website/#color-scheme", "pages carry no color-scheme meta; the stylesheet picks the theme"], | |
| 28 | compression: ["https://specification.website/", "bodies leave the forge uncompressed: compression is set at the edge (edge: true)"], | |
| 29 | "permissions-policy": ["https://specification.website/#permissions-policy", "the security floor sets CSP, nosniff and Referrer-Policy but no Permissions-Policy"], | |
| 30 | etag: ["https://specification.website/#caching", "server-rendered HTML carries no ETag, so a revisit is never a 304"], | |
| 31 | }; | |
| 32 | for (const [check, why] of Object.entries(WHY)) for (const path of PATHS) KNOWN[`${check} ${path}`] = why; | |
| 33 | ||
| 34 | let dir = ""; | |
| 35 | let app: App; | |
| 36 | let http: ForgeHttp; | |
| 37 | ||
| 38 | beforeAll(async () => { | |
| 39 | dir = await mkdtemp(join(tmpdir(), "forge-webspec-")); | |
| 40 | app = createApp({ dataDir: dir, port: 0 }); | |
| 41 | http = forgeHttp(app); | |
| 42 | }); | |
| 43 | ||
| 44 | afterAll(async () => { | |
| 45 | http.close(); | |
| 46 | app.close(); | |
| 47 | await rm(dir, { recursive: true, force: true }); | |
| 48 | }); | |
| 49 | ||
outdated
Section-only match lets any new finding of a known check through, on any path, with any message. Match |
||
| 50 | test("the forge's signed-out pages raise exactly the findings named in KNOWN", async () => { | |
outdated
Add the reverse assertion: every KNOWN check must appear in |
||
| 51 | const fetcher = (input: string, init?: RequestInit) => http.handle(new Request(input, init)); | |
| 52 | const found = await webspec("http://forge.test", PATHS, fetcher, { site: true }); | |
| 53 | const keyOf = (f: { check: string; path: string }) => `${f.check} ${f.path}`; | |
| 54 | // New: not named, or citing another section than the one named. | |
| 55 | expect(found.filter((f) => KNOWN[keyOf(f)]?.[0] !== f.section).map((f) => `${keyOf(f)}: ${f.message}`)).toEqual([]); | |
| 56 | // Fixed: named but no longer found, so the entry must go. | |
| 57 | const seen = new Set(found.map(keyOf)); | |
| 58 | expect(Object.keys(KNOWN).filter((k) => !seen.has(k))).toEqual([]); | |
| 59 | }); |
test/webspec.test.ts +6
| @@ -205,3 +205,9 @@ test("compression: a big page and stylesheet must leave encoded with Vary", asyn | ||
| 205 | 205 | expect(found[1]!.message).toContain("/a.css"); |
| 206 | 206 | expect(found[0]!.section).toBe("https://specification.website/"); |
| 207 | 207 | }); |
| 208 | ||
| 209 | test("a data: icon is not fetched: it carries its own bytes", async () => { | |
| 210 | const { headTargets } = await import("../src/release/webspec.ts"); | |
Nit: use a static import of headTargets at the top of the file, as the other tests do, instead of a dynamic import inside the test. |
||
| 211 | const html = `<link rel="icon" href="data:image/svg+xml,%3Csvg/%3E"><link rel="manifest" href="/m.json">`; | |
| 212 | expect(headTargets(html, "https://x.test/")).toEqual({ urls: ["https://x.test/m.json"], bad: [] }); | |
| 213 | }); |
A data: icon is webspec's gap, not the forge's. Fix it in webspec (skip data: URIs) rather than excusing every head-links finding on every page.