import { test, expect } from "@playwright/test"; import { resetData, writeSettings } from "./helpers"; // /api/pulse is the change token the global auto-refresh polls instead of // re-rendering the page tree on a timer. Its entire justification is that it is // cheap enough to ask constantly, so "cheap" is a contract, not an aspiration: // in-memory job/queue/worker state plus two stat() calls, and NO corpus // contact on the idle path. // // If someone later adds a channel walk, a snapshot read or a directory listing // to it, these fail — which is the point. test.describe("/api/pulse", () => { test.beforeEach(async () => { await resetData("channel-with-counts"); }); test("is fast, and stays fast when asked repeatedly", async ({ request }) => { // Warm-up: the first hit in a dev server pays compilation, which is not // what's being measured. const first = await request.get("/api/pulse"); expect(first.ok()).toBe(true); const body = await first.json(); expect(typeof body.rev).toBe("string"); expect(body.rev.length).toBeGreaterThan(0); // A first call with no known rev must return the full payload. expect(body.changed).toBe(true); expect(typeof body.cleanableBytes).toBe("number"); // The idle path: hand back the rev we were just given. Nothing has changed, // so the endpoint must say so without recomputing anything expensive. const started = Date.now(); const rounds = 5; for (let i = 0; i < rounds; i++) { const res = await request.get( `/api/pulse?rev=${encodeURIComponent(body.rev)}`, ); expect(res.ok()).toBe(true); const idle = await res.json(); expect(idle.rev).toBe(body.rev); expect(idle.changed).toBe(false); } const perCall = (Date.now() - started) / rounds; // Generous versus the ~2 ms the handler actually costs — this is a // tripwire for "someone put a corpus walk in the poller", not a benchmark, // and it runs on a shared machine. expect(perCall, `idle pulse averaged ${perCall}ms per call`).toBeLessThan( 250, ); }); test("the token moves when something the editor displays changes", async ({ request, }) => { const before = await (await request.get("/api/pulse")).json(); // settings.json's mtime is part of the token — this is what makes editing // settings repaint the sidebar without a reload. await writeSettings({ autoRefreshIntervalSeconds: 5, adminTitle: "Pulse Moved", }); await expect .poll( async () => (await (await request.get("/api/pulse")).json()).rev, { timeout: 5_000 }, ) .not.toBe(before.rev); }); test("the token is stable when nothing changes", async ({ request }) => { const a = await (await request.get("/api/pulse")).json(); await new Promise((r) => setTimeout(r, 1_200)); const b = await (await request.get("/api/pulse")).json(); expect(b.rev).toBe(a.rev); }); });