learning-website-nextjs1-12 Exercise 1: Settings, a Health Address, and a Smoke Test over Real HTTP ============================================================================================== Before a release takes the real address, look at it the way a visitor would. Three things are needed: the settings checked (so a missing variable stops the release, not the first visitor), an address a monitor can ask ("can you serve a page?"), and a script that visits a RUNNING site. Save as packages/operations/src/checks.ts: import { existsSync, statSync } from "node:fs"; import { isAbsolute, relative, resolve } from "node:path"; export interface Problem { readonly level: "error" | "warning"; readonly message: string } export type Env = Readonly>; /** * The settings a production site must have, checked before a release is switched on (the equivalent of `manage.py check --deploy`). * `appDir` is the folder of the release itself: data kept inside it would be thrown away with the next release. */ export function checkProduction(env: Env, appDir: string): Problem[] { const out: Problem[] = []; const error = (message: string) => out.push({ level: "error", message }); const warning = (message: string) => out.push({ level: "warning", message }); if (env["LW_ENV"] !== "prod") error("LW_ENV must be prod, or every link in the site points at localhost"); const content = env["LW_CONTENT_ROOT"]; if (!content) error("LW_CONTENT_ROOT is not set"); else if (!existsSync(content) || !statSync(content).isDirectory()) error(`LW_CONTENT_ROOT is not a folder: ${content}`); const data = env["LW_DATA_DIR"]; if (!data) error("LW_DATA_DIR is not set: the accounts database would be created inside the release and lost at the next one"); else if (!isAbsolute(data)) error("LW_DATA_DIR must be an absolute path"); else { const inside = relative(resolve(appDir), resolve(data)); if (inside === "" || (!inside.startsWith("..") && !isAbsolute(inside))) error("LW_DATA_DIR is inside the release folder: the next release would not see it"); } if (env["LW_LAB"] === "1") error("LW_LAB=1 switches the experiment pages on; they must not be public"); if (env["LW_ALLOW_CRAWLING"] === "1") warning("LW_ALLOW_CRAWLING=1: search engines are allowed in. Intended only once the site is ready."); const redirects = env["LW_REDIRECTS_DIR"]; if (redirects && !existsSync(redirects)) error(`LW_REDIRECTS_DIR does not exist: ${redirects}`); return out; } Save as packages/operations/src/health.ts: export interface Health { readonly status: number; readonly body: { status: string; pages?: number }; } /** * What the monitor, the load balancer and the release script ask: "can this process really serve a page?" * 200 only when the database answers AND the site has pages. It says so in one line and leaks nothing else. */ export function healthReport(check: { readonly databaseOk: boolean; readonly pages: number }): Health { if (!check.databaseOk) return { status: 503, body: { status: "database error" } }; if (check.pages === 0) return { status: 503, body: { status: "no pages", pages: 0 } }; return { status: 200, body: { status: "ok", pages: check.pages } }; } Save as apps/languages/app/healthz/route.ts: import { healthReport } from "@lw/operations"; import { getDb } from "../../lib/accounts"; import { content } from "../../lib/site"; // asked on every call: a health answer that was made at build time would say "ok" about a process that is down export const dynamic = "force-dynamic"; export function GET() { let databaseOk = true; try { getDb().prepare("SELECT 1").get(); } catch { databaseOk = false; } const { status, body } = healthReport({ databaseOk, pages: content.all().length }); return Response.json(body, { status, headers: { "Cache-Control": "no-store" } }); } Save as packages/operations/src/smoke.ts: export interface SmokeResponse { readonly status: number; readonly headers: { get(name: string): string | null }; text(): Promise; } /** One request, the way a browser sends it: method, path, extra headers, a body. The caller decides how the host is chosen. */ export type Ask = (path: string, init?: { method?: string; headers?: Record; body?: string }) => Promise; export interface SmokeCheck { readonly name: string; readonly ok: boolean; readonly detail: string } async function attempt(name: string, run: () => Promise<{ ok: boolean; detail: string }>): Promise { try { return { name, ...(await run()) }; } catch (error) { return { name, ok: false, detail: `request failed: ${(error as Error).message}` }; } } /** * Look at a RUNNING site as a visitor would. Run it against the new release before it takes the live address, and again after. * `pages` are real page paths chosen from the content, `file` is a real PDF or solution path (or undefined if the site has none), and * `origin` is the address the site believes it is at ("https://languages.osztromok.com"). */ export async function smokeTest(ask: Ask, options: { pages: readonly string[]; file?: string; origin: string }): Promise { const checks: SmokeCheck[] = []; checks.push(await attempt("/healthz says ok", async () => { const r = await ask("/healthz"); const text = await r.text(); return { ok: r.status === 200 && text.includes('"ok"') && r.headers.get("cache-control") === "no-store", detail: `${r.status} ${text.slice(0, 60)}` }; })); for (const page of options.pages) { checks.push(await attempt(`page ${page}`, async () => { const r = await ask(page); const type = r.headers.get("content-type") ?? ""; const body = await r.text(); return { ok: r.status === 200 && type.startsWith("text/html") && body.includes(" { const r = await ask("/this/page/does/not/exist"); await r.text(); return { ok: r.status === 404, detail: String(r.status) }; })); if (options.file) { const file = options.file; checks.push(await attempt(`file ${file} is served and cannot be sniffed`, async () => { const r = await ask(file); await r.text(); return { ok: r.status === 200 && r.headers.get("x-content-type-options") === "nosniff", detail: `${r.status} nosniff=${r.headers.get("x-content-type-options")}` }; })); } for (const path of ["/robots.txt", "/sitemap.xml", "/search-index.json"]) { checks.push(await attempt(path, async () => { const r = await ask(path); const body = await r.text(); return { ok: r.status === 200 && body.length > 20, detail: `${r.status}, ${body.length} characters` }; })); } const post = (origin: string | undefined) => ask("/api/login", { method: "POST", headers: { "content-type": "application/json", ...(origin ? { origin } : {}) }, body: "{}", }); checks.push(await attempt("a login from another site's page is refused (403)", async () => { const r = await post("https://evil.example"); await r.text(); return { ok: r.status === 403, detail: String(r.status) }; })); checks.push(await attempt("a login from our own page gets past that lock (400, nothing counted)", async () => { const r = await post(options.origin); await r.text(); return { ok: r.status === 400, detail: String(r.status) }; })); return checks; } Save as ops.mjs: // Operations commands, run from the release folder with the production settings in the environment (the service's /etc/lw/environment). // node ops.mjs check the production settings (exit 1 on any error) // node ops.mjs backup [folder] [--keep N] a consistent copy of the accounts database, verified, then old ones pruned // node ops.mjs restore put a backup back (STOP the services first) // node ops.mjs config [sites...] write the Apache virtual hosts and systemd units for the sites that have an app // node ops.mjs smoke [content folder] look at a running site, with the Host header a visitor would send import { mkdirSync, writeFileSync } from "node:fs"; import http from "node:http"; import { join } from "node:path"; import { assetPaths, loadPages } from "./packages/content/src/index.ts"; import { backupDatabase, checkProduction, renderUnit, renderVhosts, restoreDatabase, smokeTest } from "./packages/operations/src/index.ts"; import { siteForHost, siteForPath } from "./packages/sites/src/index.ts"; const [command, ...args] = process.argv.slice(2); const dataFile = () => { const folder = process.env.LW_DATA_DIR; if (!folder) throw new Error("LW_DATA_DIR is not set"); return join(folder, "accounts.sqlite"); }; if (command === "check") { const problems = checkProduction(process.env, process.cwd()); for (const p of problems) console.log(`${p.level.toUpperCase()}: ${p.message}`); const errors = problems.filter((p) => p.level === "error").length; console.log(errors === 0 ? `production settings: ok (${problems.length} warning${problems.length === 1 ? "" : "s"})` : `${errors} error${errors === 1 ? "" : "s"}: do not release`); process.exit(errors === 0 ? 0 : 1); } else if (command === "backup") { const keepAt = args.indexOf("--keep"); const keep = keepAt >= 0 ? Number(args[keepAt + 1]) : 14; const folder = args.filter((a, i) => a !== "--keep" && i !== keepAt + 1)[0] ?? join(process.env.LW_DATA_DIR ?? ".", "..", "backups"); const result = backupDatabase(dataFile(), folder, { keep }); console.log(`backup written: ${result.file} (${result.users} users); ${result.pruned.length} old backup(s) removed`); } else if (command === "restore") { const result = restoreDatabase(args[0], dataFile()); console.log(`restored ${args[0]} (${result.users} users)${result.replaced ? "; the database it replaced is kept as accounts.sqlite.before-restore" : ""}`); } else if (command === "config") { const [folder, ...sites] = args; const names = sites.length ? sites : ["portfolio", "languages"]; mkdirSync(folder, { recursive: true }); writeFileSync(join(folder, "lw-sites.conf"), renderVhosts({ sites: names })); for (const site of names) writeFileSync(join(folder, `lw-${site}.service`), renderUnit(site)); console.log(`wrote lw-sites.conf and ${names.length} unit(s) to ${folder}: ${names.join(", ")}`); } else if (command === "smoke") { const [base, host, contentRoot = process.env.LW_CONTENT_ROOT] = args; const site = siteForHost(host, "prod"); // real pages of THIS site, picked from the content: the first, the middle and the last const { pages } = loadPages(contentRoot); const mine = pages.filter((p) => p.site === site).map((p) => encodeURI("/" + p.path.slice(0, -".html".length))); const picked = mine.length ? [mine[0], mine[Math.floor(mine.length / 2)], mine[mine.length - 1]] : []; // a real PDF or solution file of this site, if it has one const file = assetPaths(contentRoot).find((p) => { try { return siteForPath(p) === site; } catch { return false; } }); // node:http, not fetch: fetch replaces a Host header with the address it connects to, and the app must see the visitor's host const url = new URL(base); const ask = (path, init = {}) => new Promise((resolve, reject) => { const request = http.request({ host: url.hostname, port: url.port, path, method: init.method ?? "GET", headers: { host, ...(init.headers ?? {}) } }, (response) => { const chunks = []; response.on("data", (c) => chunks.push(c)); response.on("end", () => resolve({ status: response.statusCode, headers: { get: (n) => { const v = response.headers[n.toLowerCase()]; return Array.isArray(v) ? v.join(", ") : v ?? null; } }, text: async () => Buffer.concat(chunks).toString("utf8"), })); }); request.on("error", reject); request.end(init.body); }); const checks = await smokeTest(ask, { pages: picked, file: file ? encodeURI("/" + file) : undefined, origin: `https://${host}` }); for (const c of checks) console.log(`${c.ok ? "ok " : "FAIL"} ${c.name} (${c.detail})`); const failed = checks.filter((c) => !c.ok).length; console.log(failed === 0 ? `${checks.length} checks passed` : `${failed} of ${checks.length} checks FAILED`); process.exit(failed === 0 ? 0 : 1); } else { console.error("usage: node ops.mjs check | backup | restore | config | smoke"); process.exit(2); } THE SETTINGS, with a good environment and a bad one (development, no data folder, the experiment pages on): node ops.mjs check production settings: ok (0 warnings) LW_ENV=dev LW_LAB=1 LW_DATA_DIR= node ops.mjs check ERROR: LW_ENV must be prod, or every link in the site points at localhost ERROR: LW_DATA_DIR is not set: the accounts database would be created inside the release and lost at the next one ERROR: LW_LAB=1 switches the experiment pages on; they must not be public 3 errors: do not release THE SMOKE TEST. The languages app was built with LW_ENV=prod and started with "next start" (the way the server runs it); then, asking with the Host header Apache will send: node ops.mjs smoke http://127.0.0.1:3001 languages.osztromok.com ok /healthz says ok (200 {"status":"ok","pages":561}) ok page /culture/japan/japanese-film-and-television/japanese_film_and_television_1_1 (200 text/html; charset=utf-8) ok page /japan/japanese-language/reference-materials/hiragana/hiragana_%E3%81%97 (200 text/html; charset=utf-8) ok page /japan/katakana-2/katakana_2_9 (200 text/html; charset=utf-8) ok an address that is not a page answers 404 (404) ok file /culture/japan/japanese-film-and-television/pdfs/Japanese_Film_and_Television_Course.pdf is served and cannot be sniffed (200 nosniff=nosniff) ok /robots.txt (200, 27 characters) ok /sitemap.xml (200, 74907 characters) ok /search-index.json (200, 959436 characters) ok a login from another site's page is refused (403) (403) ok a login from our own page gets past that lock (400, nothing counted) (400) 11 checks passed (The three pages are the first, the middle and the last of the site's 561 pages, so a different run looks at different pages only if the content changed; the file is a real PDF.) With the app stopped, all 11 checks fail with "connect ECONNREFUSED" instead of the script hanging or crashing. THREE MISTAKES THE REAL RUN FOUND IN MY OWN SCRIPT (none of them was in the unit tests, which use a pretend site): 1. I asked for pages with a slash at the end. The site answers /x/ with a 308 to /x, so three page checks failed with "308". The earlier chapters said so; I wrote the script without looking back. A check for a page must ask for the address the site really uses. 2. The login check "from our own page" got 403 instead of 400. Node's fetch() does not send the Host header you give it (it sends the address it connects to, 127.0.0.1:3001), so the site saw an Origin that did not match its Host. curl, with the same header, got 400. The script now uses node:http, which sends exactly what it is told. The first check (a login from another site, expected 403) had passed all along, for the wrong reason: it would have been refused whatever the Host. 3. One of the sampled pages has a character outside ASCII in its address (hiragana_し). node:http refuses it unescaped, so the address is percent-encoded first (encodeURI), the way a browser does. Save as packages/operations/src/operations.test.ts: import assert from "node:assert/strict"; import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { test } from "node:test"; import { DatabaseSync } from "node:sqlite"; import { backupDatabase, backupName, checkProduction, healthReport, listBackups, renderUnit, renderVhosts, restoreDatabase, smokeTest, verifyDatabase, type Ask, } from "./index.ts"; function scratch(): string { return mkdtempSync(join(tmpdir(), "lw-ops-")); } /** A small accounts-like database, in WAL mode like the real one. The connection is returned OPEN, so its recent writes are still in the -wal file. */ function makeDb(file: string, names: string[]): DatabaseSync { const db = new DatabaseSync(file); db.exec("PRAGMA journal_mode = WAL; CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, username TEXT NOT NULL)"); const insert = db.prepare("INSERT INTO users (username) VALUES (?)"); for (const name of names) insert.run(name); return db; } test("health: 200 only with a database and pages", () => { assert.deepEqual(healthReport({ databaseOk: true, pages: 3 }), { status: 200, body: { status: "ok", pages: 3 } }); assert.equal(healthReport({ databaseOk: false, pages: 3 }).status, 503); assert.equal(healthReport({ databaseOk: true, pages: 0 }).status, 503); assert.deepEqual(healthReport({ databaseOk: false, pages: 0 }).body, { status: "database error" }); }); test("backup names are in time order and in UTC", () => { assert.equal(backupName(new Date("2026-10-08T03:15:09Z")), "accounts-20261008-031509.sqlite"); assert.ok(backupName(new Date("2026-10-08T03:15:09Z")) < backupName(new Date("2026-10-08T03:15:10Z"))); assert.ok(backupName(new Date("2026-09-30T23:59:59Z")) < backupName(new Date("2026-10-01T00:00:00Z"))); }); test("a backup of a database that is open and has writes still in the -wal file holds every row", () => { const dir = scratch(); try { const live = makeDb(join(dir, "live.sqlite"), ["ann", "bob", "cy"]); const result = backupDatabase(join(dir, "live.sqlite"), join(dir, "backups"), { now: new Date("2026-10-08T03:15:00Z") }); assert.equal(result.users, 3); assert.equal(verifyDatabase(result.file).users, 3); live.close(); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("only the newest `keep` backups are kept, and files that are not backups are left alone", () => { const dir = scratch(); try { makeDb(join(dir, "live.sqlite"), ["ann"]).close(); const folder = join(dir, "backups"); mkdirSync(folder); writeFileSync(join(folder, "notes.txt"), "mine"); let last = { pruned: [] as string[] }; for (let second = 0; second < 5; second++) { last = backupDatabase(join(dir, "live.sqlite"), folder, { keep: 3, now: new Date(Date.UTC(2026, 9, 8, 3, 15, second)) }); } assert.deepEqual(listBackups(folder), ["accounts-20261008-031502.sqlite", "accounts-20261008-031503.sqlite", "accounts-20261008-031504.sqlite"]); assert.deepEqual(last.pruned, ["accounts-20261008-031501.sqlite"]); assert.ok(existsSync(join(folder, "notes.txt"))); assert.ok(!readdirSync(folder).some((n) => n.endsWith(".partial"))); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("a backup is never overwritten, and a nonsense keep is refused", () => { const dir = scratch(); try { makeDb(join(dir, "live.sqlite"), ["ann"]).close(); const when = new Date("2026-10-08T03:15:00Z"); backupDatabase(join(dir, "live.sqlite"), join(dir, "b"), { now: when }); assert.throws(() => backupDatabase(join(dir, "live.sqlite"), join(dir, "b"), { now: when }), /already exists/); assert.throws(() => backupDatabase(join(dir, "live.sqlite"), join(dir, "b"), { keep: 0 }), /at least 1/); assert.equal(listBackups(join(dir, "b")).length, 1); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("a backup of something that is not a database fails and removes nothing", () => { const dir = scratch(); try { makeDb(join(dir, "live.sqlite"), ["ann"]).close(); backupDatabase(join(dir, "live.sqlite"), join(dir, "b"), { now: new Date("2026-10-08T03:15:00Z") }); writeFileSync(join(dir, "broken.sqlite"), "this is not a database"); assert.throws(() => backupDatabase(join(dir, "broken.sqlite"), join(dir, "b"), { keep: 1, now: new Date("2026-10-08T03:16:00Z") })); assert.equal(listBackups(join(dir, "b")).length, 1); assert.ok(!readdirSync(join(dir, "b")).some((n) => n.endsWith(".partial"))); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("restore puts the backup back, keeps what it replaced, and moves the old -wal away", () => { const dir = scratch(); try { const live = makeDb(join(dir, "accounts.sqlite"), ["ann", "bob"]); const made = backupDatabase(join(dir, "accounts.sqlite"), join(dir, "b"), { now: new Date("2026-10-08T03:15:00Z") }); live.exec("INSERT INTO users (username) VALUES ('later')"); assert.equal((live.prepare("SELECT COUNT(*) AS n FROM users").get() as { n: number }).n, 3); live.close(); const result = restoreDatabase(made.file, join(dir, "accounts.sqlite")); assert.equal(result.users, 2); assert.equal(verifyDatabase(join(dir, "accounts.sqlite")).users, 2); assert.equal(verifyDatabase(join(dir, "accounts.sqlite.before-restore")).users, 3); assert.ok(!existsSync(join(dir, "accounts.sqlite-wal"))); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("restore refuses a bad backup before touching the database", () => { const dir = scratch(); try { makeDb(join(dir, "accounts.sqlite"), ["ann"]).close(); writeFileSync(join(dir, "bad.sqlite"), "nope"); assert.throws(() => restoreDatabase(join(dir, "bad.sqlite"), join(dir, "accounts.sqlite")), /refusing to restore/); assert.equal(verifyDatabase(join(dir, "accounts.sqlite")).users, 1); assert.ok(!existsSync(join(dir, "accounts.sqlite.before-restore"))); // a database with the wrong tables is not an accounts backup either const other = new DatabaseSync(join(dir, "other.sqlite")); other.exec("CREATE TABLE t (a)"); other.close(); assert.throws(() => restoreDatabase(join(dir, "other.sqlite"), join(dir, "accounts.sqlite")), /refusing to restore/); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("production check: a good setup passes, and each mistake is its own error", () => { const dir = scratch(); try { const release = join(dir, "release"); const good = { LW_ENV: "prod", LW_CONTENT_ROOT: dir, LW_DATA_DIR: join(dir, "data") }; assert.deepEqual(checkProduction(good, release), []); const messages = (env: Record) => checkProduction(env, release).map((p) => `${p.level}: ${p.message}`); assert.match(messages({ ...good, LW_ENV: undefined })[0] ?? "", /^error: LW_ENV must be prod/); assert.match(messages({ ...good, LW_ENV: "dev" })[0] ?? "", /^error: LW_ENV must be prod/); assert.match(messages({ ...good, LW_CONTENT_ROOT: undefined })[0] ?? "", /LW_CONTENT_ROOT is not set/); assert.match(messages({ ...good, LW_CONTENT_ROOT: join(dir, "nope") })[0] ?? "", /not a folder/); assert.match(messages({ ...good, LW_DATA_DIR: undefined })[0] ?? "", /LW_DATA_DIR is not set/); assert.match(messages({ ...good, LW_DATA_DIR: "data" })[0] ?? "", /absolute/); assert.match(messages({ ...good, LW_DATA_DIR: join(release, "data") })[0] ?? "", /inside the release/); assert.match(messages({ ...good, LW_DATA_DIR: release })[0] ?? "", /inside the release/); assert.deepEqual(messages({ ...good, LW_DATA_DIR: release + "-data" }), []); // a sibling whose name starts the same is NOT inside assert.match(messages({ ...good, LW_LAB: "1" })[0] ?? "", /^error: LW_LAB=1/); assert.match(messages({ ...good, LW_ALLOW_CRAWLING: "1" })[0] ?? "", /^warning: /); assert.match(messages({ ...good, LW_REDIRECTS_DIR: join(dir, "nope") })[0] ?? "", /LW_REDIRECTS_DIR does not exist/); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("virtual hosts: one per site, on that site's own port, and every one passes the visitor's host on", () => { const text = renderVhosts({ sites: ["portfolio", "languages", "webdevelopment"] }); assert.equal([...text.matchAll(//g)].length, 3); assert.equal([...text.matchAll(/ProxyPreserveHost On/g)].length, 3); assert.ok(text.includes("ServerName osztromok.com\n")); assert.ok(text.includes("ServerName languages.osztromok.com\n")); assert.ok(text.includes("ProxyPass / http://127.0.0.1:3001/")); assert.ok(text.includes("ProxyPass / http://127.0.0.1:3002/")); assert.ok(text.includes("ProxyPass / http://127.0.0.1:3000/")); assert.ok(text.includes("RedirectMatch 301 ^/(.*)$ https://languages.osztromok.com/$1")); assert.ok(!text.includes("systems.osztromok.com"), "a site with no app must keep answering from the old site"); assert.equal([...text.matchAll(/AddOutputFilterByType DEFLATE application\/json application\/xml text\/plain/g)].length, 3); assert.ok(text.includes("SSLCertificateFile /etc/letsencrypt/live/osztromok.com/fullchain.pem")); }); test("systemd unit: bound to the loopback address only, restarts, may write only the data folder", () => { const unit = renderUnit("languages"); assert.ok(unit.includes("ExecStart=/usr/bin/npx next start -p 3001 -H 127.0.0.1")); assert.ok(unit.includes("WorkingDirectory=/srv/lw/current/apps/languages")); assert.ok(unit.includes("Restart=on-failure")); assert.ok(unit.includes("ProtectSystem=strict")); assert.ok(unit.includes("ReadWritePaths=/srv/lw/data")); assert.ok(unit.includes("EnvironmentFile=/etc/lw/environment")); }); /** A fake site: the answers a correct languages site gives. `broken` changes one of them. */ function fakeSite(broken?: string): Ask { const headers = (h: Record) => ({ get: (n: string) => h[n.toLowerCase()] ?? null }); return async (path, init) => { const reply = (status: number, text: string, h: Record = {}) => ({ status, headers: headers(h), text: async () => text }); if (path === "/healthz") return reply(broken === "health" ? 503 : 200, '{"status":"ok","pages":10}', { "cache-control": "no-store" }); if (path === "/japan/a/") return reply(200, "

