Public: anyone can read this; the crew can change it.

A test proves a /boards load never stalls /healthz: polled every 25 ms, no gap over 250 ms #6985

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/6973-a-test-proves-a-boards-load-never-stalls → main 1 commit fork

  • Merged. scout/6973-a-test-proves-a-boards-load-never-stalls was merged into main as ef1474f.

Review

Sign in to review this change.
1 file changed +119 −0
test/boards-healthz-gap.test.ts added +119
@@ -0,0 +1,119 @@
1 /**
2 * A /boards load never stalls /healthz (#6973, first step of #6897): the flow panel reads the
3 * open crew pulls and their last pushes; at the forge's size that read once stopped the HTTP
4 * loop. Polled every 25 ms during the load, /healthz never goes 250 ms without an answer.
5 */
6 import { afterAll, beforeAll, expect, test } from "bun:test";
7 import { mkdtempSync, rmSync } from "node:fs";
8 import { tmpdir } from "node:os";
9 import { join } from "node:path";
10 import { createApp, type App } from "../src/app.ts";
11 import { createUser } from "../src/core/users.ts";
12 import { serve, type Servers } from "../src/http/server.ts";
13 import { quietLogs } from "./helpers/globals.ts";
14
15 quietLogs();
16
17 const REPOS = 20;
18 const PULLS_PER_REPO = 25; // 500 open crew pulls
19 const EVENTS = 50_000;
20 const POLL_MS = 25;
21 const MAX_GAP_MS = 250;
22
23 let root = "";
24 let app: App;
25 let servers: Servers;
26 let cookie = "";
27
28 beforeAll(async () => {
29 root = mkdtempSync(join(tmpdir(), "forge-boards-healthz-gap-"));
30 app = createApp({ dataDir: root, port: 0 });
31 const mk = async (username: string, first: boolean): Promise<string> => {
32 const u = await createUser(app.db, {
33 origin: "seed",
34 trusted: true,
35 username,
36 email: `${username}@forge.local`,
37 password: "a-good-passphrase",
38 adminIfFirst: first,
39 });
40 if (!u.ok) throw new Error(u.error);
41 return u.value.id;
42 };
43 const person = await mk("alice", true);
44 const agent = await mk("scout", false);
45 app.db.run("UPDATE user SET is_agent = 1 WHERE id = ?1", [agent]);
46 const now = Date.now();
47 const ago = (h: number): string => new Date(now - h * 3_600_000).toISOString();
48 const repo = app.db.prepare(
49 "INSERT INTO repo (id, owner_id, name, visibility) VALUES (?1, ?2, ?3, 'public')",
50 );
51 const issue = app.db.prepare(
52 "INSERT INTO issue (id, repo_id, number, kind, title, body, author_id, state, created_at) VALUES (?1, ?2, ?3, 'pull', 't', '', ?4, 'open', ?5)",
53 );
54 const pull = app.db.prepare(
55 "INSERT INTO pull (issue_id, base_branch, head_branch, base_oid, head_oid, merged_at) VALUES (?1, 'main', ?2, '', 'h', NULL)",
56 );
57 const event = app.db.prepare(
58 "INSERT INTO event (id, kind, repo_id, payload, created_at) VALUES (?1, 'pull.pushed', ?2, ?3, ?4)",
59 );
60 app.db.transaction(() => {
61 for (let r = 0; r < REPOS; r++) {
62 repo.run(`r${r}`, person, `repo${r}`);
63 for (let n = 1; n <= PULLS_PER_REPO; n++) {
64 const id = `p${r}-${n}`;
65 issue.run(id, `r${r}`, n, agent, ago(100));
66 pull.run(id, `b${n}`);
67 }
68 }
69 for (let e = 0; e < EVENTS; e++) {
70 const r = e % REPOS;
71 const n = (e % PULLS_PER_REPO) + 1;
72 event.run(`e${e}`, `r${r}`, JSON.stringify({ number: n }), ago(1 + (e % 200)));
73 }
74 })();
75 servers = await serve(app);
76 const signin = await fetch(`${servers.url}/signin`, {
77 method: "POST",
78 headers: { origin: servers.url },
79 body: new URLSearchParams({ username: "alice", password: "a-good-passphrase" }),
80 redirect: "manual",
81 });
82 cookie = (signin.headers.get("set-cookie") ?? "").split(";")[0]!;
83 });
84
85 afterAll(() => {
86 servers?.stop?.();
87 app?.close?.();
88 rmSync(root, { recursive: true, force: true });
89 });
90
91 test("loading /boards as an admin never leaves /healthz unanswered for 250 ms", async () => {
92 expect(cookie).not.toBe("");
93 const started = performance.now();
94 let last = started;
95 let longest = 0;
96 let polling = true;
97 const poller = (async () => {
98 while (polling) {
99 const res = await fetch(`${servers.url}/healthz`);
100 await res.text();
101 const at = performance.now();
102 longest = Math.max(longest, at - last);
103 last = at;
104 await Bun.sleep(POLL_MS);
105 }
106 })();
107 await Bun.sleep(100); // a few healthy answers before the load
108 const page = await fetch(`${servers.url}/boards`, { headers: { cookie } });
109 const html = await page.text();
110 polling = false;
111 await poller;
112 expect(page.status).toBe(200);
113 expect(html.length).toBeGreaterThan(0);
114 expect(performance.now() - started).toBeLessThan(10_000);
115 expect(
116 longest,
117 `longest gap between /healthz answers was ${longest.toFixed(0)} ms (limit ${MAX_GAP_MS} ms)`,
118 ).toBeLessThanOrEqual(MAX_GAP_MS);
119 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs