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" ) 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 := NewMockConfig() config.values["CACHE_ENABLED"] = "true" config.values["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) } }