import { readFile } from 'node:fs/promises';
import { createClient, type Client, type ClientOptions } from '../src/client.js';
/** One recorded request, as the fake transport saw it. */
export type Call = Readonly<{
method: string;
url: string;
headers: Readonly<Record<string, string>>;
body: BodyInit | null | undefined;
}>;
export type Reply = Readonly<{ status: number; body?: string | object }>;
export type Responder = (call: Call, index: number) => Reply | Promise<Reply>;
const NO_BODY_STATUSES = new Set([204, 205, 304]);
const toResponse = ({ status, body }: Reply): Response => {
const text = body === undefined ? '' : typeof body === 'string' ? body : JSON.stringify(body);
return new Response(NO_BODY_STATUSES.has(status) || text === '' ? null : text, { status });
};
/**
* A fetch stand-in that records every request and answers from RESPOND: a
* function of the call, or a script of replies consumed in order (the last
* one repeats). Default: 200 `{}` for everything.
*/
export const fakeFetch = (respond: Responder | readonly Reply[] = [{ status: 200, body: '{}' }]) => {
const calls: Call[] = [];
const responder: Responder =
typeof respond === 'function' ? respond : (_, i) => respond[Math.min(i, respond.length - 1)]!;
const fetchFn: typeof fetch = async (input, init) => {
const call: Call = {
method: init?.method ?? 'GET',
url: typeof input === 'string' ? input : input instanceof URL ? input.href : input.url,
headers: { ...((init?.headers as Record<string, string> | undefined) ?? {}) },
body: init?.body,
};
calls.push(call);
return toResponse(await responder(call, calls.length - 1));
};
return { fetch: fetchFn, calls };
};
/** A client whose transport records into `calls` and answers via RESPOND. */
export const captureClient = (
options: ClientOptions = { apiKey: 'ird_k' },
respond?: Responder | readonly Reply[],
): { client: Client; calls: Call[] } => {
const { fetch, calls } = fakeFetch(respond);
return { client: createClient({ ...options, fetch, sleep: noSleep }), calls };
};
export const noSleep = async (): Promise<void> => {};
/** Parse a recorded JSON body. */
export const bodyJson = (call: Call): unknown =>
typeof call.body === 'string' ? JSON.parse(call.body) : undefined;
/** Run FN and return the error it throws (fails when it does not throw). */
export const caught = async (fn: () => Promise<unknown>): Promise<unknown> => {
try {
await fn();
} catch (e) {
return e;
}
throw new Error('expected the call to throw');
};
/** The live API key from IRD_API_KEY, or from a local .env file; undefined when absent. */
export const readApiKey = async (): Promise<string | undefined> => {
const fromEnv = process.env.IRD_API_KEY?.trim();
if (fromEnv) return fromEnv;
try {
const env = await readFile('.env', 'utf8');
const line = env.split('\n').find((l) => l.startsWith('IRD_API_KEY='));
return line?.slice('IRD_API_KEY='.length).trim().replace(/^["']|["']$/g, '') || undefined;
} catch {
return undefined;
}
};