import test from 'node:test'; import assert from 'node:assert/strict'; import { createRateLimiter } from '../src/rate-limiter.js'; test('rate limiter blocks after the configured burst and recovers once the window drains', () => { const limiter = createRateLimiter({ windowMs: 1000, maxRequests: 3 }); const t0 = 1_000_000; assert.equal(limiter.take('k', t0).allowed, true); assert.equal(limiter.take('k', t0 + 1).allowed, true); assert.equal(limiter.take('k', t0 + 2).allowed, true); const blocked = limiter.take('k', t0 + 3); assert.equal(blocked.allowed, false); assert.ok(blocked.retryAfterMs > 0 && blocked.retryAfterMs <= 1000); // Crossing the window boundary must NOT immediately grant a fresh full // budget: that is the boundary-doubling burst. The trailing count decays, so // capacity returns only once the earlier requests have actually aged out. assert.equal(limiter.take('k', t0 + 1001).allowed, false, 'a 1ms boundary straddle must not reset the budget'); assert.equal(limiter.take('k', t0 + 2001).allowed, true, 'capacity must return once the window has genuinely drained'); }); test('rate limiter keys are isolated and never expose payload data', () => { const limiter = createRateLimiter({ windowMs: 60_000, maxRequests: 1 }); assert.equal(limiter.take('a').allowed, true); assert.equal(limiter.take('b').allowed, true); const blocked = limiter.take('a'); assert.equal(blocked.allowed, false); assert.doesNotMatch(blocked.reason, /image|base64|byte/i); });