summaryrefslogtreecommitdiffstats
path: root/test/server.ts
blob: c242f523e01fdecab105c6aee9d90fe59376c69d (plain)
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/**
 * A throwaway HTTP server on a random loopback port.
 *
 * The suite points a real {@link Client} at a real socket: no test touches the
 * network, and no test stubs `fetch`. The code path exercised is the one that
 * runs in production, request construction and error body reading included.
 *
 * It doubles as the test that `baseUrl` works — if it did not, none of this
 * would run.
 *
 * @module
 */

/** What the fake server should answer for one request. */
export interface Reply {
  status: number;
  body: string;
  headers?: Record<string, string>;
}

/** Per-test programmable routes, plus a request counter. */
export class Router {
  private readonly routes = new Map<string, Reply | Reply[]>();
  /** Every path requested so far, in order. */
  readonly calls: string[] = [];
  /** Answers any path with no registered route. */
  fallback?: (path: string) => Reply;

  /**
   * Register the answer for a path.
   *
   * An array makes each request consume the next entry, which is how retrying
   * is tested. The last entry repeats once the array runs out.
   */
  respond(path: string, reply: Reply | Reply[]): void {
    this.routes.set(path, reply);
  }

  /** Record the call and return the answer registered for `path`. */
  next(path: string): Reply {
    this.calls.push(path);
    const reply = this.routes.get(path);
    if (reply === undefined) {
      if (this.fallback !== undefined) {
        return this.fallback(path);
      }
      return {
        status: 404,
        body: JSON.stringify({ erro: "cep_nao_encontrado" }),
      };
    }
    if (Array.isArray(reply)) {
      return (reply.length > 1 ? reply.shift() : reply[0]) as Reply;
    }
    return reply;
  }

  /** How many requests so far began with `prefix`. */
  count(prefix: string): number {
    return this.calls.filter((call) => call.startsWith(prefix)).length;
  }
}

/** A running fake API, with its router. */
export interface FakeServer {
  url: string;
  routes: Router;
  stop: () => void;
}

/** Start a fake API on `127.0.0.1` and a port the kernel picks. */
export function startServer(): FakeServer {
  const routes = new Router();
  const server = Bun.serve({
    hostname: "127.0.0.1",
    port: 0,
    fetch(request) {
      const url = new URL(request.url);
      const reply = routes.next(`${url.pathname}${url.search}`);
      return new Response(reply.body, {
        status: reply.status,
        headers: {
          "Content-Type": "application/json; charset=utf-8",
          ...reply.headers,
        },
      });
    },
  });

  return {
    url: `http://127.0.0.1:${server.port}`,
    routes,
    stop: () => {
      server.stop(true);
    },
  };
}