learning_ai_notes/backend/src/lib/rate-limit.test.ts

49 lines
1.7 KiB
TypeScript

import { beforeEach, describe, expect, it } from 'vitest';
import { TooManyRequestsError } from '@bytelyst/errors';
import { assertRateLimit, rateLimitKey, resetRateLimits } from './rate-limit.js';
describe('rate limiting', () => {
beforeEach(() => {
resetRateLimits();
});
it('normalizes missing key parts', () => {
expect(rateLimitKey('prompt-run', '', null, 'user_1')).toBe('prompt-run:unknown:unknown:user_1');
});
it('allows requests up to the policy limit', () => {
const policy = { label: 'test endpoint', max: 2, windowMs: 1_000 };
assertRateLimit('test:user_1', policy, 1_000);
assertRateLimit('test:user_1', policy, 1_500);
expect(() => assertRateLimit('test:user_1', policy, 2_001)).not.toThrow();
});
it('throws a shared 429 error when the bucket is exhausted', () => {
const policy = { label: 'test endpoint', max: 2, windowMs: 1_000 };
assertRateLimit('test:user_1', policy, 1_000);
assertRateLimit('test:user_1', policy, 1_001);
expect(() => assertRateLimit('test:user_1', policy, 1_999)).toThrow(TooManyRequestsError);
try {
assertRateLimit('test:user_1', policy, 1_999);
} catch (error) {
expect(error).toBeInstanceOf(TooManyRequestsError);
const rateLimitError = error as TooManyRequestsError;
expect(rateLimitError.statusCode).toBe(429);
expect(rateLimitError.details).toEqual({ limit: 2, windowMs: 1_000 });
}
});
it('isolates buckets by key', () => {
const policy = { label: 'test endpoint', max: 1, windowMs: 1_000 };
assertRateLimit('test:user_1', policy, 1_000);
expect(() => assertRateLimit('test:user_2', policy, 1_001)).not.toThrow();
});
});