Repository navigation
Commit f9c1825
test(rate-limit): a flood is judged with the limiter's clock held still — a busy runner that served 60 requests over 260 ms let 22 through by refill, which is right, and failed the test
'hammering an endpoint that walks the disk is still rate limited' fired 60
GET /api/fs/list at once and asserted at most 21 answered 200. The strict
lane refills 10 tokens a second from Date.now(), so the count depended on
how long the server took to answer: under load on 12 Oct 2026 (the local CI
simulation's pt-BR leg) the 60 were served over ~260 ms, the bucket refilled
2.6 tokens, 22 got through, and the test failed on a product that was right.
`limited > 0` read the clock the same way, in the other direction.
The test now holds Date still for the flood (node:test mock timers, Date
only, so sockets and server timers keep the real clock; Node 20.11+, CI runs
20.20.2) and asserts a count: exactly the strict lane's published burst
answers 200 and every other request 429, both read from
rateLimitLanes.describe(). It then advances that clock by one second and
asserts exactly the sustained rate gets through, which proves the clock held
still is the one the limiter reads. The old `<= 21` was also the only test
that held the strict lane at the 20 burst / 10 per second AGENTS.md and the
OpenAPI text promise; that promise is now an explicit assertion on
describe(), since a count read from describe() alone would follow a widened
capacity.
Reproduced first: with the limiter's clock stepped 260 ms between two
halves of the flood the old assertions saw 22 x 200 / 38 x 429 and failed
with the CI message; with a real 400 ms pause, 24 x 200. The same real pause
with the clock held still gives exactly 20 / 40.
Hand mutants of src/middleware/rateLimiter.ts, each RED, each restored and
checked by SHA-256: api capacity 20 -> 22 (the docs pin); the api bucket
starting 2 tokens over its published capacity (21 x 200 — the old `<= 21`
let this through); a refill credited during the burst on a still clock
(25 x 200); the limiter reading performance.now() instead of Date (RED at
the flood when the real clock refills during it, 21 x 200, or else at the
one-second step, 0 x 200 / 20 x 429 — seen both ways).
Runs: rateLimiterLanes 10 times under the normal env and 10 times under the
CI pt-BR leg's env (LC_ALL/LANG pt_BR.UTF-8, TREEMAP_EXPECT_LOCALE=pt-BR),
one at a time on a busy machine (1-minute load 9.5-11.9): 20 of 20 green,
8/8 each. noWallClockBounds, apiContract, frontendContract, goldenResponses,
childEnvCarriesGuard and typecheck green.
The other limiter tests were swept and left alone: every other assertion
against the limiter is "no 429" or "some served" (boot burst, metadata lane,
draining both ways, status polls, thumbnails, apiHardening's 50 subtrees),
and elapsed time can only add tokens, so none of them reads the clock in the
direction that fails. fleetSync's 429s are the pairing-guess count, checked
before anything else, not the token bucket.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>1 parent 6ac10d8 commit f9c1825
1 file changed
Lines changed: 42 additions & 8 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
163 | 163 | | |
164 | 164 | | |
165 | 165 | | |
166 | | - | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
167 | 192 | | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
168 | 196 | | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
169 | 201 | | |
170 | | - | |
171 | | - | |
172 | | - | |
173 | | - | |
174 | | - | |
175 | | - | |
176 | | - | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
177 | 210 | | |
| 211 | + | |
178 | 212 | | |
179 | 213 | | |
180 | 214 | | |
| |||
0 commit comments