@@ -6,6 +6,18 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// patchOf 把完整设置转为全字段补丁,等价旧全量保存,供既有用例复用。
|
||||
func patchOf(in SecuritySettings) SecurityPatch {
|
||||
return SecurityPatch{
|
||||
LoginFailLimit: &in.LoginFailLimit,
|
||||
LoginLockMinutes: &in.LoginLockMinutes,
|
||||
IPRateRPS: &in.IPRateRPS,
|
||||
IPRateBurst: &in.IPRateBurst,
|
||||
RealIPHeader: &in.RealIPHeader,
|
||||
AppURL: &in.AppURL,
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecurityReadWrite(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -21,7 +33,7 @@ func TestSecurityReadWrite(t *testing.T) {
|
||||
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 {
|
||||
if err := svc.UpdateSecurity(context.Background(), patchOf(in)); err != nil {
|
||||
t.Fatalf("UpdateSecurity: %v", err)
|
||||
}
|
||||
},
|
||||
@@ -76,7 +88,7 @@ func TestUpdateSecurityRejectsInvalid(t *testing.T) {
|
||||
svc, _ := newSettingEnv(t)
|
||||
in := base
|
||||
tt.mutate(&in)
|
||||
if err := svc.UpdateSecurity(context.Background(), in); !errors.Is(err, ErrInvalidSecurity) {
|
||||
if err := svc.UpdateSecurity(context.Background(), patchOf(in)); !errors.Is(err, ErrInvalidSecurity) {
|
||||
t.Fatalf("err = %v, want ErrInvalidSecurity", err)
|
||||
}
|
||||
})
|
||||
@@ -89,9 +101,7 @@ func TestSecurityCachedSnapshot(t *testing.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 {
|
||||
if err := svc.UpdateSecurity(context.Background(), SecurityPatch{LoginFailLimit: intPtr(8)}); err != nil {
|
||||
t.Fatalf("UpdateSecurity: %v", err)
|
||||
}
|
||||
// Update 即刷新快照
|
||||
@@ -110,12 +120,67 @@ func TestEffectiveAppURL(t *testing.T) {
|
||||
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 {
|
||||
if err := svc.UpdateSecurity(context.Background(), SecurityPatch{AppURL: strPtr("https://app.example.com")}); err != nil {
|
||||
t.Fatalf("UpdateSecurity: %v", err)
|
||||
}
|
||||
if got := svc.EffectiveAppURL(); got != "https://app.example.com" {
|
||||
t.Errorf("app_url 应优先, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateSecurityPartialPatch 锁定 PATCH 语义:只写出现字段,其余不回滚。
|
||||
func TestUpdateSecurityPartialPatch(t *testing.T) {
|
||||
seeded := SecuritySettings{LoginFailLimit: 10, LoginLockMinutes: 30, IPRateRPS: 20, IPRateBurst: 60, RealIPHeader: "X-Client-IP", AppURL: "https://a.example.com"}
|
||||
tests := []struct {
|
||||
name string
|
||||
patch SecurityPatch
|
||||
want SecuritySettings
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "单字段更新不动其余",
|
||||
patch: SecurityPatch{LoginFailLimit: intPtr(3)},
|
||||
want: func() SecuritySettings {
|
||||
w := seeded
|
||||
w.LoginFailLimit = 3
|
||||
return w
|
||||
}(),
|
||||
},
|
||||
{name: "空补丁不改任何值", patch: SecurityPatch{}, want: seeded},
|
||||
{
|
||||
name: "补丁字段规范化(AppURL 去尾斜杠)",
|
||||
patch: SecurityPatch{AppURL: strPtr("https://b.example.com/")},
|
||||
want: func() SecuritySettings {
|
||||
w := seeded
|
||||
w.AppURL = "https://b.example.com"
|
||||
return w
|
||||
}(),
|
||||
},
|
||||
{name: "补丁字段越界拒绝", patch: SecurityPatch{IPRateRPS: intPtr(0)}, wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
svc, _ := newSettingEnv(t)
|
||||
if err := svc.UpdateSecurity(context.Background(), patchOf(seeded)); err != nil {
|
||||
t.Fatalf("seed: %v", err)
|
||||
}
|
||||
err := svc.UpdateSecurity(context.Background(), tt.patch)
|
||||
if tt.wantErr {
|
||||
if !errors.Is(err, ErrInvalidSecurity) {
|
||||
t.Fatalf("err = %v, want ErrInvalidSecurity", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateSecurity: %v", err)
|
||||
}
|
||||
got, err := svc.Security(context.Background())
|
||||
if err != nil || got != tt.want {
|
||||
t.Errorf("settings = %+v (%v), want %+v", got, err, tt.want)
|
||||
}
|
||||
if cached := svc.SecurityCached(); cached != tt.want {
|
||||
t.Errorf("cached = %+v, want %+v", cached, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user