96ce883d41
完成 instance+facade 一致性收尾,闭合三个原始问题: - cron/storage/cache/ratelimit 实例化(App 持 per-App 实例 + SwapDefault* 提升全局默认 + 包级 facade 代理,照 database.Manager/jwt.Manager 模式),消除单进程多 App 互相污染。 - trace 纳入 App 生命周期(WithTrace + doInit trace.Init + doShutdown trace.Close + config.TraceConfig + 自动装 Middleware)。不做实例隔离(OTel 进程全局)。 - 删除 WithoutWire 空函数技术债。 附带修复: - cache/ratelimit redis 注入(jwt 模型:注入优先、database.GetRedis() 兜底)。 - commitReplacedResources 不再关闭 previousDB/previousRedis/loggerSnapshot (多 App 下 previous 可能是另一 App 的活跃资源,关闭致 redis: client is closed)。 - app.RateLimitRegistry() + App-bound 限流中间件(多 App per-App 计数隔离)。 - StorageManager.Close() 闭合 Init/Close 对称性(io.Closer 断言,预留扩展点), closeResources 与 rollbackReplacedResources 对称收口。 新增公开 API:WithTrace / WithCronTask;app.Scheduler()/Cache()/RedisClient()/ RateLimitRegistry();cron/storage/cache/middleware 各 SwapDefault*;config.TraceConfig; middleware.RateLimitRegistry + App-bound 方法;middleware.WithRedisClient; storage.StorageManager.Close。 Breaking:删 WithoutWire;cron.AddTask pre-Init 注册失效(改用 WithCronTask); middleware.StopRateLimiters 语义收窄;commitReplacedResources 不再关 previous; cache.NewRedisCacheWithRedis / CacheManager.InitWithRedis 等签名变更。 详见 CHANGELOG [1.4.0] 升级说明。 验证:go build + go vet + go test -race ./... 全绿(-buildvcs=false)。 独立对抗性复审无 CRITICAL/HIGH。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
82 lines
2.1 KiB
Go
82 lines
2.1 KiB
Go
package config_test
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/EthanCodeCraft/xlgo-core/config"
|
|
)
|
|
|
|
func validBase() *config.Config {
|
|
return &config.Config{
|
|
Server: config.ServerConfig{Port: 8080},
|
|
}
|
|
}
|
|
|
|
func TestValidateOK(t *testing.T) {
|
|
if err := validBase().Validate(); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateServerPort(t *testing.T) {
|
|
c := validBase()
|
|
c.Server.Port = 99999
|
|
if err := c.Validate(); err == nil || !strings.Contains(err.Error(), "server.port") {
|
|
t.Fatalf("expected server.port error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateJWTSecretTooShort(t *testing.T) {
|
|
c := validBase()
|
|
c.JWT.Secret = "short"
|
|
if err := c.Validate(); err == nil || !strings.Contains(err.Error(), "jwt.secret") {
|
|
t.Fatalf("expected jwt.secret error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateJWTSecretAbsentSkipped(t *testing.T) {
|
|
c := validBase()
|
|
// secret 为空时不校验(未启用 jwt)
|
|
if err := c.Validate(); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateTraceSampleRatioWhenEnabled(t *testing.T) {
|
|
c := validBase()
|
|
c.Trace.Enabled = true
|
|
c.Trace.SampleRatio = 1.5
|
|
if err := c.Validate(); err == nil || !strings.Contains(err.Error(), "trace.sample_ratio") {
|
|
t.Fatalf("expected trace.sample_ratio error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateTraceDisabledSkipsRatio(t *testing.T) {
|
|
c := validBase()
|
|
// Enabled=false 时即使 SampleRatio 非法也不校验(trace 未启用)
|
|
c.Trace.SampleRatio = 1.5
|
|
if err := c.Validate(); err != nil {
|
|
t.Fatalf("unexpected error when trace disabled: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateDatabaseMissingHost(t *testing.T) {
|
|
c := validBase()
|
|
c.Database.Driver = "mysql"
|
|
c.Database.Port = 3306
|
|
c.Database.Name = "db"
|
|
// Host 留空
|
|
if err := c.Validate(); err == nil || !strings.Contains(err.Error(), "database.host") {
|
|
t.Fatalf("expected database.host error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateTLSMissingCert(t *testing.T) {
|
|
c := validBase()
|
|
c.Server.TLS.Enabled = true
|
|
if err := c.Validate(); err == nil || !strings.Contains(err.Error(), "cert_file") {
|
|
t.Fatalf("expected tls cert_file error, got %v", err)
|
|
}
|
|
}
|