Files
xlgo-core/app_cache_test.go
2026-07-14 10:24:10 +08:00

132 lines
4.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package xlgo_test
import (
"context"
"errors"
"net"
"strconv"
"testing"
"time"
xlgo "github.com/EthanCodeCraft/xlgo-core"
"github.com/EthanCodeCraft/xlgo-core/cache"
"github.com/EthanCodeCraft/xlgo-core/config"
"github.com/alicebob/miniredis/v2"
)
func splitAddr(t *testing.T, addr string) (string, int) {
t.Helper()
host, portStr, err := net.SplitHostPort(addr)
if err != nil {
t.Fatalf("SplitHostPort %q: %v", addr, err)
}
port, err := strconv.Atoi(portStr)
if err != nil {
t.Fatalf("Atoi port %q: %v", portStr, err)
}
if host == "" {
host = "127.0.0.1"
}
return host, port
}
// TestAppCacheMultiAppRedisIsolation 固化 Phase 4 核心契约:单进程多 App 时,每个 App 的
// 缓存走自己的 redisManager.Client(),互不串越。修复前 cache 硬编码 database.GetRedis()
// (全局),App B swap 后 App A 的缓存会读到 App B 的 redis。
//
// 用两个 miniredis 实例分别作 App A/B 的 Redis 后端,断言各自 cache 只写自己的实例、
// 互读对方的 key 为 miss。appA.Cache()/appB.Cache() 绕过全局 facade 直取 App 实例。
func TestAppCacheMultiAppRedisIsolation(t *testing.T) {
mrA := miniredis.RunT(t)
mrB := miniredis.RunT(t)
hostA, portA := splitAddr(t, mrA.Addr())
hostB, portB := splitAddr(t, mrB.Addr())
cfgA := testConfig(18120)
cfgA.Redis = config.RedisConfig{Host: hostA, Port: portA}
cfgB := testConfig(18121)
cfgB.Redis = config.RedisConfig{Host: hostB, Port: portB}
appA := xlgo.New(xlgo.WithConfig(cfgA), xlgo.WithRedis())
if err := appA.Init(); err != nil {
t.Fatalf("appA Init: %v", err)
}
defer appA.Shutdown()
appB := xlgo.New(xlgo.WithConfig(cfgB), xlgo.WithRedis())
if err := appB.Init(); err != nil {
t.Fatalf("appB Init: %v", err)
}
defer appB.Shutdown()
if appA.Cache() == nil || appB.Cache() == nil {
t.Fatal("app.Cache() = nil after Init with WithRedis")
}
ctx := context.Background()
// A 写 keyA -> 仅落 miniredisA
if err := appA.Cache().Set(ctx, "keyA", "valA", time.Minute); err != nil {
t.Fatalf("appA cache Set: %v", err)
}
if !mrA.Exists("keyA") {
t.Error("keyA not in miniredisA (appA redis)")
}
if mrB.Exists("keyA") {
t.Error("keyA leaked into miniredisB (appB redis) -- cache not isolated")
}
// B 写 keyB -> 仅落 miniredisB
if err := appB.Cache().Set(ctx, "keyB", "valB", time.Minute); err != nil {
t.Fatalf("appB cache Set: %v", err)
}
if !mrB.Exists("keyB") {
t.Error("keyB not in miniredisB (appB redis)")
}
if mrA.Exists("keyB") {
t.Error("keyB leaked into miniredisA (appA redis) -- cache not isolated")
}
// 互读对方 key -> miss(各自走自己的 redis
var got string
if ok, err := appA.Cache().Get(ctx, "keyB", &got); ok || err != nil {
t.Errorf("appA cache should miss keyB (different redis): ok=%v err=%v", ok, err)
}
if ok, err := appB.Cache().Get(ctx, "keyA", &got); ok || err != nil {
t.Errorf("appB cache should miss keyA (different redis): ok=%v err=%v", ok, err)
}
// 各自读自己的 key -> hit
if ok, err := appA.Cache().Get(ctx, "keyA", &got); err != nil || !ok || got != "valA" {
t.Errorf("appA cache Get keyA = %v %q err=%v (want true valA)", ok, got, err)
}
if ok, err := appB.Cache().Get(ctx, "keyB", &got); err != nil || !ok || got != "valB" {
t.Errorf("appB cache Get keyB = %v %q err=%v (want true valB)", ok, got, err)
}
}
// TestAppCacheRollbackOnInitFailure 固化 Phase 4 回滚:Init 失败后全局默认 CacheManager
// 必须恢复为 Init 前的实例。
func TestAppCacheRollbackOnInitFailure(t *testing.T) {
preInit := cache.NewCacheManager()
saved := cache.SwapDefaultCacheManager(preInit) // saved = init 默认;preInit 现为全局
defer cache.SwapDefaultCacheManager(saved)
mr := miniredis.RunT(t)
host, port := splitAddr(t, mr.Addr())
cfg := testConfig(18122)
cfg.Redis = config.RedisConfig{Host: host, Port: port}
app := xlgo.New(
xlgo.WithConfig(cfg),
xlgo.WithRedis(),
xlgo.WithHook(xlgo.Hook{Name: "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")
}
if cache.GetDefaultCache() != preInit {
t.Fatal("rollback did not restore preInit cache manager as global default")
}
}