4009214cbc
CI / docker (push) Successful in 7m4s
引入用户端对话/生图与管理端 Provider 配置,统一 invoke 编排、Gateway、请求日志与模型管理;前端按设计规范拆分布局,Go module 更名为 github.com/rose_cat707/Atlas;生图长任务 SSE 推送;管理端登录防爆破;协议与服务类型联动;Replicate 模型同步修复;用户端创作台 UI 重构;管理端创作历史替代请求日志。 Co-authored-by: Cursor <cursoragent@cursor.com>
51 lines
1.0 KiB
Go
51 lines
1.0 KiB
Go
package api
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestLoginGuardLockout(t *testing.T) {
|
|
g := newLoginGuard()
|
|
ip := "203.0.113.1"
|
|
|
|
for i := 0; i < loginMaxFailures-1; i++ {
|
|
locked, _ := g.recordFailure(ip)
|
|
if locked {
|
|
t.Fatalf("unexpected lock on attempt %d", i+1)
|
|
}
|
|
}
|
|
if locked, _ := g.check(ip); locked {
|
|
t.Fatal("should not be locked before threshold")
|
|
}
|
|
|
|
locked, retryAfter := g.recordFailure(ip)
|
|
if !locked || retryAfter <= 0 {
|
|
t.Fatalf("expected lockout, locked=%v retryAfter=%d", locked, retryAfter)
|
|
}
|
|
if locked, _ := g.check(ip); !locked {
|
|
t.Fatal("expected locked after threshold")
|
|
}
|
|
|
|
g.reset(ip)
|
|
if locked, _ := g.check(ip); locked {
|
|
t.Fatal("expected unlocked after reset")
|
|
}
|
|
}
|
|
|
|
func TestLoginGuardLockExpires(t *testing.T) {
|
|
g := newLoginGuard()
|
|
ip := "203.0.113.2"
|
|
|
|
g.mu.Lock()
|
|
g.byIP[ip] = &loginGuardEntry{
|
|
failures: loginMaxFailures,
|
|
lockedUntil: time.Now().Add(-time.Second),
|
|
}
|
|
g.mu.Unlock()
|
|
|
|
if locked, _ := g.check(ip); locked {
|
|
t.Fatal("expected expired lock to clear")
|
|
}
|
|
}
|