import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest' // Reset module between tests so the buckets Map is fresh let checkRateLimit: (ip: string) => boolean beforeEach(async () => { vi.resetModules() const mod = await import('@/lib/rateLimit') checkRateLimit = mod.checkRateLimit }) afterEach(() => { vi.useRealTimers() }) describe('checkRateLimit', () => { it('allows the first request from a new IP', () => { expect(checkRateLimit('1.2.3.4')).toBe(true) }) it('allows up to 5 requests from the same IP (MAX_TOKENS)', () => { const ip = '10.0.0.1' // first request seeds bucket with MAX_TOKENS-1 (4) tokens consumed expect(checkRateLimit(ip)).toBe(true) // creates bucket, tokens = 4 expect(checkRateLimit(ip)).toBe(true) // tokens = 3 expect(checkRateLimit(ip)).toBe(true) // tokens = 2 expect(checkRateLimit(ip)).toBe(true) // tokens = 1 expect(checkRateLimit(ip)).toBe(true) // tokens = 0 }) it('blocks the 6th request within the refill window', () => { const ip = '10.0.0.2' for (let i = 0; i < 5; i++) checkRateLimit(ip) expect(checkRateLimit(ip)).toBe(false) }) it('tracks different IPs independently', () => { const ip1 = '192.168.1.1' const ip2 = '192.168.1.2' for (let i = 0; i < 5; i++) checkRateLimit(ip1) // ip1 exhausted, ip2 should still be allowed expect(checkRateLimit(ip1)).toBe(false) expect(checkRateLimit(ip2)).toBe(true) }) it('refills tokens after the 15-minute window passes', () => { vi.useFakeTimers() const ip = '172.16.0.1' for (let i = 0; i < 5; i++) checkRateLimit(ip) expect(checkRateLimit(ip)).toBe(false) // Advance past REFILL_INTERVAL_MS (15 min) vi.advanceTimersByTime(15 * 60 * 1_000 + 1) expect(checkRateLimit(ip)).toBe(true) }) it('does NOT refill before the 15-minute window elapses', () => { vi.useFakeTimers() const ip = '172.16.0.2' for (let i = 0; i < 5; i++) checkRateLimit(ip) expect(checkRateLimit(ip)).toBe(false) vi.advanceTimersByTime(14 * 60 * 1_000) expect(checkRateLimit(ip)).toBe(false) }) it('handles "unknown" as a valid IP key', () => { expect(checkRateLimit('unknown')).toBe(true) }) })