88 lines
3.0 KiB
TypeScript
88 lines
3.0 KiB
TypeScript
// @ts-nocheck
|
|
import { afterEach, beforeEach, describe, expect, it, spyOn } from 'bun:test';
|
|
import logger from '../src/utils/logger';
|
|
import { checkRateLimit, cleanupRateLimitCache } from '../src/utils/rateLimit';
|
|
|
|
// Spy on logger.warn
|
|
const warnSpy = spyOn(logger, 'warn');
|
|
|
|
describe('Rate Limiter', () => {
|
|
beforeEach(() => {
|
|
warnSpy.mockClear();
|
|
// Set custom env variables for predictable tests
|
|
process.env.RATE_LIMIT_WINDOW_MS = '100'; // 100ms window
|
|
process.env.RATE_LIMIT_MAX_REQUESTS = '3'; // max 3 requests
|
|
});
|
|
|
|
afterEach(() => {
|
|
delete process.env.RATE_LIMIT_WINDOW_MS;
|
|
delete process.env.RATE_LIMIT_MAX_REQUESTS;
|
|
});
|
|
|
|
it('should allow requests under the limit', () => {
|
|
const key = 'user-1';
|
|
expect(checkRateLimit(key)).toBe(true);
|
|
expect(checkRateLimit(key)).toBe(true);
|
|
expect(checkRateLimit(key)).toBe(true);
|
|
expect(warnSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should block requests exceeding the limit and log a warning', () => {
|
|
const key = 'user-2';
|
|
expect(checkRateLimit(key)).toBe(true);
|
|
expect(checkRateLimit(key)).toBe(true);
|
|
expect(checkRateLimit(key)).toBe(true);
|
|
|
|
// 4th request exceeds limit of 3
|
|
expect(checkRateLimit(key)).toBe(false);
|
|
expect(warnSpy).toHaveBeenCalled();
|
|
const callArgs = warnSpy.mock.calls[0];
|
|
expect(callArgs[0]).toBe('Rate limit exceeded');
|
|
expect(callArgs[1].key).toBe(key);
|
|
});
|
|
|
|
it('should reset request count after the window passes', async () => {
|
|
const key = 'user-3';
|
|
expect(checkRateLimit(key)).toBe(true);
|
|
expect(checkRateLimit(key)).toBe(true);
|
|
expect(checkRateLimit(key)).toBe(true);
|
|
expect(checkRateLimit(key)).toBe(false); // blocked
|
|
|
|
// Wait for window to expire (100ms)
|
|
await new Promise((resolve) => setTimeout(resolve, 110));
|
|
|
|
// Should be allowed again
|
|
expect(checkRateLimit(key)).toBe(true);
|
|
});
|
|
|
|
it('should cleanup rate limit cache of expired keys', async () => {
|
|
const key1 = 'cleanup-1';
|
|
const key2 = 'cleanup-2';
|
|
|
|
// Populate keys
|
|
expect(checkRateLimit(key1)).toBe(true);
|
|
expect(checkRateLimit(key2)).toBe(true);
|
|
|
|
// Run cleanup immediately (none should be expired yet as 100ms hasn't passed)
|
|
cleanupRateLimitCache();
|
|
|
|
// Verify still tracked (counts shouldn't reset, e.g., if we consume remaining limits)
|
|
expect(checkRateLimit(key1)).toBe(true); // request 2
|
|
expect(checkRateLimit(key1)).toBe(true); // request 3
|
|
expect(checkRateLimit(key1)).toBe(false); // request 4 (blocked)
|
|
|
|
// Wait for window to expire
|
|
await new Promise((resolve) => setTimeout(resolve, 110));
|
|
|
|
// Run cleanup
|
|
cleanupRateLimitCache();
|
|
|
|
// Since they were deleted from the map, they should be initialized as new records
|
|
// If they were cleaned up, we should be able to do 3 requests again
|
|
expect(checkRateLimit(key1)).toBe(true); // 1
|
|
expect(checkRateLimit(key1)).toBe(true); // 2
|
|
expect(checkRateLimit(key1)).toBe(true); // 3
|
|
expect(checkRateLimit(key1)).toBe(false); // 4 (blocked)
|
|
});
|
|
});
|