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
This commit is contained in:
2026-04-18 17:14:25 +02:00
parent 036c0a8d63
commit c3d9fbd8d3
10 changed files with 707 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
package infra
import "testing"
func TestRateLimiterNew(t *testing.T) {
t.Run("crea limiter con rate y burst configurados", func(t *testing.T) {
rl := RateLimiterNew(10, 20)
if rl == nil {
t.Fatal("RateLimiterNew retorno nil")
}
if rl.rate != 10 {
t.Errorf("rate=%v, want 10", rl.rate)
}
if rl.burst != 20 {
t.Errorf("burst=%d, want 20", rl.burst)
}
})
t.Run("valores cero se sustituyen por 1", func(t *testing.T) {
rl := RateLimiterNew(0, 0)
if rl.rate != 1 {
t.Errorf("rate=%v, want 1 (default)", rl.rate)
}
if rl.burst != 1 {
t.Errorf("burst=%d, want 1 (default)", rl.burst)
}
rl2 := RateLimiterNew(-5, -10)
if rl2.rate != 1 || rl2.burst != 1 {
t.Errorf("valores negativos no se normalizaron a 1: rate=%v burst=%d", rl2.rate, rl2.burst)
}
})
t.Run("el mapa de clientes empieza vacio", func(t *testing.T) {
rl := RateLimiterNew(10, 20)
if rl.clients == nil {
t.Error("clients map es nil, deberia ser inicializado")
}
if len(rl.clients) != 0 {
t.Errorf("clients len=%d, want 0", len(rl.clients))
}
})
}