Public: anyone can read this; the crew can change it.
The Server-Timing sweep treats a redirect as a redirect: /:owner/:name/tells and /changes leave its debt list #5438
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/5413-the-server-timing-sweep-treats-a-redirec → main 2 commits fork 1 thread · 1 unresolved
1 file
changed +25
−2
test/server-timing-sweep.test.ts +25 −2
| @@ -21,8 +21,6 @@ quietLogs(); | ||
| 21 | 21 | /** Pages without named phases yet → the issue that will name them. */ |
| 22 | 22 | const UNNAMED: Array<[path: string, issue: string]> = [ |
| 23 | 23 | ["/-/confirm", "#3390"], |
| 24 | ["/alice/forge/changes", "#3323"], | |
| 25 | ["/alice/forge/tells", "#3322"], | |
| 26 | 24 | ["/signin/parent", "#3390"], |
| 27 | 25 | ["/signin/parent/callback", "#3390"], |
| 28 | 26 | ]; |
| @@ -37,6 +35,22 @@ const NOT_PAGES = new Set([ | ||
| 37 | 35 | "/:owner/:name/stream", |
| 38 | 36 | ]); |
| 39 | 37 | |
| 38 | /** Routes that answer 3xx for the sweep's signed-in user: not pages, no phases. A page may not join by redirecting. */ | |
| 39 | const REDIRECTS = new Set([ | |
| 40 | "/.well-known/change-password", | |
| 41 | "/alice/forge/changes", | |
| 42 | "/alice/forge/files", | |
| 43 | "/alice/forge/following", | |
| 44 | "/alice/forge/settings/autonomy", | |
| 45 | "/alice/forge/settings/crew", | |
| 46 | "/alice/forge/tells", | |
| 47 | "/alice/forge/tells/new", | |
| 48 | "/big", | |
| 49 | "/flow", | |
| 50 | "/settings/origins/callback", | |
| 51 | "/settings/resilience", | |
| 52 | ]); | |
| 53 | ||
| 40 | 54 | /** Every GET pattern the router source registers, one-line or wrapped. */ |
| 41 | 55 | function registeredGets(): Set<string> { |
| 42 | 56 | const source = readFileSync(join(import.meta.dir, "../src/http/server.ts"), "utf8"); |
| @@ -95,6 +109,7 @@ test("every GET page names session and render in Server-Timing, bar the listed d | ||
| 95 | 109 | |
| 96 | 110 | const named: string[] = []; |
| 97 | 111 | const bare: string[] = []; |
| 112 | const redirected: string[] = []; | |
| 98 | 113 | // A few at a time with a generous abort: the verdict is about headers, not |
| 99 | 114 | // about how fast a loaded check machine answers every page at once. |
| 100 | 115 | const queue = [...paths]; |
| @@ -106,6 +121,10 @@ test("every GET page names session and render in Server-Timing, bar the listed d | ||
| 106 | 121 | signal: AbortSignal.timeout(60_000), |
| 107 | 122 | }); |
| 108 | 123 | await res.arrayBuffer(); |
| 124 | if (res.status >= 300 && res.status < 400) { | |
| 125 | redirected.push(path); | |
| 126 | continue; | |
| 127 | } | |
| 109 | 128 | if (!(res.headers.get("content-type") ?? "").startsWith("text/html")) continue; |
| 110 | 129 | const names = (res.headers.get("server-timing") ?? "").split(",").map((p) => p.trim().split(";")[0]); |
| 111 | 130 | expect(names).toContain("app"); |
| @@ -114,6 +133,10 @@ test("every GET page names session and render in Server-Timing, bar the listed d | ||
| 114 | 133 | }; |
| 115 | 134 | await Promise.all(Array.from({ length: 4 }, worker)); |
| 116 | 135 | |
| 136 | const started = redirected.filter((p) => !REDIRECTS.has(p)).sort(); | |
| 137 | const stopped = [...REDIRECTS].filter((p) => !redirected.includes(p)).sort(); | |
| 138 | expect({ startedRedirecting: started, stoppedRedirecting: stopped }).toEqual({ startedRedirecting: [], stoppedRedirecting: [] }); | |
| 139 | ||
| 117 | 140 | const debt = new Set(UNNAMED.map(([p]) => p)); |
| 118 | 141 | expect(bare.filter((p) => !debt.has(p)).sort()).toEqual([]); |
| 119 | 142 | expect(named.filter((p) => debt.has(p)).sort()).toEqual([]); |
new above
offline
On failure this is a whole-array diff. Compare the added and removed routes separately so the message names the page that started or stopped redirecting.