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

The resilience page's Server-Timing names its phases: session, each read, render #3520

Merged opened this

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

Merged

scout/forge:scout/3505-the-resilience-page-s-server-timing-name → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/3505-the-resilience-page-s-server-timing-name was merged into main as f9824b9.

Review

Sign in to review this change.
2 files changed +59 −2
src/web/pages/resilience.ts +7 −2
@@ -8,6 +8,7 @@
88 import { readResilience } from "../../checks/resilience.ts";
99 import type { Standing } from "../../core/threats.ts";
1010 import type { Ctx } from "../../http/context.ts";
11 import { notePhase, takeSessionTime, timePhase } from "../../core/route_timing.ts";

Nit: put this import with the other ../../core imports, before ../../http/context.ts, to match how the file is ordered.

1112 import { notFoundPage, signInFirst } from "../chrome.ts";
1213 import { html } from "../html.ts";
1314 import { page } from "../layout.ts";
@@ -17,9 +18,11 @@ const TONE = { green: "success", red: "danger", untested: "default", "no test ye
1718
1819 // GET /resilience: a forge admin's, since live red lines name what to attack.
1920 export function resiliencePage(ctx: Ctx): Response {
21 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
2022 if (!ctx.auth.user) return signInFirst(ctx);
2123 if (!ctx.auth.user.isAdmin) return notFoundPage(ctx);
22 const rows = readResilience(ctx.app.config.dataDir);
24 const rows = timePhase(ctx.request, "threats", () => readResilience(ctx.app.config.dataDir));
25 const rendering = performance.now();
2326 const count = (s: Standing): number => rows.filter((r) => r.s === s).length;
2427 const body = html`<div class="page-head">
2528 <h1>Resilience</h1>
@@ -47,5 +50,7 @@ export function resiliencePage(ctx: Ctx): Response {
4750 </ul>`,
4851 { pad: false },
4952 )}`;
50 return page({ ctx, title: "Resilience", body });
53 const response = page({ ctx, title: "Resilience", body });
54 notePhase(ctx.request, "render", performance.now() - rendering);
55 return response;
5156 }
test/resilience-timing.test.ts added +52
@@ -0,0 +1,52 @@
1 /** `/resilience` names session, threats and render in Server-Timing (#3505). */
2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { mkdtempSync, rmSync } from "node:fs";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createApp, type App } from "../src/app.ts";
7 import { createUser } from "../src/core/users.ts";
8 import { serve, type Servers } from "../src/http/server.ts";
9 import { quietLogs } from "./helpers/globals.ts";
10
11 quietLogs();
12
13 let root = "";
14 let app: App;
15 let servers: Servers;
16
17 beforeAll(async () => {
18 root = mkdtempSync(join(tmpdir(), "forge-resilience-timing-"));
19 app = createApp({ dataDir: root, port: 0 });
20 const u = await createUser(app.db, {
21 origin: "seed",
22 trusted: true,
23 username: "alice",
24 email: "[email protected]",
25 password: "a-good-passphrase",
26 adminIfFirst: true,
27 });
28 if (!u.ok) throw new Error(u.error);
29 servers = await serve(app);
30 });
31 afterAll(() => {
32 servers.stop();
33 app.close();
34 rmSync(root, { recursive: true, force: true });
35 });
36
37 test("GET /resilience names its phases", async () => {
38 const signin = await fetch(`${servers.url}/signin`, {
39 method: "POST",
40 headers: { origin: servers.url },
41 body: new URLSearchParams({ username: "alice", password: "a-good-passphrase" }),
42 redirect: "manual",
43 });
44 const cookie = (signin.headers.get("set-cookie") ?? "").split(";")[0]!;
45 const res = await fetch(`${servers.url}/resilience`, { headers: { cookie } });
46 const body = await res.text();
47 expect(res.status).toBe(200);
48 expect(body).toContain("<h1>Resilience</h1>");
49 const parts = (res.headers.get("server-timing") ?? "").split(", ");
50 for (const name of ["app", "session", "threats", "render"])
51 expect(parts.find((p) => p.startsWith(`${name};`))).toMatch(/^[a-z]+;dur=\d+\.\d$/);
52 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs