Files
descrybe/apps/api/internal/httpapi/login_lockout_test.go
T
greeneclipse 8580c996c3 Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
2026-08-09 22:47:43 +02:00

92 lines
2.4 KiB
Go

package httpapi
import (
"testing"
"time"
)
func TestLoginAttemptLockoutLocksAfterMaxFails(t *testing.T) {
t.Parallel()
l := newLoginAttemptLockout(3, 100*time.Millisecond)
email := "Victim@Example.com"
for i := 0; i < 2; i++ {
l.recordFailure(email)
if locked, _ := l.locked(email); locked {
t.Fatalf("unexpected lock after %d failures", i+1)
}
}
l.recordFailure(email)
locked, retry := l.locked("victim@example.com")
if !locked {
t.Fatal("expected lock after max failures")
}
if retry < 1 {
t.Fatalf("retry-after want >=1 got %d", retry)
}
// Case-normalized key: different casing still locked.
if locked2, _ := l.locked("VICTIM@EXAMPLE.COM"); !locked2 {
t.Fatal("expected lock for normalized email")
}
// Other emails are independent.
if locked3, _ := l.locked("other@example.com"); locked3 {
t.Fatal("other email should not be locked")
}
}
func TestLoginAttemptLockoutClearOnSuccess(t *testing.T) {
t.Parallel()
l := newLoginAttemptLockout(2, time.Minute)
email := "user@example.com"
l.recordFailure(email)
l.clear(email)
if locked, _ := l.locked(email); locked {
t.Fatal("clear should remove lock state")
}
l.recordFailure(email)
if locked, _ := l.locked(email); locked {
t.Fatal("one failure after clear should not lock (max=2)")
}
}
func TestLoginAttemptLockoutExpires(t *testing.T) {
t.Parallel()
l := newLoginAttemptLockout(1, 30*time.Millisecond)
email := "temp@example.com"
l.recordFailure(email)
if locked, _ := l.locked(email); !locked {
t.Fatal("expected immediate lock at maxFails=1")
}
time.Sleep(45 * time.Millisecond)
if locked, _ := l.locked(email); locked {
t.Fatal("expected lock to expire")
}
}
func TestLoginAttemptLockoutIgnoresEmptyEmail(t *testing.T) {
t.Parallel()
l := newLoginAttemptLockout(1, time.Minute)
l.recordFailure(" ")
if locked, _ := l.locked(" "); locked {
t.Fatal("empty email must not lock")
}
}
func TestServerLoginAttemptsLazyInit(t *testing.T) {
t.Parallel()
s := &Server{}
a := s.loginAttempts()
b := s.loginAttempts()
if a == nil || a != b {
t.Fatal("loginAttempts should lazy-init once")
}
a.recordFailure("a@example.com")
a.recordFailure("a@example.com")
a.recordFailure("a@example.com")
a.recordFailure("a@example.com")
a.recordFailure("a@example.com")
if locked, _ := b.locked("a@example.com"); !locked {
t.Fatal("shared lockout state expected on Server")
}
}