ts / test / helpers.ts
461 words
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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;
  }
};

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/ts.git