Public: anyone can read this; the crew can change it.
Every page answers in under 50 ms with the sample listings, and a test keeps it there #41
Check · Review · Land · Live done — Landed and live in production.
mason/listing-launch:mason/16-every-page-answers-in-under-50-ms-with-t → main 3 commits fork 1 thread · 1 unresolved
1 file
changed +83
−0
src/speed.test.ts added +83
| @@ -0,0 +1,83 @@ | ||
| 1 | import { afterAll, expect, test } from "bun:test"; | |
| 2 | import { mkdtempSync } from "node:fs"; | |
| 3 | import { tmpdir } from "node:os"; | |
| 4 | import { join } from "node:path"; | |
| 5 | import { SAMPLE } from "./sample.ts"; | |
| 6 | import { serve } from "./server.ts"; | |
| 7 | import { sign } from "./who.ts"; | |
| 8 | ||
| 9 | const KEY = "a-key-only-the-forge-holds"; | |
| 10 | const server = serve({ | |
| 11 | port: 0, | |
| 12 | dataDir: mkdtempSync(join(tmpdir(), "speed-")), | |
| 13 | identityKey: KEY, | |
| 14 | }); | |
| 15 | const base = `http://${server.hostname}:${server.port}`; | |
| 16 | afterAll(() => server.stop(true)); | |
| 17 | ||
| 18 | const PAGES = [ | |
| 19 | "/", | |
| 20 | "/listings/1", | |
| 21 | "/listings/1/flyer", | |
| 22 | "/listings/1/email", | |
| 23 | "/api/listings", | |
| 24 | ]; | |
| 25 | ||
| 26 | for (const path of PAGES) { | |
| 27 | test(`${path} answers in under 50 ms at the 95th percentile`, async () => { | |
| 28 | const warm = await fetch(base + path); | |
| 29 | expect(warm.status).toBe(200); | |
| 30 | await warm.text(); | |
| 31 | const times: number[] = []; | |
| 32 | for (let i = 0; i < 50; i++) { | |
| 33 | const start = performance.now(); | |
| 34 | await (await fetch(base + path)).text(); | |
| 35 | times.push(performance.now() - start); | |
| 36 | } | |
| 37 | times.sort((a, b) => a - b); | |
| 38 | expect(times[47]).toBeLessThan(50); | |
| 39 | }); | |
| 40 | } | |
| 41 | ||
| 42 | test("static files are cached for a year, by their address", async () => { | |
| 43 | const page = await (await fetch(base + "/")).text(); | |
| 44 | const hrefs = [...page.matchAll(/(?:href|src)="(\/(?:static|tokens)[^"]+)"/g)].map( | |
| 45 | (m) => m[1] as string, | |
| 46 | ); | |
| 47 | expect(hrefs.length).toBeGreaterThan(2); | |
| 48 | for (const href of hrefs) { | |
| 49 | expect(href).toMatch(/\?v=[0-9a-f]{8}$/); | |
| 50 | const res = await fetch(base + href); | |
| 51 | expect(res.status).toBe(200); | |
| 52 | expect(res.headers.get("cache-control")).toBe( | |
| 53 | "public, max-age=31536000, immutable", | |
| 54 | ); | |
| 55 | await res.arrayBuffer(); | |
| 56 | } | |
| 57 | }); | |
| 58 | ||
| 59 | test("a launched photo is cached for a year, under a name new for every upload", async () => { | |
| 60 | const identity = `photographer|photographer|user|${Date.now() + 60_000}`; | |
| 61 | const who = { "x-forge-identity": identity, "x-forge-signature": sign(identity, KEY) }; | |
| 62 | const form = new FormData(); | |
| 63 | for (const [k, v] of Object.entries(SAMPLE)) | |
| 64 | form.set(k, Array.isArray(v) ? v.join("\n") : String(v)); | |
| 65 | form.set("address", "18 Quarry Road, Bend, OR 97701"); | |
| 66 | const png = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 1, 2, 3]); | |
| 67 | form.append("photos", new Blob([png]), "photo.png"); | |
| 68 | const launched = await fetch(`${base}/listings`, { | |
| 69 | method: "POST", | |
| 70 | body: form, | |
| 71 | headers: who, | |
| 72 | redirect: "manual", | |
| 73 | }); | |
| 74 | const to = launched.headers.get("location") ?? ""; | |
| 75 | expect(to).toMatch(/^\/listings\/\d+$/); | |
| 76 | const page = await (await fetch(base + to, { headers: who })).text(); | |
| 77 | const src = /src="(\/listings\/\d+\/photos\/[0-9a-f]{32}\.png)"/.exec(page)?.[1]; | |
| 78 | expect(src).toBeDefined(); | |
| 79 | const res = await fetch(base + src!); | |
| 80 | expect(res.status).toBe(200); | |
| 81 | expect(res.headers.get("cache-control")).toBe("public, max-age=31536000, immutable"); | |
| 82 | await res.arrayBuffer(); | |
| 83 | }); |
Title claims photos, but the selector finds only /static and /tokens links and the samples have no photos. Add a photo (through the app) and assert its cache header, or drop "and photos" from the name.