// Record an app's HTTP answers under real Node for tests/goldens/http (scripts/golden.mjs // drives it). The app runs as scripts/app-under-node.mjs starts it. Each request is // `{ method, path, headers?, body? | bodyBase64? }` (`bodyBase64` sends binary bytes, // such as a gzipped body); each result is // `{ request, status, headers: { }, body | bodyBase64 }` (absent // headers are null). A body whose content-type is missing or textual (text/*, JSON, // JavaScript) is recorded as `body`; any other is `bodyBase64`. tests/http-golden.ts is // the kernel half and records the same shape. import { readFileSync } from "node:fs"; import { request } from "node:http"; import { resolve } from "node:path"; import { startApp } from "./app-under-node.mjs"; const RECORDED_HEADERS = [ "content-type", "etag", "content-length", "x-powered-by", "last-modified", "cache-control", "accept-ranges", "content-range", ]; const TEXTUAL = /^(?:text\/|application\/(?:json|javascript))/; /** One exchange over node:http, so the app sees exactly the request's own headers: * fetch() would add `cache-control: no-cache` to a conditional request, which a * page's request through the Service Worker never carries. */ function send(port, method, path, headers = {}, body) { return new Promise((resolveExchange, reject) => { const sized = body === undefined ? headers : { ...headers, "content-length": Buffer.byteLength(body) }; const req = request({ host: "127.0.0.1", port, method, path, headers: sized }, (res) => { const chunks = []; res.on("data", (c) => chunks.push(c)); res.on("end", () => resolveExchange({ status: res.statusCode, headers: res.headers, body: Buffer.concat(chunks) })); }); req.on("error", reject); req.end(body); }); } /** `root` is the package root that `app` and `requests` are relative to. */ export async function recordHttp(root, { app, port, requests }, node) { const list = JSON.parse(readFileSync(resolve(root, requests), "utf8")); const running = await startApp(resolve(root, app), port, node); try { const exchanges = []; for (const { method, path, headers, body, bodyBase64 } of list) { const res = await send(port, method, path, headers, bodyBase64 === undefined ? body : Buffer.from(bodyBase64, "base64")); const type = res.headers["content-type"] ?? null; exchanges.push({ request: `${method} ${path}${headers ? ` ${JSON.stringify(headers)}` : ""}`, status: res.status, headers: Object.fromEntries(RECORDED_HEADERS.map((h) => [h, res.headers[h] ?? null])), ...(type === null || TEXTUAL.test(type) ? { body: res.body.toString("utf8") } : { bodyBase64: res.body.toString("base64") }), }); } return exchanges; } finally { await running.stop(); } }