const { RateLimiter } = require('./solution'); function clock(start = 0) { let t = start; return { now: () => t, advance: (ms) => { t += ms; } }; } describe('RateLimiter', () => { test('validates constructor arguments', () => { expect(() => new RateLimiter({ capacity: 0, refillPerSec: 1 })).toThrow(RangeError); expect(() => new RateLimiter({ capacity: 1, refillPerSec: -1 })).toThrow(RangeError); }); test('uses the system clock by default', () => { const rl = new RateLimiter({ capacity: 1, refillPerSec: 1 }); expect(rl.tryRemove()).toBe(true); }); test('starts full and spends tokens', () => { const c = clock(); const rl = new RateLimiter({ capacity: 2, refillPerSec: 1, now: c.now }); expect(rl.tryRemove()).toBe(true); expect(rl.tryRemove()).toBe(true); expect(rl.tryRemove()).toBe(false); }); test('can spend exactly all remaining tokens', () => { const c = clock(); const rl = new RateLimiter({ capacity: 3, refillPerSec: 1, now: c.now }); expect(rl.tryRemove(3)).toBe(true); expect(rl.tokens).toBe(0); }); test('failed request spends nothing', () => { const c = clock(); const rl = new RateLimiter({ capacity: 2, refillPerSec: 1, now: c.now }); expect(rl.tryRemove(5)).toBe(false); expect(rl.tokens).toBe(2); }); test('refills over time but never above capacity', () => { const c = clock(); const rl = new RateLimiter({ capacity: 2, refillPerSec: 1, now: c.now }); rl.tryRemove(2); c.advance(1000); expect(rl.tryRemove()).toBe(true); expect(rl.tryRemove()).toBe(false); c.advance(10000); rl.waitTime(); expect(rl.tokens).toBe(2); }); test('rejects non-positive cost', () => { const rl = new RateLimiter({ capacity: 1, refillPerSec: 1, now: clock().now }); expect(() => rl.tryRemove(0)).toThrow(RangeError); }); test('waitTime rounds up and handles zero refill', () => { const c = clock(); const rl = new RateLimiter({ capacity: 1, refillPerSec: 3, now: c.now }); expect(rl.waitTime()).toBe(0); rl.tryRemove(); expect(rl.waitTime()).toBe(334); const frozen = new RateLimiter({ capacity: 1, refillPerSec: 0, now: c.now }); frozen.tryRemove(); expect(frozen.waitTime()).toBe(Infinity); }); });