122 lines
3.8 KiB
Go
122 lines
3.8 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestSecurityReadWrite(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setup func(t *testing.T, svc *SettingService)
|
|
want SecuritySettings
|
|
}{
|
|
{
|
|
name: "键全部缺省回退默认",
|
|
setup: func(t *testing.T, svc *SettingService) {},
|
|
want: securityDefaults,
|
|
},
|
|
{
|
|
name: "写入后读回",
|
|
setup: func(t *testing.T, svc *SettingService) {
|
|
in := SecuritySettings{LoginFailLimit: 10, LoginLockMinutes: 30, IPRateRPS: 20, IPRateBurst: 60, RealIPHeader: "X-Client-IP", AppURL: "https://demo.example.com/"}
|
|
if err := svc.UpdateSecurity(context.Background(), in); err != nil {
|
|
t.Fatalf("UpdateSecurity: %v", err)
|
|
}
|
|
},
|
|
want: SecuritySettings{LoginFailLimit: 10, LoginLockMinutes: 30, IPRateRPS: 20, IPRateBurst: 60, RealIPHeader: "X-Client-IP", AppURL: "https://demo.example.com"},
|
|
},
|
|
{
|
|
name: "脏数据按字段回退默认",
|
|
setup: func(t *testing.T, svc *SettingService) {
|
|
for k, v := range map[string]string{
|
|
settingSecLoginFailLimit: "abc",
|
|
settingSecIPRateRPS: "9999",
|
|
settingSecRealIPHeader: "X Evil Header\r\n",
|
|
} {
|
|
if err := svc.set(context.Background(), k, v); err != nil {
|
|
t.Fatalf("set: %v", err)
|
|
}
|
|
}
|
|
},
|
|
want: securityDefaults,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
svc, _ := newSettingEnv(t)
|
|
tt.setup(t, svc)
|
|
got, err := svc.Security(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("Security: %v", err)
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("settings = %+v, want %+v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateSecurityRejectsInvalid(t *testing.T) {
|
|
base := securityDefaults
|
|
tests := []struct {
|
|
name string
|
|
mutate func(*SecuritySettings)
|
|
}{
|
|
{name: "失败阈值越界", mutate: func(s *SecuritySettings) { s.LoginFailLimit = 0 }},
|
|
{name: "锁定时长越界", mutate: func(s *SecuritySettings) { s.LoginLockMinutes = 1441 }},
|
|
{name: "限速越界", mutate: func(s *SecuritySettings) { s.IPRateRPS = 101 }},
|
|
{name: "突发越界", mutate: func(s *SecuritySettings) { s.IPRateBurst = 0 }},
|
|
{name: "请求头含非法字符", mutate: func(s *SecuritySettings) { s.RealIPHeader = "X Custom;" }},
|
|
{name: "面板地址缺协议", mutate: func(s *SecuritySettings) { s.AppURL = "demo.example.com" }},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
svc, _ := newSettingEnv(t)
|
|
in := base
|
|
tt.mutate(&in)
|
|
if err := svc.UpdateSecurity(context.Background(), in); !errors.Is(err, ErrInvalidSecurity) {
|
|
t.Fatalf("err = %v, want ErrInvalidSecurity", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSecurityCachedSnapshot(t *testing.T) {
|
|
svc, _ := newSettingEnv(t)
|
|
// 未初始化时返回默认,不触库
|
|
if got := svc.SecurityCached(); got != securityDefaults {
|
|
t.Errorf("cold cache = %+v, want defaults", got)
|
|
}
|
|
in := securityDefaults
|
|
in.LoginFailLimit = 8
|
|
if err := svc.UpdateSecurity(context.Background(), in); err != nil {
|
|
t.Fatalf("UpdateSecurity: %v", err)
|
|
}
|
|
// Update 即刷新快照
|
|
if got := svc.SecurityCached(); got.LoginFailLimit != 8 {
|
|
t.Errorf("cached limit = %d, want 8", got.LoginFailLimit)
|
|
}
|
|
// nil 容忍
|
|
if got := securityOf(nil); got != securityDefaults {
|
|
t.Errorf("securityOf(nil) = %+v, want defaults", got)
|
|
}
|
|
}
|
|
|
|
func TestEffectiveAppURL(t *testing.T) {
|
|
svc, _ := newSettingEnv(t)
|
|
svc.SetEnvPublicURL("https://env.example.com/")
|
|
if got := svc.EffectiveAppURL(); got != "https://env.example.com" {
|
|
t.Errorf("env fallback = %q", got)
|
|
}
|
|
in := securityDefaults
|
|
in.AppURL = "https://app.example.com"
|
|
if err := svc.UpdateSecurity(context.Background(), in); err != nil {
|
|
t.Fatalf("UpdateSecurity: %v", err)
|
|
}
|
|
if got := svc.EffectiveAppURL(); got != "https://app.example.com" {
|
|
t.Errorf("app_url 应优先, got %q", got)
|
|
}
|
|
}
|