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.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user