diff --git a/src/infra/fixed-window-rate-limit.test.ts b/src/infra/fixed-window-rate-limit.test.ts index 1afc50974d0..8290c1d4176 100644 --- a/src/infra/fixed-window-rate-limit.test.ts +++ b/src/infra/fixed-window-rate-limit.test.ts @@ -18,6 +18,35 @@ describe("fixed-window rate limiter", () => { expect(limiter.consume()).toMatchObject({ allowed: true, remaining: 1 }); }); + it("clamps maxRequests and windowMs to at least one", () => { + let nowMs = 100; + const limiter = createFixedWindowRateLimiter({ + maxRequests: 0.2, + windowMs: 0.4, + now: () => nowMs, + }); + + expect(limiter.consume()).toMatchObject({ allowed: true, remaining: 0, retryAfterMs: 0 }); + expect(limiter.consume()).toMatchObject({ allowed: false, remaining: 0, retryAfterMs: 1 }); + + nowMs += 1; + expect(limiter.consume()).toMatchObject({ allowed: true, remaining: 0 }); + }); + + it("reports the remaining retry window after later blocked attempts", () => { + let nowMs = 1_000; + const limiter = createFixedWindowRateLimiter({ + maxRequests: 1, + windowMs: 1_000, + now: () => nowMs, + }); + + expect(limiter.consume()).toMatchObject({ allowed: true, remaining: 0 }); + + nowMs += 250; + expect(limiter.consume()).toMatchObject({ allowed: false, retryAfterMs: 750 }); + }); + it("supports explicit reset", () => { const limiter = createFixedWindowRateLimiter({ maxRequests: 1,