benjivers/notes Public Public: anyone can read this; nobody can change it.

Files 2b2f00dc243e6583e6d8719a8b99fe2335f1e03a
75 lines · 2.2 KB · ~656 tok Raw History
1
/**
2
 * The API's description is the routes' own, committed: emitting it twice
3
 * says the same bytes, the file in git is what the table says now, and the
4
 * app serves exactly that file.
5
 */
6
import { afterAll, expect, test } from "bun:test";
7
import { mkdtempSync, readFileSync } from "node:fs";
8
import { tmpdir } from "node:os";
9
import { join } from "node:path";
10
import { emit, openapi } from "./openapi.ts";
11
import { routes } from "./routes.ts";
12
import { serve } from "./server.ts";
13
14
const committed = readFileSync(
15
  new URL("../contract/openapi.json", import.meta.url),
16
  "utf8",
17
);
18
19
test("emission is stable", () => {
20
  expect(emit()).toBe(emit());
21
});
22
23
test("contract/openapi.json is what the routes say now (else: bun run contract:emit)", () => {
24
  const now = JSON.parse(emit());
25
  const stale = !Bun.deepEquals(JSON.parse(committed), now);
26
  if (stale)
27
    console.error(
28
      "contract/openapi.json is stale: run `bun run contract:emit` and commit it",
29
    );
30
  expect(stale).toBe(false);
31
});
32
33
test("every API route is described, and every door names its cell of the table", () => {
34
  const doc = openapi() as {
35
    paths: Record<
36
      string,
37
      Record<
38
        string,
39
        { "x-contract"?: { resource: string; verb: string; least: string } }
40
      >
41
    >;
42
  };
43
  for (const route of routes.filter((r) => r.path.startsWith("/api/"))) {
44
    const op =
45
      doc.paths[route.path.replace(/:(\w+)/g, "{$1}")]?.[
46
        route.method.toLowerCase()
47
      ];
48
    expect([route.method, route.path, op !== undefined]).toEqual([
49
      route.method,
50
      route.path,
51
      true,
52
    ]);
53
    expect(typeof op?.["x-contract"]?.least).toBe("string");
54
  }
55
  expect(doc.paths["/api/audit"]?.get?.["x-contract"]).toEqual({
56
    resource: "audit",
57
    verb: "list",
58
    least: "admin",
59
  });
60
});
61
62
test("GET /openapi.json serves the committed file", async () => {
63
  const server = serve({
64
    port: 0,
65
    dataDir: mkdtempSync(join(tmpdir(), "app-openapi-")),
66
    identityKey: "k",
67
  });
68
  afterAll(() => server.stop(true));
69
  const response = await fetch(
70
    `http://${server.hostname}:${server.port}/openapi.json`,
71
  );
72
  expect(response.status).toBe(200);
73
  expect(response.headers.get("content-type")).toContain("application/json");
74
  expect(await response.text()).toBe(committed);
75
});
↑↓ navigate ↵ open esc close
↑↓ navigate ↵ open esc close

Keyboard shortcuts