wren/glyph Public Public: anyone can read this; nobody can change it.

forked from apps/glyph

workbenchthis machine
Files cd3c7d74b323cac4ab0520540ce33bcf71a582db
81 lines · 2.6 KB · ~764 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
    // Generated app's contract CLI: stderr is its output.
29
    console.error(
30
      "contract/openapi.json is stale: run `bun run contract:emit` and commit it",
31
    );
32
  expect(stale).toBe(false);
33
});
34
35
test("every API route is described, and every door names its cell of the table", () => {
36
  const doc = openapi() as {
37
    paths: Record<
38
      string,
39
      Record<
40
        string,
41
        { "x-contract"?: { resource: string; verb: string; least: string } }
42
      >
43
    >;
44
  };
45
  for (const route of routes.filter((r) => r.path.startsWith("/api/"))) {
46
    const op =
47
      doc.paths[route.path.replace(/:(\w+)/g, "{$1}")]?.[
48
        route.method.toLowerCase()
49
      ];
50
    expect([route.method, route.path, op !== undefined]).toEqual([
51
      route.method,
52
      route.path,
53
      true,
54
    ]);
55
    expect(typeof op?.["x-contract"]?.least).toBe("string");
56
  }
57
  expect(doc.paths["/api/audit"]?.get?.["x-contract"]).toEqual({
58
    resource: "audit",
59
    verb: "list",
60
    least: "admin",
61
  });
62
});
63
64
test("GET /openapi.json serves the committed file", async () => {
65
  const server = serve({
66
    port: 0,
67
    dataDir: mkdtempSync(join(tmpdir(), "app-openapi-")),
68
    identityKey: "k",
69
  });
70
  afterAll(() => server.stop(true));
71
  // Drawings open no door to a stranger, so neither does their description.
72
  const url = `http://${server.hostname}:${server.port}/openapi.json`;
73
  expect((await fetch(url)).status).toBe(401);
74
  const identity = `u1|alice|user|${Date.now() + 60_000}`;
75
  const response = await fetch(url, {
76
    headers: { "x-forge-identity": identity, "x-forge-signature": sign(identity, "k") },
77
  });
78
  expect(response.status).toBe(200);
79
  expect(response.headers.get("content-type")).toContain("application/json");
80
  expect(await response.text()).toBe(committed);
81
});
↑↓ navigate ↵ open esc close
↑↓ navigate ↵ open esc close

Keyboard shortcuts