package middleware import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestLoginAttemptTracker_RecordFailedAttempt(t *testing.T) { tracker := NewLoginAttemptTracker(3, 5*time.Minute, 5*time.Minute) username := "testuser" // First failed attempt locked, remainingTime := tracker.RecordFailedAttempt(username) assert.False(t, locked) assert.Equal(t, time.Duration(0), remainingTime) // Second failed attempt locked, remainingTime = tracker.RecordFailedAttempt(username) assert.False(t, locked) assert.Equal(t, time.Duration(0), remainingTime) // Third failed attempt - should lock locked, remainingTime = tracker.RecordFailedAttempt(username) assert.True(t, locked) assert.Greater(t, remainingTime, time.Duration(0)) // Verify user is locked locked, _ = tracker.IsLocked(username) assert.True(t, locked) // Clear attempts tracker.ClearAttempts(username) // Should no longer be locked locked, _ = tracker.IsLocked(username) assert.False(t, locked) } func TestLoginAttemptTracker_IsLocked(t *testing.T) { tracker := NewLoginAttemptTracker(3, 5*time.Minute, 5*time.Minute) username := "lockeduser" // Record failed attempts up to max for i := 0; i < 3; i++ { tracker.RecordFailedAttempt(username) } // Verify user is locked locked, remainingTime := tracker.IsLocked(username) assert.True(t, locked) assert.Greater(t, remainingTime, time.Duration(0)) // Clear attempts tracker.ClearAttempts(username) // Should no longer be locked locked, remainingTime = tracker.IsLocked(username) assert.False(t, locked) assert.Equal(t, time.Duration(0), remainingTime) } func TestLoginAttemptTracker_ConcurrentAccess(t *testing.T) { tracker := NewLoginAttemptTracker(5, 5*time.Minute, 5*time.Minute) done := make(chan bool, 10) // Concurrent access from multiple goroutines for i := 0; i < 10; i++ { go func(index int) { username := "user" + string(rune('0'+index)) tracker.RecordFailedAttempt(username) tracker.IsLocked(username) tracker.ClearAttempts(username) done <- true }(i) } // Wait for all goroutines for i := 0; i < 10; i++ { <-done } // Should complete without deadlock or race } func TestDeviceRateLimiter_CheckRateLimit(t *testing.T) { limiter := NewDeviceRateLimiter() config := DeviceRateLimitConfig{ SyncRequestsPerMinute: 5, } deviceID := "test-device-123" requestType := "sync" // First 5 requests should succeed for i := 0; i < 5; i++ { allowed := limiter.CheckRateLimit(deviceID, requestType, config) assert.True(t, allowed, "Request %d should be allowed", i+1) } // 6th request should be rate limited allowed := limiter.CheckRateLimit(deviceID, requestType, config) assert.False(t, allowed, "Request 6 should be rate limited") // Get remaining requests remaining := limiter.GetRemainingRequests(deviceID, requestType, config) assert.Equal(t, 0, remaining) } func TestDeviceRateLimiter_DifferentDevices(t *testing.T) { limiter := NewDeviceRateLimiter() config := DeviceRateLimitConfig{ SyncRequestsPerMinute: 2, } // Exhaust limit for device1 for i := 0; i < 2; i++ { limiter.CheckRateLimit("device1", "sync", config) } // Device1 should be rate limited allowed := limiter.CheckRateLimit("device1", "sync", config) assert.False(t, allowed) // Device2 should still work allowed = limiter.CheckRateLimit("device2", "sync", config) assert.True(t, allowed) } func TestDeviceRateLimiter_GetRemainingRequests(t *testing.T) { limiter := NewDeviceRateLimiter() config := DeviceRateLimitConfig{ SyncRequestsPerMinute: 10, } deviceID := "test-device-456" // Initially should have all requests remaining remaining := limiter.GetRemainingRequests(deviceID, "sync", config) assert.Equal(t, 10, remaining) // Use 3 requests for i := 0; i < 3; i++ { limiter.CheckRateLimit(deviceID, "sync", config) } // Should have 7 remaining remaining = limiter.GetRemainingRequests(deviceID, "sync", config) assert.Equal(t, 7, remaining) } func TestNewRateLimiter(t *testing.T) { config := RateLimiterConfig{ Enabled: true, RequestsPerMinute: 60, CleanupInterval: time.Minute, } limiter := NewRateLimiter(config) assert.NotNil(t, limiter) }