package database import ( "context" "testing" "time" pkgRedis "carrotskin/pkg/redis" miniredis "github.com/alicebob/miniredis/v2" goRedis "github.com/redis/go-redis/v9" ) func newCacheWithMiniRedis(t *testing.T) (*CacheManager, func()) { t.Helper() mr, err := miniredis.Run() if err != nil { t.Fatalf("failed to start miniredis: %v", err) } rdb := goRedis.NewClient(&goRedis.Options{ Addr: mr.Addr(), }) client := &pkgRedis.Client{Client: rdb} cache := NewCacheManager(client, CacheConfig{ Prefix: "t:", Expiration: time.Minute, Enabled: true, Policy: CachePolicy{ UserTTL: 2 * time.Minute, UserEmailTTL: 3 * time.Minute, ProfileTTL: 2 * time.Minute, ProfileListTTL: 90 * time.Second, TextureTTL: 2 * time.Minute, TextureListTTL: 45 * time.Second, }, }) cleanup := func() { _ = rdb.Close() mr.Close() } return cache, cleanup } func TestCacheManager_GetSet_TryGet(t *testing.T) { cache, cleanup := newCacheWithMiniRedis(t) defer cleanup() ctx := context.Background() type User struct { ID int Name string } u := User{ID: 1, Name: "alice"} if err := cache.Set(ctx, "user:1", u, 10*time.Second); err != nil { t.Fatalf("Set err: %v", err) } var got User if err := cache.Get(ctx, "user:1", &got); err != nil { t.Fatalf("Get err: %v", err) } if got != u { t.Fatalf("unexpected value: %+v", got) } var got2 User ok, err := cache.TryGet(ctx, "user:1", &got2) if err != nil || !ok { t.Fatalf("TryGet failed, ok=%v err=%v", ok, err) } if got2 != u { t.Fatalf("unexpected TryGet: %+v", got2) } } func TestCacheManager_DeletePattern(t *testing.T) { cache, cleanup := newCacheWithMiniRedis(t) defer cleanup() ctx := context.Background() _ = cache.Set(ctx, "user:1", "a", 0) _ = cache.Set(ctx, "user:2", "b", 0) _ = cache.Set(ctx, "profile:1", "c", 0) // 删除 user:* 键 if err := cache.DeletePattern(ctx, "user:*"); err != nil { t.Fatalf("DeletePattern err: %v", err) } var v string ok, _ := cache.TryGet(ctx, "user:1", &v) if ok { t.Fatalf("expected user:1 deleted") } ok, _ = cache.TryGet(ctx, "user:2", &v) if ok { t.Fatalf("expected user:2 deleted") } ok, _ = cache.TryGet(ctx, "profile:1", &v) if !ok { t.Fatalf("expected profile:1 kept") } } func TestCachedAndCachedList(t *testing.T) { cache, cleanup := newCacheWithMiniRedis(t) defer cleanup() ctx := context.Background() callCount := 0 result, err := Cached(ctx, cache, "key1", func() (*string, error) { callCount++ val := "hello" return &val, nil }, cache.Policy.UserTTL) if err != nil || *result != "hello" || callCount != 1 { t.Fatalf("Cached first call failed") } // 等待缓存写入完成 for i := 0; i < 10; i++ { var tmp string if ok, _ := cache.TryGet(ctx, "key1", &tmp); ok { break } time.Sleep(10 * time.Millisecond) } // 第二次应命中缓存 _, err = Cached(ctx, cache, "key1", func() (*string, error) { callCount++ val := "world" return &val, nil }, cache.Policy.UserTTL) if err != nil || callCount != 1 { t.Fatalf("Cached should hit cache, callCount=%d err=%v", callCount, err) } listCall := 0 _, err = CachedList(ctx, cache, "list", func() ([]string, error) { listCall++ return []string{"a", "b"}, nil }, cache.Policy.ProfileListTTL) if err != nil || listCall != 1 { t.Fatalf("CachedList first call failed") } for i := 0; i < 10; i++ { var tmp []string if ok, _ := cache.TryGet(ctx, "list", &tmp); ok { break } time.Sleep(10 * time.Millisecond) } _, err = CachedList(ctx, cache, "list", func() ([]string, error) { listCall++ return []string{"c"}, nil }, cache.Policy.ProfileListTTL) if err != nil || listCall != 1 { t.Fatalf("CachedList should hit cache, calls=%d err=%v", listCall, err) } } func TestIncrementWithExpire(t *testing.T) { cache, cleanup := newCacheWithMiniRedis(t) defer cleanup() ctx := context.Background() val, err := cache.IncrementWithExpire(ctx, "counter", time.Second) if err != nil || val != 1 { t.Fatalf("first increment failed, val=%d err=%v", val, err) } val, err = cache.IncrementWithExpire(ctx, "counter", time.Second) if err != nil || val != 2 { t.Fatalf("second increment failed, val=%d err=%v", val, err) } ttl, err := cache.TTL(ctx, "counter") if err != nil || ttl <= 0 { t.Fatalf("TTL not set: ttl=%v err=%v", ttl, err) } }