package main import ( "context" "fmt" "testing" "atlas9.dev/c/core/throttle" ) func TestLoginThrottle_AllowsNormalLogin(t *testing.T) { lt := NewLoginThrottle(throttle.NewMemoryBucket()) ctx := context.Background() ok, err := lt.Check(ctx, "1.2.3.4", "user@example.com") if err != nil { t.Fatal(err) } if !ok { t.Error("normal login should be allowed") } } func TestLoginThrottle_BlocksAfterEmailExhausted(t *testing.T) { lt := NewLoginThrottle(throttle.NewMemoryBucket()) lt.EmailRefillRate = 0 // disable refill for test ctx := context.Background() for i := 0; i < lt.EmailCapacity; i++ { ok, err := lt.Check(ctx, "1.2.3.4", "user@example.com") if err != nil { t.Fatal(err) } if !ok { t.Errorf("attempt %d should be allowed", i+1) } } ok, err := lt.Check(ctx, "1.2.3.4", "user@example.com") if err != nil { t.Fatal(err) } if ok { t.Error("should be denied after email capacity exhausted") } } func TestLoginThrottle_BlocksAfterIPExhausted(t *testing.T) { lt := NewLoginThrottle(throttle.NewMemoryBucket()) lt.IPRefillRate = 0 // disable refill for test ctx := context.Background() for i := 0; i < lt.IPCapacity; i++ { email := fmt.Sprintf("user%d@example.com", i) ok, err := lt.Check(ctx, "1.2.3.4", email) if err != nil { t.Fatal(err) } if !ok { t.Errorf("attempt %d should be allowed", i+1) } } ok, err := lt.Check(ctx, "1.2.3.4", "another@example.com") if err != nil { t.Fatal(err) } if ok { t.Error("should be denied after IP capacity exhausted") } } func TestLoginThrottle_ResetEmailAllowsLogin(t *testing.T) { lt := NewLoginThrottle(throttle.NewMemoryBucket()) lt.EmailRefillRate = 0 ctx := context.Background() // Exhaust email bucket. for i := 0; i < lt.EmailCapacity; i++ { lt.Check(ctx, "1.2.3.4", "user@example.com") } // Reset email. lt.ResetEmail(ctx, "user@example.com") ok, err := lt.Check(ctx, "1.2.3.4", "user@example.com") if err != nil { t.Fatal(err) } if !ok { t.Error("should be allowed after reset") } }