go-proxy/ratelimit_test.go

116 lines
3.5 KiB
Go
Raw Permalink Normal View History

package proxy
import (
"testing"
"time"
)
// TestRateLimiter_Allow_Good verifies the first N calls within budget are allowed.
//
// limiter := proxy.NewRateLimiter(proxy.RateLimit{MaxConnectionsPerMinute: 10})
// limiter.Allow("1.2.3.4:3333") // true (first 10 calls)
func TestRateLimiter_Allow_Good(t *testing.T) {
rl := NewRateLimiter(RateLimit{MaxConnectionsPerMinute: 10, BanDurationSeconds: 60})
for i := 0; i < 10; i++ {
if !rl.Allow("1.2.3.4:3333") {
t.Fatalf("expected call %d to be allowed", i+1)
}
}
}
// TestRateLimiter_Allow_Bad verifies the 11th call fails when budget is 10/min.
//
// limiter := proxy.NewRateLimiter(proxy.RateLimit{MaxConnectionsPerMinute: 10})
// // calls 1-10 pass, call 11 fails
func TestRateLimiter_Allow_Bad(t *testing.T) {
rl := NewRateLimiter(RateLimit{MaxConnectionsPerMinute: 10, BanDurationSeconds: 60})
for i := 0; i < 10; i++ {
rl.Allow("1.2.3.4:3333")
}
if rl.Allow("1.2.3.4:3333") {
t.Fatalf("expected 11th call to be rejected")
}
}
// TestRateLimiter_Allow_Ugly verifies a banned IP stays banned for BanDurationSeconds.
//
// limiter := proxy.NewRateLimiter(proxy.RateLimit{MaxConnectionsPerMinute: 1, BanDurationSeconds: 300})
// limiter.Allow("1.2.3.4:3333") // true (exhausts budget)
// limiter.Allow("1.2.3.4:3333") // false (banned for 300 seconds)
func TestRateLimiter_Allow_Ugly(t *testing.T) {
rl := NewRateLimiter(RateLimit{MaxConnectionsPerMinute: 1, BanDurationSeconds: 300})
if !rl.Allow("1.2.3.4:3333") {
t.Fatalf("expected first call to pass")
}
if rl.Allow("1.2.3.4:3333") {
t.Fatalf("expected second call to fail")
}
// Verify the IP is still banned even with a fresh bucket
rl.mu.Lock()
rl.bucketByHost["1.2.3.4"] = &tokenBucket{tokens: 100, lastRefill: time.Now()}
rl.mu.Unlock()
if rl.Allow("1.2.3.4:3333") {
t.Fatalf("expected banned IP to remain banned regardless of fresh bucket")
}
}
// TestRateLimiter_Tick_Good verifies Tick removes expired bans.
//
// limiter := proxy.NewRateLimiter(proxy.RateLimit{MaxConnectionsPerMinute: 1, BanDurationSeconds: 1})
// limiter.Tick()
func TestRateLimiter_Tick_Good(t *testing.T) {
rl := NewRateLimiter(RateLimit{MaxConnectionsPerMinute: 1, BanDurationSeconds: 1})
rl.Allow("1.2.3.4:3333")
rl.Allow("1.2.3.4:3333") // triggers ban
// Simulate expired ban
rl.mu.Lock()
rl.banUntilByHost["1.2.3.4"] = time.Now().Add(-time.Second)
rl.mu.Unlock()
rl.Tick()
rl.mu.Lock()
_, banned := rl.banUntilByHost["1.2.3.4"]
rl.mu.Unlock()
if banned {
t.Fatalf("expected expired ban to be removed by Tick")
}
}
// TestRateLimiter_Allow_ReplenishesHighLimits verifies token replenishment at high rates.
//
// limiter := proxy.NewRateLimiter(proxy.RateLimit{MaxConnectionsPerMinute: 120})
func TestRateLimiter_Allow_ReplenishesHighLimits(t *testing.T) {
rl := NewRateLimiter(RateLimit{MaxConnectionsPerMinute: 120, BanDurationSeconds: 1})
rl.mu.Lock()
rl.bucketByHost["1.2.3.4"] = &tokenBucket{
tokens: 0,
lastRefill: time.Now().Add(-30 * time.Second),
}
rl.mu.Unlock()
if !rl.Allow("1.2.3.4:1234") {
t.Fatalf("expected bucket to replenish at 120/min")
}
}
// TestRateLimiter_Disabled_Good verifies a zero-budget limiter allows all connections.
//
// limiter := proxy.NewRateLimiter(proxy.RateLimit{MaxConnectionsPerMinute: 0})
// limiter.Allow("any-ip") // always true
func TestRateLimiter_Disabled_Good(t *testing.T) {
rl := NewRateLimiter(RateLimit{MaxConnectionsPerMinute: 0})
for i := 0; i < 100; i++ {
if !rl.Allow("1.2.3.4:3333") {
t.Fatalf("expected disabled limiter to allow all connections")
}
}
}