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

93 lines
3.0 KiB
TypeScript

/**
* Shared test helpers for the HttpsTransport test suite. Centralises
* fake fetch construction so each test file stays focused on the
* behaviour under test.
*/
import type { MessageEnvelope, PeerId } from "@suaveplan/types";
import type { FetchFn } from "../src/types/transport.types.js";
export interface FakeFetchCall {
readonly url: string;
readonly method: string;
readonly headers: Readonly<Record<string, string>>;
readonly body: string | undefined;
}
export interface FakeFetchOptions {
readonly responder: (call: FakeFetchCall) => Response | Promise<Response>;
readonly history?: FakeFetchCall[];
}
export function makeFakeFetch(options: FakeFetchOptions): FetchFn {
const history = options.history ?? [];
return async (
input: string | URL | Request,
init?: RequestInit
): Promise<Response> => {
const url = typeof input === "string" ? input : input.toString();
const method = init?.method ?? "GET";
const headers = init?.headers
? Object.fromEntries(
Object.entries(init.headers as Record<string, string>).map(
([k, v]) => [k.toLowerCase(), v]
)
)
: {};
const body = typeof init?.body === "string" ? init.body : undefined;
const call: FakeFetchCall = { url, method, headers, body };
history.push(call);
if (init?.signal?.aborted) {
throw new DOMException("aborted", "AbortError");
}
return options.responder(call);
};
}
export function jsonResponse(value: unknown, status = 200): Response {
return new Response(JSON.stringify(value), {
status,
headers: { "content-type": "application/json" },
});
}
export function makeEnvelope(
overrides: Partial<MessageEnvelope> = {}
): MessageEnvelope {
return {
id: overrides.id ?? "msg-1",
type: overrides.type ?? "ping",
senderId: overrides.senderId ?? "peer-a",
targetId: overrides.targetId ?? "peer-b",
payload: overrides.payload ?? new Uint8Array([1, 2, 3]),
timestamp: overrides.timestamp ?? 1_000,
headers: overrides.headers ?? {},
} as MessageEnvelope;
}
export const TEST_PEER: PeerId = "peer-b";
/** Yield the microtask queue several times to let async loops progress. */
export async function flushMicrotasks(times = 4): Promise<void> {
for (let i = 0; i < times; i++) {
await Promise.resolve();
}
}
/**
* Wait until `predicate()` returns true or the deadline expires.
* Polls with a small real-time delay so backoff timers (which use
* real `setTimeout`) get a chance to fire between checks.
*/
export async function waitUntil(
predicate: () => boolean,
timeoutMs = 2_000
): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
if (predicate()) return;
await new Promise<void>((r) => setTimeout(r, 5));
}
throw new Error("waitUntil: condition never satisfied");
}