test: tests unitaires Vitest — format, version-utils, rate-limit
- Installe vitest@4 + @vitest/coverage-v8 (40 tests, 0 échec) - lib/__tests__/format.test.js : 14 tests (formatKonstitisyon, formatDate, hasRestrictedChar) - lib/__tests__/version-utils.test.js : 17 tests (filterVersions par texte/auteur/date, getFilterStats) - lib/__tests__/rate-limit.test.js : 9 tests avec fake timers (limite, reset, retryAfter, keys indépendantes) - vitest.config.mjs : environnement node, imports explicites (pas de globals) - package.json : scripts test / test:watch / test:coverage + override XO pour les fichiers de test Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user