package cache_test import ( "context" "testing" "time" "github.com/AmaniQuery/amaniquery/internal/cache" ) // TestLRUCache_SetGet verifies basic set/get operations func TestLRUCache_SetGet(t *testing.T) { lru := cache.NewLRUCache(100) // Test set and get lru.Set("key1", []byte("value1")) value, found := lru.Get("key1") if !found { t.Fatal("Expected to find key1") } if string(value) != "value1" { t.Errorf("Expected 'value1', got '%s'", string(value)) } // Test missing key _, found = lru.Get("nonexistent") if found { t.Error("Expected not to find nonexistent key") } } // TestLRUCache_Update verifies updating existing keys func TestLRUCache_Update(t *testing.T) { lru := cache.NewLRUCache(100) lru.Set("key1", []byte("original")) lru.Set("key1", []byte("updated")) value, found := lru.Get("key1") if !found { t.Fatal("Expected to find key1") } if string(value) != "updated" { t.Errorf("Expected 'updated', got '%s'", string(value)) } } // TestLRUCache_Eviction verifies LRU eviction when over capacity func TestLRUCache_Eviction(t *testing.T) { lru := cache.NewLRUCache(3) // Fill cache to capacity lru.Set("key1", []byte("value1")) lru.Set("key2", []byte("value2")) lru.Set("key3", []byte("value3")) // Access key1 to make it recently used lru.Get("key1") // Add new key, should evict key2 (least recently used) lru.Set("key4", []byte("value4")) // key2 should be evicted _, found := lru.Get("key2") if found { t.Error("Expected key2 to be evicted") } // key1 should still exist (was accessed recently) _, found = lru.Get("key1") if !found { t.Error("Expected key1 to still exist") } // key3 and key4 should exist _, found = lru.Get("key3") if !found { t.Error("Expected key3 to exist") } _, found = lru.Get("key4") if !found { t.Error("Expected key4 to exist") } } // TestLRUCache_Delete verifies deletion operations func TestLRUCache_Delete(t *testing.T) { lru := cache.NewLRUCache(100) lru.Set("key1", []byte("value1")) lru.Set("key2", []byte("value2")) lru.Delete("key1") _, found := lru.Get("key1") if found { t.Error("Expected key1 to be deleted") } // key2 should still exist _, found = lru.Get("key2") if !found { t.Error("Expected key2 to still exist") } } // TestLRUCache_DeleteNonexistent verifies deleting nonexistent keys doesn't panic func TestLRUCache_DeleteNonexistent(t *testing.T) { lru := cache.NewLRUCache(100) // Should not panic lru.Delete("nonexistent") } // TestCacheMissError tests the error type func TestCacheMissError(t *testing.T) { err := cache.ErrCacheMiss if err.Error() != "cache miss" { t.Errorf("Expected 'cache miss', got '%s'", err.Error()) } } // TestMultiTierCache_LocalOnly tests cache without Redis connection func TestMultiTierCache_LocalOnly(t *testing.T) { // Create cache with invalid Redis URL to ensure Redis is not used cfg := cache.Config{ RedisURL: "redis://invalid:6379", // Will fail connection LocalSize: 100, TTL: time.Hour, MaxRetries: 1, PoolSize: 1, } c, err := cache.New(cfg) if err != nil { t.Fatalf("Failed to create cache: %v", err) } defer c.Close() ctx := context.Background() // Set and get should work with local cache err = c.Set(ctx, "key1", []byte("value1"), 0) if err != nil { t.Fatalf("Set failed: %v", err) } value, err := c.Get(ctx, "key1") if err != nil { t.Fatalf("Get failed: %v", err) } if string(value) != "value1" { t.Errorf("Expected 'value1', got '%s'", string(value)) } } // TestMultiTierCache_CacheMiss tests cache miss behavior func TestMultiTierCache_CacheMiss(t *testing.T) { cfg := cache.Config{ RedisURL: "redis://invalid:6379", LocalSize: 100, TTL: time.Hour, MaxRetries: 1, PoolSize: 1, } c, err := cache.New(cfg) if err != nil { t.Fatalf("Failed to create cache: %v", err) } defer c.Close() ctx := context.Background() _, err = c.Get(ctx, "nonexistent") if err == nil { t.Error("Expected cache miss error") } } // TestMultiTierCache_Delete tests deletion func TestMultiTierCache_Delete(t *testing.T) { cfg := cache.Config{ RedisURL: "redis://invalid:6379", LocalSize: 100, TTL: time.Hour, MaxRetries: 1, PoolSize: 1, } c, err := cache.New(cfg) if err != nil { t.Fatalf("Failed to create cache: %v", err) } defer c.Close() ctx := context.Background() // Set then delete c.Set(ctx, "key1", []byte("value1"), 0) err = c.Delete(ctx, "key1") if err != nil { t.Fatalf("Delete failed: %v", err) } // Should be gone _, err = c.Get(ctx, "key1") if err == nil { t.Error("Expected cache miss after delete") } } // TestMultiTierCache_JSON tests JSON operations func TestMultiTierCache_JSON(t *testing.T) { cfg := cache.Config{ RedisURL: "redis://invalid:6379", LocalSize: 100, TTL: time.Hour, MaxRetries: 1, PoolSize: 1, } c, err := cache.New(cfg) if err != nil { t.Fatalf("Failed to create cache: %v", err) } defer c.Close() ctx := context.Background() type testData struct { Name string `json:"name"` Value int `json:"value"` } original := testData{Name: "test", Value: 42} err = c.SetJSON(ctx, "json-key", original, 0) if err != nil { t.Fatalf("SetJSON failed: %v", err) } var result testData err = c.GetJSON(ctx, "json-key", &result) if err != nil { t.Fatalf("GetJSON failed: %v", err) } if result.Name != original.Name || result.Value != original.Value { t.Errorf("JSON mismatch: expected %+v, got %+v", original, result) } } // TestMultiTierCache_Metrics tests metrics tracking func TestMultiTierCache_Metrics(t *testing.T) { cfg := cache.Config{ RedisURL: "redis://invalid:6379", LocalSize: 100, TTL: time.Hour, MaxRetries: 1, PoolSize: 1, } c, err := cache.New(cfg) if err != nil { t.Fatalf("Failed to create cache: %v", err) } defer c.Close() ctx := context.Background() // Set and get to generate metrics c.Set(ctx, "key1", []byte("value1"), 0) c.Get(ctx, "key1") // Hit c.Get(ctx, "key2") // Miss metrics := c.GetMetrics() if metrics.LocalHits != 1 { t.Errorf("Expected 1 local hit, got %d", metrics.LocalHits) } if metrics.LocalMisses != 1 { t.Errorf("Expected 1 local miss, got %d", metrics.LocalMisses) } } // BenchmarkLRUCache_Set benchmarks LRU set operations func BenchmarkLRUCache_Set(b *testing.B) { lru := cache.NewLRUCache(10000) b.ResetTimer() for i := 0; i < b.N; i++ { lru.Set("key"+string(rune(i%1000)), []byte("value")) } } // BenchmarkLRUCache_Get benchmarks LRU get operations func BenchmarkLRUCache_Get(b *testing.B) { lru := cache.NewLRUCache(10000) // Pre-populate for i := 0; i < 1000; i++ { lru.Set("key"+string(rune(i)), []byte("value")) } b.ResetTimer() for i := 0; i < b.N; i++ { lru.Get("key" + string(rune(i%1000))) } }