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

Settings › Capacity answers a warm request under 50 ms and works with JavaScript off, and a test holds both #9730

Merged opened this

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

Merged

scout/forge:scout/9725-settings-capacity-answers-a-warm-request → main 2 commits fork

  • Merged. scout/9725-settings-capacity-answers-a-warm-request was merged into main as 287b84a.

Review

Sign in to review this change.
2 files changed +133 −0
test/capacity-nojs.test.ts added +101
@@ -0,0 +1,101 @@
1 /**
2 * Settings › Capacity works with JavaScript off (#9725): no script carries
3 * its function, and every change on it is a plain POST form that the server
4 * holds to a same-origin check.
5 */
6 import { afterAll, beforeAll, expect, test } from "bun:test";
7 import { mkdtemp, rm } from "node:fs/promises";
8 import { tmpdir } from "node:os";
9 import { join } from "node:path";
10 import { createApp, type App } from "../src/app.ts";
11 import { serve, type Servers } from "../src/http/server.ts";
12 import { seedAdmin } from "./helpers/admin.ts";
13
14 let dataDir = "";
15 let app: App, servers: Servers, bearer = "";
16
17 beforeAll(async () => {
18 dataDir = await mkdtemp(join(tmpdir(), "forge-capnojs-"));
19 app = createApp({ dataDir, port: 0 });
20 servers = await serve(app);
21 const admin = await seedAdmin(app, { username: "alice", email: "[email protected]", password: "correct-horse-battery" });
22 if (!admin.ok) throw new Error(admin.error);
23 const minted = await fetch(`${servers.url}/api/v1/tokens`, {
24 method: "POST",
25 headers: { "content-type": "application/json" },
26 body: JSON.stringify({ adminConfirm: true, username: "alice", password: "correct-horse-battery", name: "cli", scope: "admin" }),
27 });
28 bearer = ((await minted.json()) as { token: string }).token;
29 });
30
31 afterAll(async () => {
32 servers.stop();
33 app.close();
34 await rm(dataDir, { recursive: true, force: true });
35 });
36
37 const page = async () => {
38 const res = await fetch(`${servers.url}/settings/capacity`, { headers: { authorization: `Bearer ${bearer}` } });
39 expect(res.status).toBe(200);
40 return res.text();
41 };
42
43 /**
44 * The page's own content: from `.settings-main` to the end of <main>. The
45 * shared settings nav before it (its `js-only` filter) is chrome, held by
46 * its own tests; this page must not lean on it.
47 */
48 const main = (html: string) => /<div class="settings-main"[\s\S]*<\/main>/.exec(html)?.[0] ?? "";
49
50 test("the page's content carries no script and no inline handler", async () => {
51 const body = main(await page());
52 expect(body).not.toBe("");
53 expect(body).not.toMatch(/<script\b/i);
54 expect(body).not.toMatch(/\son[a-z]+\s*=/i);
55 expect(body).not.toMatch(/javascript:/i);
56 });
57
58 test("every change is a plain POST form to /settings/capacity, crew size and CPUs among them; self-swap is read, not changed", async () => {
59 const body = main(await page());
60 const forms = [...body.matchAll(/<form\b([^>]*)>([\s\S]*?)<\/form>/g)];
61 expect(forms.length).toBeGreaterThan(0);
62 for (const [, attrs] of forms) {
63 expect(attrs).toMatch(/method="post"/);
64 expect(attrs).toMatch(/action="\/settings\/capacity"/);
65 }
66 const keys = forms.map(([, , inner]) => /name="key" value="([^"]+)"/.exec(inner!)?.[1]);
67 // Crew size, and the two settings sized from the pod's CPUs.
68 expect(keys).toEqual(expect.arrayContaining(["crew.concurrency", "checks.concurrency", "checks.shards"]));
69 // Nothing outside a form asks to be pressed: a button out of a form needs a script.
70 const outside = body.replace(/<form\b[\s\S]*?<\/form>/g, "");
71 expect(outside).not.toMatch(/<(button|input|select|textarea)\b/);
72 expect(outside).toContain('id="self-swap"');
73 });
74
75 test("a change from another origin is refused and changes nothing", async () => {
76 const before = await page();
77 const form = /<form\b[^>]*>([\s\S]*?)<\/form>/.exec(main(before))![1]!;
78 const key = /name="key" value="([^"]+)"/.exec(form)![1]!;
79 const res = await fetch(`${servers.url}/settings/capacity`, {
80 method: "POST",
81 redirect: "manual",
82 headers: { authorization: `Bearer ${bearer}`, origin: "https://elsewhere.example", "content-type": "application/x-www-form-urlencoded" },
83 body: new URLSearchParams({ key, reset: "1" }),
84 });
85 expect(res.status).toBe(403);
86 expect(main(await page())).not.toContain("→");
87 });
88
89 test("the same change from this origin lands, with no script involved", async () => {
90 const form = /<form\b[^>]*>([\s\S]*?)<\/form>/.exec(main(await page()))![1]!;
91 const key = /name="key" value="([^"]+)"/.exec(form)![1]!;
92 const value = /<input[^>]*type="number"[^>]*min="(\d+)"/.exec(form)?.[1];
93 const res = await fetch(`${servers.url}/settings/capacity`, {
94 method: "POST",
95 redirect: "manual",
96 headers: { authorization: `Bearer ${bearer}`, origin: servers.url, "content-type": "application/x-www-form-urlencoded" },
97 body: new URLSearchParams({ key, value: value ?? "" }),
98 });
99 expect(res.status).toBe(303);
100 expect(res.headers.get("location")).toContain("/settings/capacity");
101 });
test/capacity-timing.test.ts +32
@@ -6,6 +6,7 @@ import { join } from "node:path";
66 import { createApp, type App } from "../src/app.ts";
77 import { serve, type Servers } from "../src/http/server.ts";
88 import { seedAdmin } from "./helpers/admin.ts";
9 import { expectWork, requestStatements } from "./helpers/work.ts";
910
1011 let dataDir = "";
1112 let app: App, servers: Servers, bearer = "";
@@ -63,3 +64,34 @@ test("render does not include the time already named as errors", async () => {
6364 errors.recent = real;
6465 }
6566 });
67
68 // A warm answer costs a fixed, small number of SQL statements (#9725): work is
69 // counted, not timed (#7824), so a regression — an N+1 over the settings or
70 // the change history, a lost cache — fails every run, not a lucky one.
71 // 58 warm today, 59 once a setting is away from its default; the rest is margin.
72 const WARM_STATEMENTS = 64;
73
74 test("a warm capacity page costs a bounded number of statements, however many changes it lists", async () => {
75 const url = `${servers.url}/settings/capacity`;
76 const init = { headers: { authorization: `Bearer ${bearer}` } };
77 await (await fetch(url, init)).text();
78 const warm = await requestStatements(app.db, url, init);
79 expectWork(warm, { max: WARM_STATEMENTS });
80 const change = async (n: number) => {
81 const res = await fetch(url, {
82 method: "POST",
83 redirect: "manual",
84 headers: { ...init.headers, origin: servers.url, "content-type": "application/x-www-form-urlencoded" },
85 body: new URLSearchParams({ key: "queue.batch", value: String(n) }),
86 });
87 expect(res.status).toBe(303);
88 };
89 // A setting away from its default reads who changed it; after that, more
90 // history costs nothing more: twelve changes and twenty-four cost the same.
91 for (let n = 1; n <= 12; n++) await change((n % 7) + 1);
92 const some = await requestStatements(app.db, url, init);
93 for (let n = 13; n <= 24; n++) await change((n % 7) + 1);
94 const more = await requestStatements(app.db, url, init);
95 expectWork(some, { max: WARM_STATEMENTS });
96 expect(more).toBe(some);
97 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs