Files
fn_registry/functions/infra/rate_limiter_cleanup_test.go
egutierrez c3d9fbd8d3 feat(infra): rate limit middlewares HTTP por IP y por key + tests
Implementa fase 2 del issue 0016:
- rate_limit_middleware: limita por IP (X-Forwarded-For > X-Real-IP > RemoteAddr)
- rate_limiter_by_key: middleware configurable con keyFunc custom (API key, user ID...)
- Cuando se rechaza responde 429 con HTTPError JSON y headers Retry-After + X-RateLimit-*
- Tests con httptest.NewRecorder cubriendo: limite, burst, IPs independientes, XFF prioritario,
  recarga temporal, JSON body, headers IETF, GC stop idempotente, key vacia salta limit
2026-04-18 17:14:25 +02:00

69 lines
1.7 KiB
Go

package infra
import (
"testing"
"time"
)
func TestRateLimiterCleanup(t *testing.T) {
t.Run("purga entries con lastSeen mas antiguo que maxAge", func(t *testing.T) {
rl := RateLimiterNew(10, 20)
// Inyectar manualmente una entry vieja
rl.mu.Lock()
rl.clients["old"] = &rateLimiterClient{tokens: 5, lastSeen: time.Now().Add(-time.Hour)}
rl.clients["new"] = &rateLimiterClient{tokens: 5, lastSeen: time.Now()}
rl.mu.Unlock()
stop := RateLimiterCleanup(rl, 10*time.Minute, 30*time.Millisecond)
defer stop()
// Esperar al menos un tick
time.Sleep(100 * time.Millisecond)
rl.mu.Lock()
_, oldExists := rl.clients["old"]
_, newExists := rl.clients["new"]
rl.mu.Unlock()
if oldExists {
t.Error("entry 'old' no fue purgada")
}
if !newExists {
t.Error("entry 'new' fue purgada incorrectamente")
}
})
t.Run("no purga entries recientes", func(t *testing.T) {
rl := RateLimiterNew(10, 20)
RateLimiterCheck(rl, "fresh")
stop := RateLimiterCleanup(rl, 10*time.Minute, 30*time.Millisecond)
defer stop()
time.Sleep(100 * time.Millisecond)
rl.mu.Lock()
_, exists := rl.clients["fresh"]
rl.mu.Unlock()
if !exists {
t.Error("entry reciente fue purgada incorrectamente")
}
})
t.Run("stop detiene la goroutine sin panic", func(t *testing.T) {
rl := RateLimiterNew(10, 20)
stop := RateLimiterCleanup(rl, time.Second, 10*time.Millisecond)
stop()
// Si esto no panica, pasa
time.Sleep(50 * time.Millisecond)
})
t.Run("stop es idempotente", func(t *testing.T) {
rl := RateLimiterNew(10, 20)
stop := RateLimiterCleanup(rl, time.Second, 10*time.Millisecond)
stop()
stop() // segunda llamada no debe panic
stop() // tercera tampoco
})
}