//go:build test && !dev // +build test,!dev package mw import ( "sync" "testing" "time" "crussell/clock" ) // TestProgressiveRateLimiter_SingleRequest verifies no delay for first request. func TestProgressiveRateLimiter_SingleRequest(t *testing.T) { prl := NewProgressiveRateLimiter() delay := prl.Check("192.168.1.1") if delay != 0 { t.Errorf("expected 0 delay for first request, got %d", delay) } } // TestProgressiveRateLimiter_BurstAllowsPageLoad verifies 15 requests in 5s are OK. func TestProgressiveRateLimiter_BurstAllowsPageLoad(t *testing.T) { prl := NewProgressiveRateLimiter() ip := "192.168.1.1" for i := 0; i < 15; i++ { delay := prl.Check(ip) if delay != 0 { t.Errorf("expected 0 delay for request %d (within burst), got %d", i+1, delay) } } } // TestProgressiveRateLimiter_ExcessBurstDelays verifies 35+ requests in 5s get delayed. func TestProgressiveRateLimiter_ExcessBurstDelays(t *testing.T) { prl := NewProgressiveRateLimiter() ip := "192.168.1.1" delayed := false for i := 0; i < 35; i++ { delay := prl.Check(ip) if delay > 0 { delayed = true } } if !delayed { t.Error("expected at least one delay after 35 burst requests") } } // TestProgressiveRateLimiter_SustainedAllowsNormal verifies 30 requests // spread over 60 seconds are not delayed. func TestProgressiveRateLimiter_SustainedAllowsNormal(t *testing.T) { prl := NewProgressiveRateLimiter() ip := "192.168.1.2" for i := 0; i < 30; i++ { prl.mu.Lock() state, exists := prl.requests[ip] if !exists { prl.requests[ip] = &ipProgressiveState{ timestamps: []time.Time{clock.Now().Add(-time.Duration(60-i*2) * time.Second)}, } prl.mu.Unlock() continue } state.timestamps = append(state.timestamps, clock.Now().Add(-time.Duration(60-i*2)*time.Second)) prl.mu.Unlock() } delay := prl.Check(ip) if delay != 0 { t.Errorf("expected 0 delay for 30 sustained requests over 60s, got %d", delay) } } // TestProgressiveRateLimiter_ExcessSustainedDelays verifies 130+ requests // in 60s triggers delay. func TestProgressiveRateLimiter_ExcessSustainedDelays(t *testing.T) { prl := NewProgressiveRateLimiter() ip := "192.168.1.3" now := clock.Now() prl.mu.Lock() state := &ipProgressiveState{timestamps: make([]time.Time, 130)} for i := 0; i < 130; i++ { state.timestamps[i] = now.Add(-time.Duration(60-i/3) * time.Second) } prl.requests[ip] = state prl.mu.Unlock() delay := prl.Check(ip) if delay == 0 { t.Error("expected delay > 0 for 130 sustained requests") } } // TestProgressiveRateLimiter_DifferentIPs verifies rate limiter // tracks IPs independently. func TestProgressiveRateLimiter_DifferentIPs(t *testing.T) { prl := NewProgressiveRateLimiter() for i := 0; i < 40; i++ { prl.Check("10.0.0.1") } delay := prl.Check("10.0.0.2") if delay != 0 { t.Errorf("expected 0 delay for separate IP, got %d", delay) } } // TestProgressiveRateLimiter_CleanupRemovesStaleEntries verifies that // IPs with no activity for 60s are cleaned up. func TestProgressiveRateLimiter_CleanupRemovesStaleEntries(t *testing.T) { prl := NewProgressiveRateLimiter() prl.mu.Lock() prl.requests["stale-ip"] = &ipProgressiveState{ timestamps: []time.Time{clock.Now().Add(-120 * time.Second)}, } prl.mu.Unlock() prl.cleanup() prl.mu.RLock() _, exists := prl.requests["stale-ip"] prl.mu.RUnlock() if exists { t.Error("expected stale IP to be cleaned up") } } var _ = sync.Mutex{}