200 lines
6.5 KiB
Go
200 lines
6.5 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/EthanCodeCraft/xlgo-core/database"
|
|
"github.com/alicebob/miniredis/v2"
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
func setupM10MiniRedis(t *testing.T) *miniredis.Miniredis {
|
|
t.Helper()
|
|
mr := miniredis.RunT(t)
|
|
client := redis.NewClient(&redis.Options{Addr: mr.Addr()})
|
|
t.Cleanup(func() { _ = client.Close() })
|
|
database.SetTestRedisClient(client)
|
|
t.Cleanup(func() { database.SetTestRedisClient(nil) })
|
|
return mr
|
|
}
|
|
|
|
func TestM10CacheWritesReturnRedisNotReady(t *testing.T) {
|
|
database.SetTestRedisClient(nil)
|
|
t.Cleanup(func() { database.SetTestRedisClient(nil) })
|
|
|
|
c := &redisCache{}
|
|
ctx := context.Background()
|
|
|
|
if err := c.Set(ctx, "k", "v", time.Minute); !errors.Is(err, ErrRedisNotReady) {
|
|
t.Fatalf("Set without Redis err = %v, want ErrRedisNotReady", err)
|
|
}
|
|
if err := c.Delete(ctx, "k"); !errors.Is(err, ErrRedisNotReady) {
|
|
t.Fatalf("Delete without Redis err = %v, want ErrRedisNotReady", err)
|
|
}
|
|
if err := c.DeleteByPattern(ctx, "k:*"); !errors.Is(err, ErrRedisNotReady) {
|
|
t.Fatalf("DeleteByPattern without Redis err = %v, want ErrRedisNotReady", err)
|
|
}
|
|
}
|
|
|
|
func TestM10RawHelpersReturnRedisNotReady(t *testing.T) {
|
|
database.SetTestRedisClient(nil)
|
|
t.Cleanup(func() { database.SetTestRedisClient(nil) })
|
|
ctx := context.Background()
|
|
|
|
if _, err := Incr(ctx, "k"); !errors.Is(err, ErrRedisNotReady) {
|
|
t.Fatalf("Incr without Redis err = %v, want ErrRedisNotReady", err)
|
|
}
|
|
if _, err := IncrBy(ctx, "k", 2); !errors.Is(err, ErrRedisNotReady) {
|
|
t.Fatalf("IncrBy without Redis err = %v, want ErrRedisNotReady", err)
|
|
}
|
|
if _, err := Decr(ctx, "k"); !errors.Is(err, ErrRedisNotReady) {
|
|
t.Fatalf("Decr without Redis err = %v, want ErrRedisNotReady", err)
|
|
}
|
|
if _, err := GetTTL(ctx, "k"); !errors.Is(err, ErrRedisNotReady) {
|
|
t.Fatalf("GetTTL without Redis err = %v, want ErrRedisNotReady", err)
|
|
}
|
|
if _, err := SetExpire(ctx, "k", time.Minute); !errors.Is(err, ErrRedisNotReady) {
|
|
t.Fatalf("SetExpire without Redis err = %v, want ErrRedisNotReady", err)
|
|
}
|
|
if _, err := GetRaw(ctx, "k"); !errors.Is(err, ErrRedisNotReady) {
|
|
t.Fatalf("GetRaw without Redis err = %v, want ErrRedisNotReady", err)
|
|
}
|
|
if err := SetRaw(ctx, "k", "v", time.Minute); !errors.Is(err, ErrRedisNotReady) {
|
|
t.Fatalf("SetRaw without Redis err = %v, want ErrRedisNotReady", err)
|
|
}
|
|
}
|
|
|
|
func TestM10ExistsReturnsBackendErrors(t *testing.T) {
|
|
setupM10MiniRedis(t)
|
|
c := &redisCache{}
|
|
ctx := context.Background()
|
|
|
|
exists, err := c.Exists(ctx, "missing")
|
|
if err != nil {
|
|
t.Fatalf("Exists missing err = %v", err)
|
|
}
|
|
if exists {
|
|
t.Fatal("Exists missing = true, want false")
|
|
}
|
|
|
|
if err := c.Set(ctx, "present", "value", time.Minute); err != nil {
|
|
t.Fatalf("Set present: %v", err)
|
|
}
|
|
exists, err = c.Exists(ctx, "present")
|
|
if err != nil || !exists {
|
|
t.Fatalf("Exists present = %v, err=%v; want true,nil", exists, err)
|
|
}
|
|
|
|
canceled, cancel := context.WithCancel(ctx)
|
|
cancel()
|
|
if exists, err = c.Exists(canceled, "present"); err == nil || exists {
|
|
t.Fatalf("Exists canceled = %v, err=%v; want false,error", exists, err)
|
|
}
|
|
}
|
|
|
|
func TestM10GetReturnsBackendAndDecodeErrors(t *testing.T) {
|
|
setupM10MiniRedis(t)
|
|
c := &redisCache{}
|
|
ctx := context.Background()
|
|
|
|
var got string
|
|
ok, err := c.Get(ctx, "missing", &got)
|
|
if err != nil || ok {
|
|
t.Fatalf("Get missing = %v, err=%v; want false,nil", ok, err)
|
|
}
|
|
|
|
if err := c.Set(ctx, "present", "value", time.Minute); err != nil {
|
|
t.Fatalf("Set present: %v", err)
|
|
}
|
|
ok, err = c.Get(ctx, "present", &got)
|
|
if err != nil || !ok || got != "value" {
|
|
t.Fatalf("Get present = %v, %q, err=%v; want true,value,nil", ok, got, err)
|
|
}
|
|
|
|
if err := c.client().Set(ctx, "bad-json", "{", time.Minute).Err(); err != nil {
|
|
t.Fatalf("Set bad-json: %v", err)
|
|
}
|
|
ok, err = c.Get(ctx, "bad-json", &got)
|
|
if err == nil || ok {
|
|
t.Fatalf("Get bad-json = %v, err=%v; want false,error", ok, err)
|
|
}
|
|
|
|
canceled, cancel := context.WithCancel(ctx)
|
|
cancel()
|
|
ok, err = c.Get(canceled, "present", &got)
|
|
if err == nil || ok {
|
|
t.Fatalf("Get canceled = %v, err=%v; want false,error", ok, err)
|
|
}
|
|
}
|
|
|
|
func TestM10PackageExistsReturnsBackendErrors(t *testing.T) {
|
|
setupM10MiniRedis(t)
|
|
orig := GetDefaultCache()
|
|
t.Cleanup(func() { SetDefaultCacheManager(orig) })
|
|
SetDefaultCacheManager(NewCacheManager())
|
|
Init()
|
|
|
|
ctx := context.Background()
|
|
if err := GetCache().Set(ctx, "present", "value", time.Minute); err != nil {
|
|
t.Fatalf("Set through facade: %v", err)
|
|
}
|
|
exists, err := Exists(ctx, "present")
|
|
if err != nil || !exists {
|
|
t.Fatalf("facade Exists = %v, err=%v; want true,nil", exists, err)
|
|
}
|
|
}
|
|
|
|
func TestM10PackageGetReturnsBackendErrors(t *testing.T) {
|
|
setupM10MiniRedis(t)
|
|
orig := GetDefaultCache()
|
|
t.Cleanup(func() { SetDefaultCacheManager(orig) })
|
|
SetDefaultCacheManager(NewCacheManager())
|
|
Init()
|
|
|
|
ctx := context.Background()
|
|
if err := GetCache().Set(ctx, "present", "value", time.Minute); err != nil {
|
|
t.Fatalf("Set through facade: %v", err)
|
|
}
|
|
var got string
|
|
ok, err := Get(ctx, "present", &got)
|
|
if err != nil || !ok || got != "value" {
|
|
t.Fatalf("facade Get = %v, %q, err=%v; want true,value,nil", ok, got, err)
|
|
}
|
|
}
|
|
|
|
// TestM10CustomCacheServiceCompile asserts a user-provided CacheService
|
|
// implementation compiles against the (bool, error) contract. This is a
|
|
// compile-time guard: if the interface changes again, this stops building.
|
|
type customCacheSvc struct{}
|
|
|
|
func (customCacheSvc) Get(ctx context.Context, key string, dest any) (bool, error) { return false, nil }
|
|
func (customCacheSvc) Set(ctx context.Context, key string, value any, ttl time.Duration) error {
|
|
return nil
|
|
}
|
|
func (customCacheSvc) Delete(ctx context.Context, key string) error { return nil }
|
|
func (customCacheSvc) DeleteByPattern(ctx context.Context, p string) error { return nil }
|
|
func (customCacheSvc) Exists(ctx context.Context, key string) (bool, error) { return false, nil }
|
|
|
|
func TestM10CustomCacheServiceCompiles(t *testing.T) {
|
|
var _ CacheService = customCacheSvc{}
|
|
}
|
|
|
|
// TestM10RedisNotReadyOnGetExists asserts the Redis-not-ready error is
|
|
// surfaced (not swallowed) when no backend is configured.
|
|
func TestM10RedisNotReadyOnGetExists(t *testing.T) {
|
|
database.SetTestRedisClient(nil)
|
|
t.Cleanup(func() { database.SetTestRedisClient(nil) })
|
|
c := &redisCache{}
|
|
ctx := context.Background()
|
|
|
|
if _, err := c.Get(ctx, "k", new(string)); !errors.Is(err, ErrRedisNotReady) {
|
|
t.Fatalf("Get without Redis err = %v, want ErrRedisNotReady", err)
|
|
}
|
|
if _, err := c.Exists(ctx, "k"); !errors.Is(err, ErrRedisNotReady) {
|
|
t.Fatalf("Exists without Redis err = %v, want ErrRedisNotReady", err)
|
|
}
|
|
}
|