141 lines
3.7 KiB
TypeScript
141 lines
3.7 KiB
TypeScript
import { tap, expect } from '@push.rocks/tapbundle';
|
|
import { RateLimiter } from '../ts/mail/delivery/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);
|
|
});
|
|
|
|
tap.test('stop', async () => {
|
|
await tap.stopForcefully();
|
|
});
|
|
|
|
export default tap.start(); |