axonhub / internal /pkg /xcache /cache_test.go
llzai's picture
Upload 1793 files
9853396 verified
package xcache
import (
"context"
"testing"
"time"
"github.com/alicebob/miniredis/v2"
"github.com/eko/gocache/lib/v4/store"
"github.com/redis/go-redis/v9"
"github.com/stretchr/testify/require"
gocache "github.com/patrickmn/go-cache"
"github.com/looplj/axonhub/internal/pkg/xredis"
)
func intPtr(i int) *int {
return &i
}
func TestNewMemory(t *testing.T) {
client := gocache.New(5*time.Minute, 10*time.Minute)
cache := NewMemory[string](client)
ctx := context.Background()
// Test Set and Get
err := cache.Set(ctx, "test-key", "test-value")
require.NoError(t, err)
value, err := cache.Get(ctx, "test-key")
require.NoError(t, err)
require.Equal(t, "test-value", value)
// Test GetType
require.Equal(t, "cache", cache.GetType())
}
func TestNewMemoryWithOptions(t *testing.T) {
cache := NewMemoryWithOptions[int](5*time.Minute, 10*time.Minute)
ctx := context.Background()
// Test with different data type
err := cache.Set(ctx, "number", 42)
require.NoError(t, err)
value, err := cache.Get(ctx, "number")
require.NoError(t, err)
require.Equal(t, 42, value)
}
func TestNewRedis(t *testing.T) {
// Start miniredis server
mr := miniredis.RunT(t)
defer mr.Close()
// Create Redis client
client := redis.NewClient(&redis.Options{
Addr: mr.Addr(),
})
cache := NewRedis[string](client)
ctx := context.Background()
// Test Set and Get
err := cache.Set(ctx, "redis-key", "redis-value")
require.NoError(t, err)
value, err := cache.Get(ctx, "redis-key")
require.NoError(t, err)
require.Equal(t, "redis-value", value)
// Test GetType
require.Equal(t, "cache", cache.GetType())
}
func TestNewRedisWithOptions(t *testing.T) {
// Start miniredis server
mr := miniredis.RunT(t)
defer mr.Close()
opts := &redis.Options{
Addr: mr.Addr(),
}
cache := NewRedisWithOptions[string](opts)
ctx := context.Background()
// Test with string data type
err := cache.Set(ctx, "redis-string", "test-value")
require.NoError(t, err)
value, err := cache.Get(ctx, "redis-string")
require.NoError(t, err)
require.Equal(t, "test-value", value)
}
func TestNewTwoLevel(t *testing.T) {
// Memory cache
memClient := gocache.New(5*time.Minute, 10*time.Minute)
memCache := NewMemory[string](memClient)
// Redis cache with miniredis
mr := miniredis.RunT(t)
defer mr.Close()
redisClient := redis.NewClient(&redis.Options{
Addr: mr.Addr(),
})
redisCache := NewRedis[string](redisClient)
// Two-level cache
cache := NewTwoLevel[string](memCache, redisCache)
ctx := context.Background()
// Test Set - should set in both levels
err := cache.Set(ctx, "two-level-key", "two-level-value")
require.NoError(t, err)
// Test Get - should get from memory first
value, err := cache.Get(ctx, "two-level-key")
require.NoError(t, err)
require.Equal(t, "two-level-value", value)
// Clear memory cache to test Redis fallback
err = memCache.Clear(ctx)
require.NoError(t, err)
// Should still get value from Redis
value, err = cache.Get(ctx, "two-level-key")
require.NoError(t, err)
require.Equal(t, "two-level-value", value)
// Test GetType
require.Equal(t, "chain", cache.GetType())
}
func TestNewTwoLevelWithClients(t *testing.T) {
// Start miniredis server
mr := miniredis.RunT(t)
defer mr.Close()
memClient := gocache.New(5*time.Minute, 10*time.Minute)
redisClient := redis.NewClient(&redis.Options{
Addr: mr.Addr(),
})
cache := NewTwoLevelWithClients[string](
memClient,
redisClient,
[]store.Option{},
[]store.Option{},
)
ctx := context.Background()
// Test basic functionality
err := cache.Set(ctx, "client-key", "client-value")
require.NoError(t, err)
value, err := cache.Get(ctx, "client-key")
require.NoError(t, err)
require.Equal(t, "client-value", value)
}
func TestNewFromConfig_Memory(t *testing.T) {
cfg := Config{
Mode: ModeMemory,
Memory: MemoryConfig{
Expiration: 5 * time.Minute,
CleanupInterval: 10 * time.Minute,
},
}
cache := NewFromConfig[string](cfg)
ctx := context.Background()
// Test basic functionality
err := cache.Set(ctx, "memory-config-key", "memory-config-value")
require.NoError(t, err)
value, err := cache.Get(ctx, "memory-config-key")
require.NoError(t, err)
require.Equal(t, "memory-config-value", value)
// Should be memory cache
require.Equal(t, "cache", cache.GetType())
}
func TestNewFromConfig_Redis(t *testing.T) {
// Start miniredis server
mr := miniredis.RunT(t)
defer mr.Close()
cfg := Config{
Mode: ModeRedis,
Redis: xredis.Config{
Addr: mr.Addr(),
Expiration: 5 * time.Minute,
},
}
cache := NewFromConfig[string](cfg)
ctx := context.Background()
// Test basic functionality
err := cache.Set(ctx, "redis-config-key", "redis-config-value")
require.NoError(t, err)
value, err := cache.Get(ctx, "redis-config-key")
require.NoError(t, err)
require.Equal(t, "redis-config-value", value)
// Should be redis cache
require.Equal(t, "cache", cache.GetType())
}
func TestNewFromConfig_RedisWithoutAddr(t *testing.T) {
cfg := Config{
Mode: ModeRedis,
// No Redis config - should fallback to memory
}
require.Panics(t, func() {
_ = NewFromConfig[string](cfg)
})
}
func TestNewFromConfig_TwoLevel(t *testing.T) {
// Start miniredis server
mr := miniredis.RunT(t)
defer mr.Close()
cfg := Config{
Mode: ModeTwoLevel,
Memory: MemoryConfig{
Expiration: 5 * time.Minute,
CleanupInterval: 10 * time.Minute,
},
Redis: xredis.Config{
Addr: mr.Addr(),
Expiration: 15 * time.Minute,
},
}
cache := NewFromConfig[string](cfg)
ctx := context.Background()
// Test basic functionality
err := cache.Set(ctx, "two-level-config-key", "two-level-config-value")
require.NoError(t, err)
value, err := cache.Get(ctx, "two-level-config-key")
require.NoError(t, err)
require.Equal(t, "two-level-config-value", value)
// Should be chain cache
require.Equal(t, "chain", cache.GetType())
}
func TestNewFromConfig_TwoLevelWithoutRedis(t *testing.T) {
cfg := Config{
Mode: ModeTwoLevel,
Memory: MemoryConfig{
Expiration: 5 * time.Minute,
CleanupInterval: 10 * time.Minute,
},
// No Redis config - should fallback to memory
}
cache := NewFromConfig[string](cfg)
ctx := context.Background()
// Test basic functionality
err := cache.Set(ctx, "two-level-fallback-key", "two-level-fallback-value")
require.NoError(t, err)
value, err := cache.Get(ctx, "two-level-fallback-key")
require.NoError(t, err)
require.Equal(t, "two-level-fallback-value", value)
// Should fallback to memory cache
require.Equal(t, "cache", cache.GetType())
}
func TestNewFromConfig_EmptyMode(t *testing.T) {
cfg := Config{} // Empty config
cache := NewFromConfig[string](cfg)
// Should return noop cache
require.Equal(t, "noop", cache.GetType())
ctx := context.Background()
_, err := cache.Get(ctx, "test")
require.Error(t, err)
require.ErrorIs(t, err, ErrCacheNotConfigured)
}
func TestNewFromConfig_InvalidMode(t *testing.T) {
cfg := Config{
Mode: "invalid-mode",
}
cache := NewFromConfig[string](cfg)
// Should return noop cache
require.Equal(t, "noop", cache.GetType())
ctx := context.Background()
_, err := cache.Get(ctx, "test")
require.Error(t, err)
require.ErrorIs(t, err, ErrCacheNotConfigured)
}
func TestCacheWithExpiration(t *testing.T) {
// Start miniredis server
mr := miniredis.RunT(t)
defer mr.Close()
cfg := Config{
Mode: ModeRedis,
Redis: xredis.Config{
Addr: mr.Addr(),
Expiration: 100 * time.Millisecond, // Very short expiration for testing
},
}
cache := NewFromConfig[string](cfg)
ctx := context.Background()
// Set value with default expiration
err := cache.Set(ctx, "expiring-key", "expiring-value")
require.NoError(t, err)
// Should be able to get immediately
value, err := cache.Get(ctx, "expiring-key")
require.NoError(t, err)
require.Equal(t, "expiring-value", value)
// Wait for expiration
time.Sleep(150 * time.Millisecond)
// Should be expired now (or might still work depending on cache implementation)
_, err = cache.Get(ctx, "expiring-key")
// Note: Some cache implementations might not expire immediately, so we don't assert error here
}
func TestCacheOperations(t *testing.T) {
// Start miniredis server
mr := miniredis.RunT(t)
defer mr.Close()
cfg := Config{
Mode: ModeRedis,
Redis: xredis.Config{
Addr: mr.Addr(),
},
}
cache := NewFromConfig[string](cfg)
ctx := context.Background()
// Test Set multiple keys
err := cache.Set(ctx, "key1", "value1")
require.NoError(t, err)
err = cache.Set(ctx, "key2", "value2")
require.NoError(t, err)
// Test Get multiple keys
value1, err := cache.Get(ctx, "key1")
require.NoError(t, err)
require.Equal(t, "value1", value1)
value2, err := cache.Get(ctx, "key2")
require.NoError(t, err)
require.Equal(t, "value2", value2)
// Test Delete
err = cache.Delete(ctx, "key1")
require.NoError(t, err)
// key1 should be gone
_, err = cache.Get(ctx, "key1")
require.Error(t, err)
// key2 should still exist
value2, err = cache.Get(ctx, "key2")
require.NoError(t, err)
require.Equal(t, "value2", value2)
// Test Clear
err = cache.Clear(ctx)
require.NoError(t, err)
// All keys should be gone
_, err = cache.Get(ctx, "key2")
require.Error(t, err)
}
func TestDefaultIfZero(t *testing.T) {
// Test with zero value
result := defaultIfZero(0, 5*time.Minute)
require.Equal(t, 5*time.Minute, result)
// Test with non-zero value
result = defaultIfZero(10*time.Minute, 5*time.Minute)
require.Equal(t, 10*time.Minute, result)
}
func TestComplexDataTypes(t *testing.T) {
type TestStruct struct {
ID int `json:"id"`
Name string `json:"name"`
}
// Start miniredis server
mr := miniredis.RunT(t)
defer mr.Close()
cfg := Config{
Mode: ModeMemory, // Use memory for complex types
}
cache := NewFromConfig[TestStruct](cfg)
ctx := context.Background()
testData := TestStruct{
ID: 123,
Name: "Test Name",
}
// Test Set and Get with struct
err := cache.Set(ctx, "struct-key", testData)
require.NoError(t, err)
retrievedData, err := cache.Get(ctx, "struct-key")
require.NoError(t, err)
require.Equal(t, testData, retrievedData)
}
func TestSeparateExpirationConfig(t *testing.T) {
// Start miniredis server
mr := miniredis.RunT(t)
defer mr.Close()
cfg := Config{
Mode: ModeTwoLevel,
Memory: MemoryConfig{
Expiration: 100 * time.Millisecond, // Very short for memory
CleanupInterval: 10 * time.Minute,
},
Redis: xredis.Config{
Addr: mr.Addr(),
Expiration: 5 * time.Minute, // Much longer for Redis
},
}
cache := NewFromConfig[string](cfg)
ctx := context.Background()
// Test that cache works with separate expiration settings
err := cache.Set(ctx, "separate-exp-key", "separate-exp-value")
require.NoError(t, err)
// Should be able to get immediately
value, err := cache.Get(ctx, "separate-exp-key")
require.NoError(t, err)
require.Equal(t, "separate-exp-value", value)
// Should be chain cache (two-level)
require.Equal(t, "chain", cache.GetType())
// Note: We can't easily test the actual expiration behavior in a unit test
// since the two-level cache behavior is complex, but we can verify the
// configuration is accepted and the cache works
}