Files
fn_registry/functions/infra/rate_limit_headers_test.go
T
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

68 lines
2.1 KiB
Go

package infra
import (
"strconv"
"testing"
"time"
)
func TestRateLimitHeaders(t *testing.T) {
now := time.Now()
t.Run("setea X-RateLimit-Limit con el burst", func(t *testing.T) {
result := RateLimitResult{Allowed: true, Remaining: 5, ResetAt: now}
h := RateLimitHeaders(result, 20)
if h.Get("X-RateLimit-Limit") != "20" {
t.Errorf("X-RateLimit-Limit=%q, want 20", h.Get("X-RateLimit-Limit"))
}
})
t.Run("setea X-RateLimit-Remaining con result.Remaining", func(t *testing.T) {
result := RateLimitResult{Allowed: true, Remaining: 7, ResetAt: now}
h := RateLimitHeaders(result, 10)
if h.Get("X-RateLimit-Remaining") != "7" {
t.Errorf("X-RateLimit-Remaining=%q, want 7", h.Get("X-RateLimit-Remaining"))
}
})
t.Run("setea X-RateLimit-Reset como timestamp unix", func(t *testing.T) {
future := now.Add(30 * time.Second)
result := RateLimitResult{Allowed: true, Remaining: 1, ResetAt: future}
h := RateLimitHeaders(result, 10)
got := h.Get("X-RateLimit-Reset")
want := strconv.FormatInt(future.Unix(), 10)
if got != want {
t.Errorf("X-RateLimit-Reset=%q, want %q", got, want)
}
})
t.Run("incluye Retry-After cuando no esta permitido", func(t *testing.T) {
result := RateLimitResult{Allowed: false, Remaining: 0, ResetAt: now, RetryAfter: 2.3}
h := RateLimitHeaders(result, 10)
if h.Get("Retry-After") == "" {
t.Fatal("Retry-After deberia estar seteado")
}
// Debe redondear hacia arriba
ra, _ := strconv.Atoi(h.Get("Retry-After"))
if ra != 3 {
t.Errorf("Retry-After=%d, want 3 (ceil de 2.3)", ra)
}
})
t.Run("Retry-After minimo es 1", func(t *testing.T) {
result := RateLimitResult{Allowed: false, Remaining: 0, ResetAt: now, RetryAfter: 0.1}
h := RateLimitHeaders(result, 10)
if h.Get("Retry-After") != "1" {
t.Errorf("Retry-After=%q, want 1 (minimo)", h.Get("Retry-After"))
}
})
t.Run("no incluye Retry-After cuando esta permitido", func(t *testing.T) {
result := RateLimitResult{Allowed: true, Remaining: 5, ResetAt: now}
h := RateLimitHeaders(result, 10)
if h.Get("Retry-After") != "" {
t.Errorf("Retry-After=%q, want vacio", h.Get("Retry-After"))
}
})
}