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>
72 lines
2.4 KiB
Go
72 lines
2.4 KiB
Go
package xlgo_test
|
||
|
||
import (
|
||
"errors"
|
||
"testing"
|
||
|
||
xlgo "github.com/EthanCodeCraft/xlgo-core"
|
||
"github.com/EthanCodeCraft/xlgo-core/config"
|
||
"github.com/EthanCodeCraft/xlgo-core/storage"
|
||
)
|
||
|
||
// localStorageCfg 构造一个本地存储配置(tmpdir 由调用方提供)。
|
||
func localStorageCfg(port int, dir string) *config.Config {
|
||
cfg := testConfig(port)
|
||
cfg.Storage = config.StorageConfig{
|
||
Driver: "local",
|
||
Local: config.LocalStorageConfig{Path: dir},
|
||
}
|
||
return cfg
|
||
}
|
||
|
||
// TestAppWithStorageSwapsDefault 固化 Phase 2 契约:App.Init(WithStorage) 必须把 App 的
|
||
// StorageManager 提升为全局默认。证据:Init 前 GetStorage()=nil(空 manager),Init 后非 nil。
|
||
func TestAppWithStorageSwapsDefault(t *testing.T) {
|
||
// 用空 manager 作"Init 前"默认,确保 GetStorage() 起点 nil
|
||
saved := storage.SwapDefaultStorageManager(storage.NewStorageManager())
|
||
defer storage.SwapDefaultStorageManager(saved)
|
||
|
||
if s := storage.GetStorage(); s != nil {
|
||
t.Fatalf("precondition: GetStorage() should be nil, got %T", s)
|
||
}
|
||
|
||
dir := t.TempDir()
|
||
app := xlgo.New(xlgo.WithConfig(localStorageCfg(18094, dir)), xlgo.WithStorage())
|
||
if err := app.Init(); err != nil {
|
||
t.Fatalf("Init: %v", err)
|
||
}
|
||
defer app.Shutdown()
|
||
|
||
if s := storage.GetStorage(); s == nil {
|
||
t.Fatal("storage.GetStorage() = nil after App.Init with WithStorage(未把 App manager 提升为全局默认)")
|
||
}
|
||
}
|
||
|
||
// TestAppStorageRollbackOnInitFailure 固化 Phase 2 回滚契约:Init 在 storage 之后失败时,
|
||
// rollbackReplacedResources 必须把全局默认 swap 回 Init 前的旧 manager。
|
||
// 用 OnInit hook 触发失败(OnInit 在 storage init 之后、commitReplacedResources 之前)。
|
||
func TestAppStorageRollbackOnInitFailure(t *testing.T) {
|
||
saved := storage.SwapDefaultStorageManager(storage.NewStorageManager())
|
||
defer storage.SwapDefaultStorageManager(saved)
|
||
|
||
dir := t.TempDir()
|
||
app := xlgo.New(
|
||
xlgo.WithConfig(localStorageCfg(18095, dir)),
|
||
xlgo.WithStorage(),
|
||
xlgo.WithHook(xlgo.Hook{
|
||
Name: "force-fail",
|
||
OnInit: func(*xlgo.App) error { return errors.New("boom") },
|
||
}),
|
||
)
|
||
|
||
if err := app.Init(); err == nil {
|
||
_ = app.Shutdown()
|
||
t.Fatal("expected Init to fail via OnInit hook")
|
||
}
|
||
|
||
// 回滚后全局默认应恢复为 saved(空 manager)-> GetStorage() 返回 nil
|
||
if s := storage.GetStorage(); s != nil {
|
||
t.Errorf("Init 失败回滚未恢复 storage 全局默认: GetStorage()=%T", s)
|
||
}
|
||
}
|