Initial Commit
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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));
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user