Public: anyone can read this; the crew can change it.
A test boots a freshly scaffolded app and holds its webspec() findings to a named list #3046
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/3041-a-test-boots-a-freshly-scaffolded-app-an → main 2 commits fork 2 threads · 2 unresolved
1 file
changed +68
−0
test/scaffold-webspec.test.ts added +68
| @@ -0,0 +1,68 @@ | ||
| 1 | /** | |
| 2 | * A freshly scaffolded app is held to the website specification (#3041). | |
| 3 | * The scaffold is written out, booted on a loopback port, and `webspec()` | |
| 4 | * reads `/` and the site files. The findings' checks must equal KNOWN: a | |
| 5 | * new gap fails, and so does a fixed gap still listed. | |
| 6 | */ | |
| 7 | import { afterAll, beforeAll, expect, test } from "bun:test"; | |
| 8 | import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; | |
| 9 | import { tmpdir } from "node:os"; | |
| 10 | import { dirname, join } from "node:path"; | |
| 11 | import { scaffoldFiles } from "../src/apps/scaffold.ts"; | |
| 12 | import { webspec, type Finding } from "../src/release/webspec.ts"; | |
| 13 | ||
| 14 | /** Today's gaps, each with its specification.website section. */ | |
| 15 | const KNOWN: string[] = [ | |
| 16 | "change-password", // https://specification.website/#change-password | |
| 17 | "etag", // https://specification.website/#caching | |
| 18 | "robots", // https://specification.website/#robots | |
| 19 | "security-txt", // https://specification.website/#security-txt | |
| 20 | ]; | |
| 21 | ||
| 22 | let dir: string; | |
| 23 | let child: ReturnType<typeof Bun.spawn> | undefined; | |
| 24 | let findings: Finding[] = []; | |
| 25 | ||
| 26 | beforeAll(async () => { | |
| 27 | dir = await mkdtemp(join(tmpdir(), "forge-scaffold-webspec-")); | |
| 28 | for (const f of scaffoldFiles({ name: "field-notes", description: "" })) { | |
| 29 | const path = join(dir, f.path); | |
| 30 | await mkdir(dirname(path), { recursive: true }); | |
| 31 | if (f.content !== null) await writeFile(path, f.content); | |
| 32 | } | |
| 33 | const port = 20000 + Math.floor(Math.random() * 20000); | |
| 34 | child = Bun.spawn(["bun", "run", "src/server.ts"], { | |
| 35 | cwd: dir, | |
| 36 | env: { | |
| 37 | PATH: process.env.PATH ?? "/usr/bin:/bin", | |
| 38 | HOME: dir, | |
| 39 | TMPDIR: process.env.TMPDIR ?? tmpdir(), | |
| 40 | PORT: String(port), | |
| 41 | FORGE_APP_DATA: join(dir, "data"), | |
| 42 | FORGE_APP_URL: `http://127.0.0.1:${port}`, | |
| 43 | }, | |
| 44 | stdout: "ignore", | |
| 45 | stderr: "ignore", | |
| 46 | }); | |
| 47 | const base = `http://127.0.0.1:${port}`; | |
| 48 | const end = Date.now() + 15_000; | |
| 49 | for (;;) { | |
| 50 | try { | |
| 51 | if ((await fetch(`${base}/healthz`)).ok) break; | |
| 52 | } catch {} | |
| 53 | if (Date.now() > end) throw new Error("the scaffold did not boot"); | |
| 54 | await Bun.sleep(50); | |
| 55 | } | |
| 56 | findings = await webspec(base, ["/"], fetch, { site: true }); | |
| 57 | }, 30_000); | |
| 58 | ||
| 59 | afterAll(async () => { | |
| 60 | child?.kill(); | |
| 61 | await child?.exited; | |
| 62 | await rm(dir, { recursive: true, force: true }); | |
Names only: a new finding under etag/robots/etc. goes unnoticed. Map to |
||
| 63 | }); | |
| 64 | ||
| 65 | test("a fresh scaffold's webspec findings are exactly the known gaps", () => { | |
| 66 | const got = [...new Set(findings.map((f) => f.check))].sort(); | |
| 67 | expect(got).toEqual([...KNOWN].sort()); | |
| 68 | }); |
new above
offline
Random port can collide; retry the spawn on failure or have the server pick the port.