x

", { "content-type": "text/html; charset=utf-8" }); if (path === "/this/page/does/not/exist") return reply(broken === "soft404" ? 200 : 404, "gone"); if (path === "/japan/a/pdfs/a.pdf") return reply(200, "%PDF", broken === "sniff" ? {} : { "x-content-type-options": "nosniff" }); if (path === "/robots.txt" || path === "/sitemap.xml" || path === "/search-index.json") return reply(200, "x".repeat(50)); if (path === "/api/login") { const origin = init?.headers?.["origin"]; if (origin === "https://evil.example") return reply(broken === "csrf" ? 400 : 403, "{}"); return reply(400, "{}"); } return reply(404, ""); }; } const OPTIONS = { pages: ["/japan/a/"], file: "/japan/a/pdfs/a.pdf", origin: "https://languages.osztromok.com" }; test("smoke test: a correct site passes every check", async () => { const checks = await smokeTest(fakeSite(), OPTIONS); assert.equal(checks.length, 9); assert.deepEqual(checks.filter((c) => !c.ok), []); }); test("smoke test: each kind of fault is caught by its own check, and only that one", async () => { for (const [fault, name] of [ ["health", "/healthz says ok"], ["soft404", "an address that is not a page answers 404"], ["sniff", "file /japan/a/pdfs/a.pdf is served and cannot be sniffed"], ["csrf", "a login from another site's page is refused (403)"], ] as const) { const failed = (await smokeTest(fakeSite(fault), OPTIONS)).filter((c) => !c.ok).map((c) => c.name); assert.deepEqual(failed, [name], fault); } }); test("smoke test: a site that is down fails every check instead of throwing", async () => { const down: Ask = async () => { throw new Error("connect ECONNREFUSED"); }; const checks = await smokeTest(down, OPTIONS); assert.equal(checks.length, 9); assert.ok(checks.every((c) => !c.ok && c.detail.includes("ECONNREFUSED"))); }); npm test ℹ tests 114 ℹ pass 114 ℹ fail 0 (100 earlier and 14 new) PLANTED MISTAKES: in the unit tests the pretend site is made to break in four different ways (the health address says 503, a missing page answers 200, a file loses its nosniff header, a login from another site is accepted) and each is caught by exactly its own check, and only that one. A site that is down fails all 11 without throwing. In the production check, "the data folder inside the release" is tested with a sibling folder whose name merely starts the same ("release-data"), which must NOT count as inside. WHY THIS WORKS AS AN ANSWER --------------------------- The release is judged on what a visitor gets, over HTTP, with real pages, a real file and the real refusal of a cross-site login, not on the unit tests passing. And the run found three mistakes the unit tests could not.