package authcommon import ( "testing" "time" ) func TestAttemptLimiterBlocksUntilWindowExpiresAndCanReset(t *testing.T) { now := time.Date(2026, 7, 26, 1, 2, 3, 0, time.UTC) limiter, err := newAttemptLimiter( 2, time.Minute, 10, func() time.Time { return now }, ) if err != nil { t.Fatalf("newAttemptLimiter() error = %v", err) } if allowed, _ := limiter.Allow("admin:127.0.0.1"); !allowed { t.Fatal("first attempt was blocked") } if allowed, _ := limiter.Allow("admin:127.0.0.1"); !allowed { t.Fatal("second attempt was blocked") } if allowed, wait := limiter.Allow("admin:127.0.0.1"); allowed || wait != time.Minute { t.Fatalf("third attempt = %v, wait = %v", allowed, wait) } limiter.Reset("admin:127.0.0.1") if allowed, _ := limiter.Allow("admin:127.0.0.1"); !allowed { t.Fatal("attempt after reset was blocked") } now = now.Add(time.Minute) if allowed, _ := limiter.Allow("admin:127.0.0.1"); !allowed { t.Fatal("attempt after window expiry was blocked") } } func TestAttemptLimiterBoundsTrackedKeys(t *testing.T) { now := time.Date(2026, 7, 26, 1, 2, 3, 0, time.UTC) limiter, err := newAttemptLimiter( 1, time.Minute, 1, func() time.Time { return now }, ) if err != nil { t.Fatalf("newAttemptLimiter() error = %v", err) } if allowed, _ := limiter.Allow("one"); !allowed { t.Fatal("first key was blocked") } if allowed, wait := limiter.Allow("two"); allowed || wait != time.Minute { t.Fatalf("second key = %v, wait = %v", allowed, wait) } now = now.Add(time.Minute) if allowed, _ := limiter.Allow("two"); !allowed { t.Fatal("expired key was not evicted") } } func TestLoginAttemptKeyUsesRemoteAddressOnly(t *testing.T) { if got := LoginAttemptKey("buyer", "127.0.0.1:1234"); got != "buyer:127.0.0.1" { t.Fatalf("IPv4 key = %q", got) } if got := LoginAttemptKey("admin", "[2001:db8::1]:443"); got != "admin:2001:db8::1" { t.Fatalf("IPv6 key = %q", got) } if got := RetryAfterSeconds(time.Millisecond); got != 1 { t.Fatalf("RetryAfterSeconds() = %d", got) } }