//go:build test && !dev // +build test,!dev package mw import ( "context" "testing" "time" "crussell/clock" ) // 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") } } // ============================================================ // RateLimiter Cleanup Tests // ============================================================ // TestRateLimiter_Cleanup verifies that stale entries are removed. func TestRateLimiter_Cleanup(t *testing.T) { rl := NewRateLimiter(10, time.Minute) rl.mu.Lock() rl.requests["stale-key"] = []time.Time{clock.Now().Add(-5 * time.Minute)} rl.requests["fresh-key"] = []time.Time{clock.Now()} rl.mu.Unlock() rl.Cleanup() rl.mu.RLock() _, staleExists := rl.requests["stale-key"] _, freshExists := rl.requests["fresh-key"] rl.mu.RUnlock() if staleExists { t.Error("expected stale-key to be removed") } if !freshExists { t.Error("expected fresh-key to be preserved") } } // TestRateLimiter_Cleanup_EmptyMap handles an empty requests map. func TestRateLimiter_Cleanup_EmptyMap(t *testing.T) { rl := NewRateLimiter(10, time.Minute) rl.mu.Lock() rl.requests = make(map[string][]time.Time) rl.mu.Unlock() rl.Cleanup() rl.mu.RLock() count := len(rl.requests) rl.mu.RUnlock() if count != 0 { t.Errorf("expected empty map, got %d entries", count) } } // TestCleanupAllRateLimiters iterates all registered limiters. func TestCleanupAllRateLimiters(t *testing.T) { registeredLimitersMu.Lock() saved := registeredLimiters registeredLimitersMu.Unlock() defer func() { registeredLimitersMu.Lock() registeredLimiters = saved registeredLimitersMu.Unlock() }() rl1 := NewRateLimiter(10, time.Minute) rl2 := NewRateLimiter(20, time.Minute) rl1.mu.Lock() rl1.requests["rl1-stale"] = []time.Time{clock.Now().Add(-5 * time.Minute)} rl1.mu.Unlock() rl2.mu.Lock() rl2.requests["rl2-stale"] = []time.Time{clock.Now().Add(-5 * time.Minute)} rl2.mu.Unlock() _, err := CleanupAllRateLimiters(context.Background()) if err != nil { t.Errorf("expected nil error, got %v", err) } rl1.mu.RLock() _, rl1Stale := rl1.requests["rl1-stale"] rl1.mu.RUnlock() rl2.mu.RLock() _, rl2Stale := rl2.requests["rl2-stale"] rl2.mu.RUnlock() if rl1Stale { t.Error("expected rl1-stale to be removed") } if rl2Stale { t.Error("expected rl2-stale to be removed") } } // TestCleanupAllRateLimiters_Empty does not panic with no registered limiters. func TestCleanupAllRateLimiters_Empty(t *testing.T) { registeredLimitersMu.Lock() saved := registeredLimiters registeredLimiters = nil registeredLimitersMu.Unlock() defer func() { registeredLimitersMu.Lock() registeredLimiters = saved registeredLimitersMu.Unlock() }() _, err := CleanupAllRateLimiters(context.Background()) if err != nil { t.Errorf("expected nil error, got %v", err) } } // TestCleanupProgressiveRateLimiter cleans the global limiter. func TestCleanupProgressiveRateLimiter(t *testing.T) { globalProgressiveLimiter.mu.Lock() saved := globalProgressiveLimiter.requests globalProgressiveLimiter.requests = map[string]*ipProgressiveState{ "global-stale": {timestamps: []time.Time{clock.Now().Add(-120 * time.Second)}}, } globalProgressiveLimiter.mu.Unlock() defer func() { globalProgressiveLimiter.mu.Lock() globalProgressiveLimiter.requests = saved globalProgressiveLimiter.mu.Unlock() }() _, err := CleanupProgressiveRateLimiter(context.Background()) if err != nil { t.Errorf("expected nil error, got %v", err) } globalProgressiveLimiter.mu.RLock() _, exists := globalProgressiveLimiter.requests["global-stale"] globalProgressiveLimiter.mu.RUnlock() if exists { t.Error("expected global-stale to be removed") } }