import { describe, it, expect, beforeEach } from 'bun:test'; import { RateLimiter } from '../../../src/services/rate-limit'; describe('RateLimiter - token bucket básico', () => { beforeEach(() => { RateLimiter.reset(); // No dependemos de NODE_ENV aquí; testea la clase aislada. process.env.RATE_LIMIT_PER_MIN = '15'; process.env.RATE_LIMIT_BURST = '15'; }); it('permite hasta el burst inicial y bloquea al exceder', () => { const user = '1234567890'; const t0 = 0; // 15 primeros deben pasar for (let i = 0; i < 15; i++) { const ok = RateLimiter.checkAndConsume(user, t0); expect(ok).toBe(true); } // 16º sin tiempo transcurrido debe bloquear const denied = RateLimiter.checkAndConsume(user, t0); expect(denied).toBe(false); }); it('recupera tokens con el tiempo (refill lineal por minuto)', () => { const user = '1234567890'; const t0 = 0; // Agotar bucket for (let i = 0; i < 15; i++) { expect(RateLimiter.checkAndConsume(user, t0)).toBe(true); } expect(RateLimiter.checkAndConsume(user, t0)).toBe(false); // agotado // Tras 60s debe haberse recuperado capacidad suficiente para permitir de nuevo const t1 = 60_000; expect(RateLimiter.checkAndConsume(user, t1)).toBe(true); // Y puede consumir varios más (hasta ~15 por minuto) let count = 1; while (RateLimiter.checkAndConsume(user, t1)) { count++; if (count > 20) break; // seguridad } expect(count).toBeGreaterThan(1); }); it('shouldNotify limita el aviso a como mucho 1/min por usuario', () => { const user = 'u1'; const t0 = 0; expect(RateLimiter.shouldNotify(user, t0)).toBe(true); // primera vez notifica expect(RateLimiter.shouldNotify(user, t0 + 10_000)).toBe(false); // dentro de la ventana expect(RateLimiter.shouldNotify(user, t0 + 60_000)).toBe(true); // pasado el cooldown }); });