Scert/backend/internal/bootstrap/session_test.go

94 lines
3.4 KiB
Go

package bootstrap
import (
"crypto/sha256"
"errors"
"net/http"
"testing"
"time"
)
func TestBootstrapTokenCanOnlyBeExchangedOnce(t *testing.T) {
manager, token, err := NewSessionManager()
if err != nil {
t.Fatal(err)
}
session, err := manager.Exchange(token, "192.0.2.10:49000")
if err != nil {
t.Fatalf("兑换 Bootstrap 一次性令牌失败:%v", err)
}
if session.Token == "" || !manager.Authenticate(session.Token) {
t.Fatal("Bootstrap Cookie 会话未生效")
}
if _, err := manager.Exchange(token, "192.0.2.10:49001"); !errors.Is(err, ErrBootstrapTokenInvalid) {
t.Fatalf("Bootstrap 一次性令牌被重复兑换:%v", err)
}
manager.RevokeAll()
if manager.Authenticate(session.Token) {
t.Fatal("Bootstrap 会话未被全部注销")
}
}
func TestBootstrapSessionExpiryAndSourceRateLimit(t *testing.T) {
manager, _, err := NewSessionManager()
if err != nil {
t.Fatal(err)
}
now := time.Now().UTC()
manager.now = func() time.Time { return now }
expiredToken := "expired-session"
manager.sessions[sha256.Sum256([]byte(expiredToken))] = bootstrapSessionRecord{ExpiresAt: now.Add(-time.Second)}
if manager.Authenticate(expiredToken) {
t.Fatal("过期 Bootstrap 会话仍然有效")
}
for index := 0; index < bootstrapSourceAttemptLimit; index++ {
if _, err := manager.Exchange("wrong-token", "198.51.100.20"); !errors.Is(err, ErrBootstrapTokenInvalid) {
t.Fatalf("错误令牌返回结果不正确:%v", err)
}
}
if _, err := manager.Exchange("wrong-token", "198.51.100.20"); !errors.Is(err, ErrBootstrapRateLimited) {
t.Fatalf("Bootstrap 来源限流未生效:%v", err)
}
manager.now = func() time.Time { return now.Add(bootstrapSourceAttemptWindow + time.Second) }
if _, err := manager.Exchange("wrong-token", "198.51.100.20"); !errors.Is(err, ErrBootstrapTokenInvalid) {
t.Fatalf("Bootstrap 来源限流窗口未恢复:%v", err)
}
}
func TestBootstrapSessionCapacityAndGlobalConcurrencyLimit(t *testing.T) {
manager, token, err := NewSessionManager()
if err != nil {
t.Fatal(err)
}
now := manager.now().UTC()
for index := 0; index < bootstrapSessionCapacity; index++ {
value := sha256.Sum256([]byte{byte(index)})
manager.sessions[value] = bootstrapSessionRecord{ExpiresAt: now.Add(time.Minute)}
}
if _, err := manager.Exchange(token, "203.0.113.12"); !errors.Is(err, ErrBootstrapSessionFull) {
t.Fatalf("Bootstrap 会话容量限制未生效:%v", err)
}
manager.sessions = make(map[[sha256.Size]byte]bootstrapSessionRecord)
manager.inFlight = bootstrapExchangeInFlightLimit
if _, err := manager.Exchange(token, "203.0.113.13"); !errors.Is(err, ErrBootstrapBusy) {
t.Fatalf("Bootstrap 全局并发限制未生效:%v", err)
}
}
func TestBootstrapCookieSecurityAttributes(t *testing.T) {
expiresAt := time.Now().UTC().Add(10 * time.Minute)
cookie := BootstrapSessionCookie("session-token", expiresAt)
if cookie.Name != SessionCookieName || cookie.Value != "session-token" {
t.Fatalf("Bootstrap Cookie 名称或值错误:%+v", cookie)
}
if cookie.Path != "/api/v1/bootstrap" || !cookie.Secure || !cookie.HttpOnly || cookie.SameSite != http.SameSiteStrictMode {
t.Fatalf("Bootstrap Cookie 安全属性不完整:%+v", cookie)
}
cleared := ClearBootstrapSessionCookie()
if cleared.MaxAge != -1 || cleared.Value != "" || !cleared.Secure || !cleared.HttpOnly || cleared.SameSite != http.SameSiteStrictMode {
t.Fatalf("Bootstrap Cookie 清理属性不正确:%+v", cleared)
}
}