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

Files otel
79 lines · 2.5 KB · ~732 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
import { sign } from "./who.ts";
14
15
const committed = readFileSync(
16
  new URL("../contract/openapi.json", import.meta.url),
17
  "utf8",
18
);
19
20
test("emission is stable", () => {
21
  expect(emit()).toBe(emit());
22
});
23
24
test("contract/openapi.json is what the routes say now (else: bun run contract:emit)", () => {
25
  const now = JSON.parse(emit());
26
  const stale = !Bun.deepEquals(JSON.parse(committed), now);
27
  if (stale)
28
    console.error(
29
      "contract/openapi.json is stale: run `bun run contract:emit` and commit it",
30
    );
31
  expect(stale).toBe(false);
32
});
33
34
test("every API route is described, and every door names its cell of the table", () => {
35
  const doc = openapi() as {
36
    paths: Record<
37
      string,
38
      Record<
39
        string,
40
        { "x-contract"?: { resource: string; verb: string; least: string } }
41
      >
42
    >;
43
  };
44
  for (const route of routes.filter((r) => r.path.startsWith("/api/"))) {
45
    const op =
46
      doc.paths[route.path.replace(/:(\w+)/g, "{$1}")]?.[
47
        route.method.toLowerCase()
48
      ];
49
    expect([route.method, route.path, op !== undefined]).toEqual([
50
      route.method,
51
      route.path,
52
      true,
53
    ]);
54
    expect(typeof op?.["x-contract"]?.least).toBe("string");
55
  }
56
  expect(doc.paths["/api/audit"]?.get?.["x-contract"]).toEqual({
57
    resource: "audit",
58
    verb: "list",
59
    least: "admin",
60
  });
61
});
62
63
test("GET /openapi.json serves the committed file", async () => {
64
  const server = serve({
65
    port: 0,
66
    dataDir: mkdtempSync(join(tmpdir(), "app-openapi-")),
67
    identityKey: "k",
68
  });
69
  afterAll(() => server.stop(true));
70
  // The table opens strangers no door, so the description is for the signed in.
71
  const identity = `u1|alice|user|${Date.now() + 60_000}`;
72
  const response = await fetch(
73
    `http://${server.hostname}:${server.port}/openapi.json`,
74
    { headers: { "x-forge-identity": identity, "x-forge-signature": sign(identity, "k") } },
75
  );
76
  expect(response.status).toBe(200);
77
  expect(response.headers.get("content-type")).toContain("application/json");
78
  expect(await response.text()).toBe(committed);
79
});
↑↓ navigate ↵ open esc close
↑↓ navigate ↵ open esc close

Keyboard shortcuts