Files
2026-05-27 23:29:53 +01:00

86 lines
2.8 KiB
TypeScript

/**
* Test-only helpers that stand up a real `node:http` server on a random
* port and route a `FetchLike` through it. Used by every provider's
* `*.test.ts` to exercise the full HTTP path without mocks.
*
* Lives under `tests/setup/` (not `src/`) so the universal dep-graph
* scan never sees it.
*/
import {
createServer,
type IncomingMessage,
type Server,
type ServerResponse,
} from "node:http";
import type { FetchLike } from "../../src/types/provider.js";
export interface RecordedRequest {
readonly method: string;
readonly path: string;
readonly headers: Record<string, string | string[] | undefined>;
readonly body: string;
}
export interface RecordingServer {
readonly origin: string;
readonly requests: RecordedRequest[];
readonly fetch: FetchLike;
close: () => Promise<void>;
}
export type Responder = (
req: IncomingMessage,
res: ServerResponse,
body: string
) => void | Promise<void>;
/**
* Stand up an HTTP server on a random port, capture every request, and
* return a `FetchLike` that proxies any URL (regardless of original
* scheme/host) to the local server preserving the path and method.
*/
export async function startRecordingServer(
respond: Responder
): Promise<RecordingServer> {
const requests: RecordedRequest[] = [];
const server: Server = createServer((req, res) => {
const chunks: Buffer[] = [];
req.on("data", (c) => chunks.push(c));
req.on("end", () => {
const body = Buffer.concat(chunks).toString("utf8");
requests.push({
method: req.method ?? "GET",
path: req.url ?? "/",
headers: { ...req.headers },
body,
});
respond(req, res, body);
});
});
await new Promise<void>((r) => server.listen(0, "127.0.0.1", r));
const addr = server.address();
if (addr === null || typeof addr === "string") {
throw new Error("server.address() returned an unexpected value");
}
const origin = `http://127.0.0.1:${addr.port}`;
const fetchImpl: FetchLike = async (url, init) => {
const u = new URL(url);
const rerouted = `${origin}${u.pathname}${u.search}`;
return globalThis.fetch(rerouted, init);
};
const close = async (): Promise<void> => {
await new Promise<void>((resolve, reject) => {
server.close((err) => (err ? reject(err) : resolve()));
});
};
return { origin, requests, fetch: fetchImpl, close };
}
/** Convenience: respond with a JSON body + status. */
export function jsonResponder(body: unknown, status = 200): Responder {
return (_req, res) => {
res.writeHead(status, { "content-type": "application/json" });
res.end(typeof body === "string" ? body : JSON.stringify(body));
};
}