package manager import ( "context" "encoding/json" "fmt" "os" "testing" "time" "wallet-pay-api/common/global" "wallet-pay-api/common/model" "wallet-pay-api/pkg/loggerx" "github.com/redis/go-redis/v9" ) func newTestRedisCache() *CacheRedis { // 支持通过环境变量覆盖 redis 地址 addr := os.Getenv("TEST_REDIS_ADDR") if addr == "" { addr = "10.40.92.54:30379" } client := redis.NewClient(&redis.Options{ Addr: addr, Password: "123000", DB: 0, }) return NewCacheRedis(context.Background(), client, "test") } func TestBatchGetRedis(t *testing.T) { ctx := context.Background() cache := newTestRedisCache() keys := []string{"k1", "k2", "k3"} resp, err := BatchGetRedis[model.YuebaoUserAsset](cache, ctx, keys) if err != nil { t.Fatal(err) } t.Logf("%+v", resp) } func TestCacheRedis_Types(t *testing.T) { ctx := context.Background() cache := newTestRedisCache() // int err := cache.Set(ctx, "int", 123, time.Second*10, []int64{123}) if err != nil { t.Fatal(err) } var i int err = cache.Get(ctx, "int", &i) if err != nil || i != 123 { t.Errorf("int get failed: %v, %d", err, i) } // string err = cache.Set(ctx, "str", "abc", time.Second*10, []int64{124}) if err != nil { t.Fatal(err) } var s string err = cache.Get(ctx, "str", &s) if err != nil || s != "abc" { t.Errorf("string get failed: %v, %s", err, s) } // struct st := TestStruct{Name: "foo"} err = cache.Set(ctx, "struct", st, time.Second*10, []int64{125}) if err != nil { t.Fatal(err) } var st2 TestStruct err = cache.Get(ctx, "struct", &st2) if err != nil || st2.Name != "foo" { t.Errorf("struct get failed: %v, %+v", err, st2) } // map m := map[string]int{"a": 1} err = cache.Set(ctx, "map", m, time.Second*10, []int64{126}) if err != nil { t.Fatal(err) } var m2 map[string]int err = cache.Get(ctx, "map", &m2) if err != nil || m2["a"] != 1 { t.Errorf("map get failed: %v, %+v", err, m2) } // slice sl := []string{"x", "y"} err = cache.Set(ctx, "slice", sl, time.Second*10, []int64{127}) if err != nil { t.Fatal(err) } var sl2 []string err = cache.Get(ctx, "slice", &sl2) if err != nil || len(sl2) != 2 || sl2[0] != "x" { t.Errorf("slice get failed: %v, %+v", err, sl2) } } func TestCacheRedis_Expire(t *testing.T) { ctx := context.Background() cache := newTestRedisCache() err := cache.Set(ctx, "expire", "v", time.Millisecond*200, []int64{128}) if err != nil { t.Fatal(err) } var s string err = cache.Get(ctx, "expire", &s) if err != nil || s != "v" { t.Errorf("expire get failed: %v, %s", err, s) } time.Sleep(time.Millisecond * 250) err = cache.Get(ctx, "expire", &s) if err == nil { t.Errorf("should expire, got: %v", s) } } func TestCacheRedis_Del(t *testing.T) { ctx := context.Background() cache := newTestRedisCache() err := cache.Set(ctx, "del", 1, time.Second*10, []int64{129}) if err != nil { t.Fatal(err) } err = cache.Del(ctx, "del") if err != nil { t.Fatal(err) } var i int err = cache.Get(ctx, "del", &i) if err == nil { t.Errorf("should not get deleted key") } } func TestCacheRedis_TypeMismatch(t *testing.T) { ctx := context.Background() cache := newTestRedisCache() err := cache.Set(ctx, "mismatch", 123, time.Second*10, []int64{123}) if err != nil { t.Fatal(err) } var s string err = cache.Get(ctx, "mismatch", &s) if err == nil { t.Errorf("should type mismatch") } } func TestCacheRedis_BatchGet(t *testing.T) { ctx := context.Background() cache := newTestRedisCache() cache.Set(ctx, "ba", 1, time.Second*10, []int64{1}) cache.Set(ctx, "bb", 2, time.Second*10, []int64{2}) cache.Set(ctx, "bc", 3, time.Second*10, []int64{3}) keys := []string{"bj", "ba", "bb", "bx"} m, err := BatchGetRedis[int](cache, ctx, keys) if err != nil { t.Fatal(err) } b, _ := json.Marshal(m) fmt.Println(string(b)) if len(m) != 2 || *m["ba"] != 1 || *m["bb"] != 2 { t.Errorf("batch get failed: %+v", m) } } func TestCacheManager_Redis_Generic(t *testing.T) { ctx := context.Background() cache := newTestRedisCache() m := NewCacheManager[TestStruct](cache, "pref", func(t *TestStruct) int64 { return 0 }) data := TestStruct{Name: "bar"} err := m.Set(ctx, "k1", &data, time.Second*10, []int64{0}) if err != nil { t.Fatal(err) } got, err := m.Get(ctx, "k1") if err != nil || got.Name != "bar" { t.Errorf("manager redis get failed: %v, %+v", err, got) } err = m.Del(ctx, "k1") if err != nil { t.Fatal(err) } got, err = m.Get(ctx, "k1") if err == nil { t.Errorf("manager redis del failed") } } func TestICacheInterface(t *testing.T) { ctx := context.Background() var cache ICache = NewCacheLocal(time.Second * 10) err := cache.Set(ctx, "iface", "val", time.Second*10, []int64{130}) if err != nil { t.Fatal(err) } var s string err = cache.Get(ctx, "iface", &s) if err != nil || s != "val" { t.Errorf("iface get failed: %v, %s", err, s) } err = cache.Del(ctx, "iface") if err != nil { t.Fatal(err) } err = cache.Get(ctx, "iface", &s) if err == nil { t.Errorf("iface del failed") } } func TestCacheManager_Generic(t *testing.T) { ctx := context.Background() cache := NewCacheLocal(time.Second * 10) m := NewCacheManager[TestStruct](cache, "pref", func(t *TestStruct) int64 { return 0 }) data := TestStruct{Name: "bar"} err := m.Set(ctx, "k1", &data, time.Second*10, []int64{0}) if err != nil { t.Fatal(err) } got, err := m.Get(ctx, "k1") if err != nil || got.Name != "bar" { t.Errorf("manager get failed: %v, %+v", err, got) } err = m.Del(ctx, "k1") if err != nil { t.Fatal(err) } got, err = m.Get(ctx, "k1") if err == nil { t.Errorf("manager del failed") } } func TestCacheLocal_Boundary(t *testing.T) { ctx := context.Background() cache := NewCacheLocal(time.Second * 10) // 空 key err := cache.Set(ctx, "", "v", time.Second*10, []int64{0}) if err != nil { t.Fatal(err) } var s string err = cache.Get(ctx, "", &s) if err != nil || s != "v" { t.Errorf("empty key failed: %v, %s", err, s) } // nil value err = cache.Set(ctx, "nil", nil, time.Second*10, []int64{0}) if err != nil { t.Fatal(err) } var x any err = cache.Get(ctx, "nil", &x) if err != nil || x != nil { t.Errorf("nil value failed: %v, %v", err, x) } // 极短 ttl err = cache.Set(ctx, "short", "v", time.Millisecond*1, []int64{0}) if err != nil { t.Fatal(err) } time.Sleep(time.Millisecond * 5) err = cache.Get(ctx, "short", &s) if err == nil { t.Errorf("short ttl should expire") } // 重复 set err = cache.Set(ctx, "dup", "v1", time.Second*10, []int64{0}) if err != nil { t.Fatal(err) } err = cache.Set(ctx, "dup", "v2", time.Second*10, []int64{0}) if err != nil { t.Fatal(err) } err = cache.Get(ctx, "dup", &s) if err != nil || s != "v2" { t.Errorf("dup set failed: %v, %s", err, s) } // Stop 后行为 cache.Stop() // Stop 后再次 Stop 不应 panic cache.Stop() } func TestCacheLocal_Concurrent(t *testing.T) { ctx := context.Background() cache := NewCacheLocal(time.Second * 10) n := 100 done := make(chan struct{}, n*2) for i := 0; i < n; i++ { go func(i int) { key := "k" + string(rune(i)) cache.Set(ctx, key, i, time.Second*10, []int64{int64(i)}) done <- struct{}{} }(i) go func(i int) { key := "k" + string(rune(i)) var v int cache.Get(ctx, key, &v) done <- struct{}{} }(i) } for i := 0; i < n*2; i++ { <-done } } func TestCacheLocal_Types(t *testing.T) { ctx := context.Background() cache := NewCacheLocal(time.Second * 10) // int err := cache.Set(ctx, "int", 123, time.Second*10, []int64{0}) if err != nil { t.Fatal(err) } var i int err = cache.Get(ctx, "int", &i) if err != nil || i != 123 { t.Errorf("int get failed: %v, %d", err, i) } // string err = cache.Set(ctx, "str", "abc", time.Second*10, []int64{0}) if err != nil { t.Fatal(err) } var s string err = cache.Get(ctx, "str", &s) if err != nil || s != "abc" { t.Errorf("string get failed: %v, %s", err, s) } // struct st := TestStruct{Name: "foo"} err = cache.Set(ctx, "struct", st, time.Second*10, []int64{0}) if err != nil { t.Fatal(err) } var st2 TestStruct err = cache.Get(ctx, "struct", &st2) if err != nil || st2.Name != "foo" { t.Errorf("struct get failed: %v, %+v", err, st2) } // map m := map[string]int{"a": 1} err = cache.Set(ctx, "map", m, time.Second*10, []int64{0}) if err != nil { t.Fatal(err) } var m2 map[string]int err = cache.Get(ctx, "map", &m2) if err != nil || m2["a"] != 1 { t.Errorf("map get failed: %v, %+v", err, m2) } // slice sl := []string{"x", "y"} err = cache.Set(ctx, "slice", sl, time.Second*10, []int64{0}) if err != nil { t.Fatal(err) } var sl2 []string err = cache.Get(ctx, "slice", &sl2) if err != nil || len(sl2) != 2 || sl2[0] != "x" { t.Errorf("slice get failed: %v, %+v", err, sl2) } } func TestCacheLocal_Expire(t *testing.T) { ctx := context.Background() cache := NewCacheLocal(time.Millisecond * 50) err := cache.Set(ctx, "expire", "v", time.Millisecond*100, []int64{0}) if err != nil { t.Fatal(err) } var s string err = cache.Get(ctx, "expire", &s) if err != nil || s != "v" { t.Errorf("expire get failed: %v, %s", err, s) } time.Sleep(time.Millisecond * 120) err = cache.Get(ctx, "expire", &s) if err == nil { t.Errorf("should expire, got: %v", s) } } func TestCacheLocal_Del(t *testing.T) { ctx := context.Background() cache := NewCacheLocal(time.Second * 10) err := cache.Set(ctx, "del", 1, time.Second*10, []int64{0}) if err != nil { t.Fatal(err) } err = cache.Del(ctx, "del") if err != nil { t.Fatal(err) } var i int err = cache.Get(ctx, "del", &i) if err == nil { t.Errorf("should not get deleted key") } } func TestCacheLocal_TypeMismatch(t *testing.T) { ctx := context.Background() cache := NewCacheLocal(time.Second * 10) err := cache.Set(ctx, "mismatch", 123, time.Second*10, []int64{0}) if err != nil { t.Fatal(err) } var s string err = cache.Get(ctx, "mismatch", &s) if err == nil { t.Errorf("should type mismatch") } } func TestCacheLocal_BatchGet(t *testing.T) { ctx := context.Background() cache := NewCacheLocal(time.Second * 10) cache.Set(ctx, "a", 1, time.Second*10, []int64{0}) cache.Set(ctx, "b", 2, time.Second*10, []int64{0}) cache.Set(ctx, "c", 3, time.Second*10, []int64{0}) keys := []string{"a", "b", "x"} m, err := BatchGetLocal[int](cache, ctx, keys) if err != nil { t.Fatal(err) } if len(m) != 2 || *m["a"] != 1 || *m["b"] != 2 { t.Errorf("batch get failed: %+v", m) } } type TestStruct struct { Name string `json:"name"` }