Files
skybridge/test/cache_test.go
2025-08-22 15:01:40 -04:00

454 lines
11 KiB
Go

package test
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"github.com/kms/api-key-service/internal/cache"
)
// MockConfig implements ConfigProvider for testing
type MockConfig struct {
values map[string]string
}
func NewMockConfig() *MockConfig {
return &MockConfig{
values: map[string]string{
"CACHE_ENABLED": "true",
"CACHE_TTL": "1h",
},
}
}
func (m *MockConfig) GetString(key string) string {
return m.values[key]
}
func (m *MockConfig) GetInt(key string) int { return 0 }
func (m *MockConfig) GetBool(key string) bool {
if key == "CACHE_ENABLED" {
return m.values[key] == "true"
}
return false
}
func (m *MockConfig) GetDuration(key string) time.Duration {
if key == "CACHE_TTL" {
if d, err := time.ParseDuration(m.values[key]); err == nil {
return d
}
}
return 0
}
func (m *MockConfig) GetStringSlice(key string) []string { return nil }
func (m *MockConfig) IsSet(key string) bool { return m.values[key] != "" }
func (m *MockConfig) Validate() error { return nil }
func (m *MockConfig) GetDatabaseDSN() string { return "" }
func (m *MockConfig) GetServerAddress() string { return "" }
func (m *MockConfig) GetMetricsAddress() string { return "" }
func (m *MockConfig) GetJWTSecret() string { return m.GetString("JWT_SECRET") }
func (m *MockConfig) IsDevelopment() bool { return true }
func (m *MockConfig) IsProduction() bool { return false }
func TestMemoryCache_SetAndGet(t *testing.T) {
config := NewMockConfig()
logger := zap.NewNop()
memCache := cache.NewMemoryCache(config, logger)
defer memCache.Close()
ctx := context.Background()
key := "test-key"
value := []byte("test-value")
ttl := time.Hour
// Set value
err := memCache.Set(ctx, key, value, ttl)
require.NoError(t, err)
// Get value
retrieved, err := memCache.Get(ctx, key)
require.NoError(t, err)
assert.Equal(t, value, retrieved)
}
func TestMemoryCache_GetNonExistent(t *testing.T) {
config := NewMockConfig()
logger := zap.NewNop()
memCache := cache.NewMemoryCache(config, logger)
defer memCache.Close()
ctx := context.Background()
key := "non-existent-key"
// Try to get non-existent key
_, err := memCache.Get(ctx, key)
assert.Error(t, err)
}
func TestMemoryCache_Expiration(t *testing.T) {
config := NewMockConfig()
logger := zap.NewNop()
memCache := cache.NewMemoryCache(config, logger)
defer memCache.Close()
ctx := context.Background()
key := "expiring-key"
value := []byte("expiring-value")
ttl := 100 * time.Millisecond
// Set value with short TTL
err := memCache.Set(ctx, key, value, ttl)
require.NoError(t, err)
// Get value immediately (should work)
retrieved, err := memCache.Get(ctx, key)
require.NoError(t, err)
assert.Equal(t, value, retrieved)
// Wait for expiration
time.Sleep(150 * time.Millisecond)
// Try to get expired value
_, err = memCache.Get(ctx, key)
assert.Error(t, err)
}
func TestMemoryCache_Delete(t *testing.T) {
config := NewMockConfig()
logger := zap.NewNop()
memCache := cache.NewMemoryCache(config, logger)
defer memCache.Close()
ctx := context.Background()
key := "delete-key"
value := []byte("delete-value")
ttl := time.Hour
// Set value
err := memCache.Set(ctx, key, value, ttl)
require.NoError(t, err)
// Verify it exists
exists, err := memCache.Exists(ctx, key)
require.NoError(t, err)
assert.True(t, exists)
// Delete value
err = memCache.Delete(ctx, key)
require.NoError(t, err)
// Verify it no longer exists
exists, err = memCache.Exists(ctx, key)
require.NoError(t, err)
assert.False(t, exists)
}
func TestMemoryCache_Exists(t *testing.T) {
config := NewMockConfig()
logger := zap.NewNop()
memCache := cache.NewMemoryCache(config, logger)
defer memCache.Close()
ctx := context.Background()
key := "exists-key"
value := []byte("exists-value")
ttl := time.Hour
// Check non-existent key
exists, err := memCache.Exists(ctx, key)
require.NoError(t, err)
assert.False(t, exists)
// Set value
err = memCache.Set(ctx, key, value, ttl)
require.NoError(t, err)
// Check existing key
exists, err = memCache.Exists(ctx, key)
require.NoError(t, err)
assert.True(t, exists)
}
func TestMemoryCache_Clear(t *testing.T) {
config := NewMockConfig()
logger := zap.NewNop()
memCache := cache.NewMemoryCache(config, logger)
defer memCache.Close()
ctx := context.Background()
ttl := time.Hour
// Set multiple values
err := memCache.Set(ctx, "key1", []byte("value1"), ttl)
require.NoError(t, err)
err = memCache.Set(ctx, "key2", []byte("value2"), ttl)
require.NoError(t, err)
// Verify they exist
exists, err := memCache.Exists(ctx, "key1")
require.NoError(t, err)
assert.True(t, exists)
exists, err = memCache.Exists(ctx, "key2")
require.NoError(t, err)
assert.True(t, exists)
// Clear cache
err = memCache.Clear(ctx)
require.NoError(t, err)
// Verify they no longer exist
exists, err = memCache.Exists(ctx, "key1")
require.NoError(t, err)
assert.False(t, exists)
exists, err = memCache.Exists(ctx, "key2")
require.NoError(t, err)
assert.False(t, exists)
}
func TestCacheManager_SetAndGetJSON(t *testing.T) {
config := NewMockConfig()
logger := zap.NewNop()
cacheManager := cache.NewCacheManager(config, logger)
defer cacheManager.Close()
ctx := context.Background()
key := "json-key"
ttl := time.Hour
// Test data
originalData := map[string]interface{}{
"name": "test",
"value": 42,
"items": []string{"a", "b", "c"},
}
// Set JSON
err := cacheManager.SetJSON(ctx, key, originalData, ttl)
require.NoError(t, err)
// Get JSON
var retrievedData map[string]interface{}
err = cacheManager.GetJSON(ctx, key, &retrievedData)
require.NoError(t, err)
// Compare data
assert.Equal(t, originalData["name"], retrievedData["name"])
assert.Equal(t, float64(42), retrievedData["value"]) // JSON numbers are float64
// JSON arrays become []interface{}, so we need to compare differently
retrievedItems := retrievedData["items"].([]interface{})
expectedItems := []interface{}{"a", "b", "c"}
assert.Equal(t, expectedItems, retrievedItems)
}
func TestCacheManager_GetJSONNonExistent(t *testing.T) {
config := NewMockConfig()
logger := zap.NewNop()
cacheManager := cache.NewCacheManager(config, logger)
defer cacheManager.Close()
ctx := context.Background()
key := "non-existent-json-key"
var data map[string]interface{}
err := cacheManager.GetJSON(ctx, key, &data)
assert.Error(t, err)
}
func TestCacheManager_RawBytesOperations(t *testing.T) {
config := NewMockConfig()
logger := zap.NewNop()
cacheManager := cache.NewCacheManager(config, logger)
defer cacheManager.Close()
ctx := context.Background()
key := "raw-key"
value := []byte("raw-value")
ttl := time.Hour
// Set raw bytes
err := cacheManager.Set(ctx, key, value, ttl)
require.NoError(t, err)
// Get raw bytes
retrieved, err := cacheManager.Get(ctx, key)
require.NoError(t, err)
assert.Equal(t, value, retrieved)
// Check exists
exists, err := cacheManager.Exists(ctx, key)
require.NoError(t, err)
assert.True(t, exists)
// Delete
err = cacheManager.Delete(ctx, key)
require.NoError(t, err)
// Verify deleted
exists, err = cacheManager.Exists(ctx, key)
require.NoError(t, err)
assert.False(t, exists)
}
func TestCacheKey(t *testing.T) {
prefix := "test"
key := "key123"
expected := "test:key123"
result := cache.CacheKey(prefix, key)
assert.Equal(t, expected, result)
}
func TestCacheKeyPrefixes(t *testing.T) {
// Test that constants are defined
assert.Equal(t, "perm", cache.KeyPrefixPermission)
assert.Equal(t, "app", cache.KeyPrefixApplication)
assert.Equal(t, "token", cache.KeyPrefixToken)
assert.Equal(t, "user_claims", cache.KeyPrefixUserClaims)
assert.Equal(t, "token_revoked", cache.KeyPrefixTokenRevoked)
}
func TestCacheManager_ConfigMethods(t *testing.T) {
// Create mock config with cache settings
config := &MockConfig{
values: map[string]string{
"CACHE_ENABLED": "true",
"CACHE_TTL": "1h",
},
}
logger := zap.NewNop()
cacheManager := cache.NewCacheManager(config, logger)
defer cacheManager.Close()
// Test IsEnabled
assert.True(t, cacheManager.IsEnabled())
// Test GetDefaultTTL
ttl := cacheManager.GetDefaultTTL()
assert.Equal(t, time.Hour, ttl)
}
func TestCacheManager_InvalidJSON(t *testing.T) {
config := NewMockConfig()
logger := zap.NewNop()
cacheManager := cache.NewCacheManager(config, logger)
defer cacheManager.Close()
ctx := context.Background()
key := "invalid-json-key"
// Set invalid JSON data manually
invalidJSON := []byte("{invalid json}")
err := cacheManager.Set(ctx, key, invalidJSON, time.Hour)
require.NoError(t, err)
// Try to get as JSON (should fail)
var data map[string]interface{}
err = cacheManager.GetJSON(ctx, key, &data)
assert.Error(t, err)
}
func TestCacheManager_SetJSONMarshalError(t *testing.T) {
config := NewMockConfig()
logger := zap.NewNop()
cacheManager := cache.NewCacheManager(config, logger)
defer cacheManager.Close()
ctx := context.Background()
key := "marshal-error-key"
// Try to set data that can't be marshaled (function)
invalidData := func() {}
err := cacheManager.SetJSON(ctx, key, invalidData, time.Hour)
assert.Error(t, err)
}
// Benchmark tests
func BenchmarkMemoryCache_Set(b *testing.B) {
config := NewMockConfig()
logger := zap.NewNop()
memCache := cache.NewMemoryCache(config, logger)
defer memCache.Close()
ctx := context.Background()
value := []byte("benchmark-value")
ttl := time.Hour
b.ResetTimer()
for i := 0; i < b.N; i++ {
key := "benchmark-key-" + string(rune(i))
memCache.Set(ctx, key, value, ttl)
}
}
func BenchmarkMemoryCache_Get(b *testing.B) {
config := NewMockConfig()
logger := zap.NewNop()
memCache := cache.NewMemoryCache(config, logger)
defer memCache.Close()
ctx := context.Background()
key := "benchmark-get-key"
value := []byte("benchmark-value")
ttl := time.Hour
// Pre-populate cache
memCache.Set(ctx, key, value, ttl)
b.ResetTimer()
for i := 0; i < b.N; i++ {
memCache.Get(ctx, key)
}
}
func BenchmarkCacheManager_SetJSON(b *testing.B) {
config := NewMockConfig()
logger := zap.NewNop()
cacheManager := cache.NewCacheManager(config, logger)
defer cacheManager.Close()
ctx := context.Background()
data := map[string]interface{}{
"name": "benchmark",
"value": 42,
"items": []string{"a", "b", "c"},
}
ttl := time.Hour
b.ResetTimer()
for i := 0; i < b.N; i++ {
key := "benchmark-json-key-" + string(rune(i))
cacheManager.SetJSON(ctx, key, data, ttl)
}
}
func BenchmarkCacheManager_GetJSON(b *testing.B) {
config := NewMockConfig()
logger := zap.NewNop()
cacheManager := cache.NewCacheManager(config, logger)
defer cacheManager.Close()
ctx := context.Background()
key := "benchmark-json-get-key"
data := map[string]interface{}{
"name": "benchmark",
"value": 42,
"items": []string{"a", "b", "c"},
}
ttl := time.Hour
// Pre-populate cache
cacheManager.SetJSON(ctx, key, data, ttl)
b.ResetTimer()
for i := 0; i < b.N; i++ {
var retrieved map[string]interface{}
cacheManager.GetJSON(ctx, key, &retrieved)
}
}