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(); }); });