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) } }