Files
konstitisyon.nu/lib/__tests__/rate-limit.test.js
T

109 lines
2.9 KiB
JavaScript
Raw Normal View History

import {
describe,
it,
expect,
beforeEach,
afterEach,
vi,
} from 'vitest'
import {createRateLimiter} from '../rate-limit.js'
describe('createRateLimiter', () => {
beforeEach(() => {
vi.useFakeTimers()
vi.setSystemTime(0) // Temps fixe et prévisible
})
afterEach(() => {
vi.useRealTimers()
})
it('autorise les requêtes dans la limite', () => {
const check = createRateLimiter({windowMs: 60_000, max: 3})
expect(check('ip1').success).toBe(true)
expect(check('ip1').success).toBe(true)
expect(check('ip1').success).toBe(true)
})
it('bloque quand la limite est atteinte', () => {
const check = createRateLimiter({windowMs: 60_000, max: 2})
check('ip1')
check('ip1')
const result = check('ip1')
expect(result.success).toBe(false)
})
it('renvoie retryAfter en secondes quand bloqué', () => {
const check = createRateLimiter({windowMs: 60_000, max: 1})
check('ip1') // start = 0
const result = check('ip1') // now = 0, retryAfter = ceil((0 + 60000 - 0) / 1000)
expect(result.success).toBe(false)
expect(result.retryAfter).toBe(60)
})
it('retryAfter décroît avec le temps écoulé', () => {
const check = createRateLimiter({windowMs: 60_000, max: 1})
check('ip1') // start = 0
vi.advanceTimersByTime(30_000) // 30s plus tard
const result = check('ip1') // retryAfter = ceil((0 + 60000 - 30000) / 1000) = 30
expect(result.success).toBe(false)
expect(result.retryAfter).toBe(30)
})
it('réinitialise le compteur après expiration de la fenêtre', () => {
const check = createRateLimiter({windowMs: 60_000, max: 1})
check('ip1')
expect(check('ip1').success).toBe(false)
vi.advanceTimersByTime(61_000) // Fenêtre expirée
expect(check('ip1').success).toBe(true)
})
it('suit les clés indépendamment', () => {
const check = createRateLimiter({windowMs: 60_000, max: 1})
check('ip1')
expect(check('ip1').success).toBe(false)
expect(check('ip2').success).toBe(true) // ip2 non affectée
})
it('réinitialise proprement à la requête suivante après expiration', () => {
const check = createRateLimiter({windowMs: 60_000, max: 2})
check('ip1')
check('ip1')
expect(check('ip1').success).toBe(false)
vi.advanceTimersByTime(61_000)
// Nouvelle fenêtre : 2 requêtes autorisées à nouveau
expect(check('ip1').success).toBe(true)
expect(check('ip1').success).toBe(true)
expect(check('ip1').success).toBe(false)
})
it('purge les entrées expirées lors du cleanup', () => {
const check = createRateLimiter({windowMs: 60_000, max: 5})
// Plusieurs IPs remplissent le store
check('ip1')
check('ip2')
check('ip3')
// Avancer d'une fenêtre complète pour déclencher le cleanup
vi.advanceTimersByTime(61_000)
// ip1 repart de zéro (entrée purgée)
expect(check('ip1').success).toBe(true)
})
})