import { tap, expect } from '@push.rocks/tapbundle'; import { RateLimiter } from '../ts/mta/classes.ratelimiter.js'; tap.test('RateLimiter - should be instantiable', async () => { const limiter = new RateLimiter({ maxPerPeriod: 10, periodMs: 1000, perKey: true }); expect(limiter).toBeTruthy(); }); tap.test('RateLimiter - should allow requests within rate limit', async () => { const limiter = new RateLimiter({ maxPerPeriod: 5, periodMs: 1000, perKey: true }); // Should allow 5 requests for (let i = 0; i < 5; i++) { expect(limiter.isAllowed('test')).toEqual(true); } // 6th request should be denied expect(limiter.isAllowed('test')).toEqual(false); }); tap.test('RateLimiter - should enforce per-key limits', async () => { const limiter = new RateLimiter({ maxPerPeriod: 3, periodMs: 1000, perKey: true }); // Should allow 3 requests for key1 for (let i = 0; i < 3; i++) { expect(limiter.isAllowed('key1')).toEqual(true); } // 4th request for key1 should be denied expect(limiter.isAllowed('key1')).toEqual(false); // But key2 should still be allowed expect(limiter.isAllowed('key2')).toEqual(true); }); tap.test('RateLimiter - should refill tokens over time', async () => { const limiter = new RateLimiter({ maxPerPeriod: 2, periodMs: 100, // Short period for testing perKey: true }); // Use all tokens expect(limiter.isAllowed('test')).toEqual(true); expect(limiter.isAllowed('test')).toEqual(true); expect(limiter.isAllowed('test')).toEqual(false); // Wait for refill await new Promise(resolve => setTimeout(resolve, 150)); // Should have tokens again expect(limiter.isAllowed('test')).toEqual(true); }); tap.test('RateLimiter - should support burst allowance', async () => { const limiter = new RateLimiter({ maxPerPeriod: 2, periodMs: 100, perKey: true, burstTokens: 2, // Allow 2 extra tokens for bursts initialTokens: 4 // Start with max + burst tokens }); // Should allow 4 requests (2 regular + 2 burst) for (let i = 0; i < 4; i++) { expect(limiter.isAllowed('test')).toEqual(true); } // 5th request should be denied expect(limiter.isAllowed('test')).toEqual(false); // Wait for refill await new Promise(resolve => setTimeout(resolve, 150)); // Should have 2 tokens again (rate-limited to normal max, not burst) expect(limiter.isAllowed('test')).toEqual(true); expect(limiter.isAllowed('test')).toEqual(true); // 3rd request after refill should fail (only normal max is refilled, not burst) expect(limiter.isAllowed('test')).toEqual(false); }); tap.test('RateLimiter - should return correct stats', async () => { const limiter = new RateLimiter({ maxPerPeriod: 10, periodMs: 1000, perKey: true }); // Make some requests limiter.isAllowed('test'); limiter.isAllowed('test'); limiter.isAllowed('test'); // Get stats const stats = limiter.getStats('test'); expect(stats.remaining).toEqual(7); expect(stats.limit).toEqual(10); expect(stats.allowed).toEqual(3); expect(stats.denied).toEqual(0); }); tap.test('RateLimiter - should reset limits', async () => { const limiter = new RateLimiter({ maxPerPeriod: 3, periodMs: 1000, perKey: true }); // Use all tokens expect(limiter.isAllowed('test')).toEqual(true); expect(limiter.isAllowed('test')).toEqual(true); expect(limiter.isAllowed('test')).toEqual(true); expect(limiter.isAllowed('test')).toEqual(false); // Reset limiter.reset('test'); // Should have tokens again expect(limiter.isAllowed('test')).toEqual(true); }); export default tap.start();