package infra import ( "testing" "time" ) func TestRateLimiterCheck(t *testing.T) { t.Run("primer request siempre allowed con burst-1 remaining", func(t *testing.T) { rl := RateLimiterNew(10, 20) result := RateLimiterCheck(rl, "client-1") if !result.Allowed { t.Error("primer request rechazado") } if result.Remaining != 19 { t.Errorf("Remaining=%d, want 19", result.Remaining) } if result.RetryAfter != 0 { t.Errorf("RetryAfter=%v, want 0", result.RetryAfter) } }) t.Run("consumir todos los tokens bloquea siguiente request", func(t *testing.T) { rl := RateLimiterNew(1, 5) // Consumir los 5 tokens del burst for i := 0; i < 5; i++ { r := RateLimiterCheck(rl, "client-2") if !r.Allowed { t.Fatalf("request %d rechazado, esperaba allowed", i) } } // El sexto deberia ser rechazado r := RateLimiterCheck(rl, "client-2") if r.Allowed { t.Error("request post-burst permitido, esperaba rechazo") } if r.Remaining != 0 { t.Errorf("Remaining=%d, want 0", r.Remaining) } }) t.Run("los tokens se recargan con el paso del tiempo", func(t *testing.T) { rl := RateLimiterNew(100, 2) // 100 tokens/seg = 1 token cada 10ms // Consumir burst RateLimiterCheck(rl, "client-3") RateLimiterCheck(rl, "client-3") // Tercer request rechazado r := RateLimiterCheck(rl, "client-3") if r.Allowed { t.Fatal("tercer request inmediato deberia estar rechazado") } // Esperar suficiente para recargar al menos 1 token time.Sleep(50 * time.Millisecond) r2 := RateLimiterCheck(rl, "client-3") if !r2.Allowed { t.Error("request tras recarga deberia estar permitido") } }) t.Run("retryAfter es positivo cuando se rechaza", func(t *testing.T) { rl := RateLimiterNew(1, 1) // 1 token/seg, burst 1 RateLimiterCheck(rl, "client-4") r := RateLimiterCheck(rl, "client-4") if r.Allowed { t.Fatal("segundo request inmediato deberia rechazarse") } if r.RetryAfter <= 0 { t.Errorf("RetryAfter=%v, want > 0", r.RetryAfter) } if r.RetryAfter > 1.5 { t.Errorf("RetryAfter=%v, esperado <= 1s aprox", r.RetryAfter) } }) t.Run("keys distintas tienen buckets independientes", func(t *testing.T) { rl := RateLimiterNew(1, 2) // Consumir todo de A RateLimiterCheck(rl, "A") RateLimiterCheck(rl, "A") ra := RateLimiterCheck(rl, "A") if ra.Allowed { t.Fatal("A deberia estar agotado") } // B sigue intacto rb := RateLimiterCheck(rl, "B") if !rb.Allowed { t.Error("B deberia tener tokens propios") } }) }