package cache import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestCacheKeyString(t *testing.T) { key := &CacheKey{ ActivityType: "implementer", TaskID: "T1.1", InputHash: "abc123", ModelID: "claude-opus", } keyStr := key.String() assert.Contains(t, keyStr, "implementer") assert.Contains(t, keyStr, "T1.1") assert.Contains(t, keyStr, "abc123") assert.Contains(t, keyStr, "claude-opus") } func TestComputeHash(t *testing.T) { data := map[string]interface{}{ "task": "T1.1", "code": "package main", } hash1, err := ComputeHash(data) assert.NoError(t, err) assert.NotEmpty(t, hash1) hash2, err := ComputeHash(data) assert.NoError(t, err) assert.Equal(t, hash1, hash2) } func TestSetAndGet(t *testing.T) { tmpDir := t.TempDir() cache := NewResultCache(tmpDir, 100, 0) key := &CacheKey{ ActivityType: "implementer", TaskID: "T1.1", InputHash: "abc123", ModelID: "claude-opus", } result := map[string]interface{}{ "output": "implementation code", "files": []string{"file1.go", "file2.go"}, } err := cache.Set(key, result) assert.NoError(t, err) retrieved, found, err := cache.Get(key) assert.NoError(t, err) assert.True(t, found) assert.Equal(t, "implementation code", retrieved["output"]) } func TestCacheMiss(t *testing.T) { tmpDir := t.TempDir() cache := NewResultCache(tmpDir, 100, 0) key := &CacheKey{ ActivityType: "implementer", TaskID: "T1.1", InputHash: "abc123", ModelID: "claude-opus", } retrieved, found, err := cache.Get(key) assert.NoError(t, err) assert.False(t, found) assert.Nil(t, retrieved) } func TestInvalidate(t *testing.T) { tmpDir := t.TempDir() cache := NewResultCache(tmpDir, 100, 0) key := &CacheKey{ ActivityType: "implementer", TaskID: "T1.1", InputHash: "abc123", ModelID: "claude-opus", } cache.Set(key, map[string]interface{}{"output": "code"}) assert.Equal(t, 1, cache.GetSize()) cache.Invalidate(key) assert.Equal(t, 0, cache.GetSize()) _, found, _ := cache.Get(key) assert.False(t, found) } func TestClear(t *testing.T) { tmpDir := t.TempDir() cache := NewResultCache(tmpDir, 100, 0) for i := 0; i < 10; i++ { key := &CacheKey{ ActivityType: "implementer", TaskID: "T1.1", InputHash: string(rune(48 + i)), ModelID: "claude-opus", } cache.Set(key, map[string]interface{}{"output": "code"}) } assert.Equal(t, 10, cache.GetSize()) cache.Clear() assert.Equal(t, 0, cache.GetSize()) } func TestGetStats(t *testing.T) { tmpDir := t.TempDir() cache := NewResultCache(tmpDir, 100, 0) key := &CacheKey{ ActivityType: "implementer", TaskID: "T1.1", InputHash: "abc123", ModelID: "claude-opus", } cache.Set(key, map[string]interface{}{"output": "code"}) cache.Get(key) // Hit stats := cache.GetStats() assert.Equal(t, 1, stats["size"]) assert.Equal(t, 100, stats["max_size"]) assert.Equal(t, 1, stats["total_hits"]) } func TestTTLExpiration(t *testing.T) { tmpDir := t.TempDir() cache := NewResultCache(tmpDir, 100, 100*time.Millisecond) key := &CacheKey{ ActivityType: "implementer", TaskID: "T1.1", InputHash: "abc123", ModelID: "claude-opus", } cache.Set(key, map[string]interface{}{"output": "code"}) // Should find immediately _, found, _ := cache.Get(key) assert.True(t, found) // Wait for TTL to expire time.Sleep(150 * time.Millisecond) // Should not find after TTL _, found, _ = cache.Get(key) assert.False(t, found) } func TestMaxSizeEviction(t *testing.T) { tmpDir := t.TempDir() cache := NewResultCache(tmpDir, 3, 0) // Add 3 entries for i := 0; i < 3; i++ { key := &CacheKey{ ActivityType: "implementer", TaskID: "T1.1", InputHash: string(rune(48 + i)), ModelID: "claude-opus", } cache.Set(key, map[string]interface{}{"output": "code"}) } assert.Equal(t, 3, cache.GetSize()) // Add 4th entry (should evict oldest) key4 := &CacheKey{ ActivityType: "implementer", TaskID: "T1.1", InputHash: "3", ModelID: "claude-opus", } cache.Set(key4, map[string]interface{}{"output": "code"}) // Size should still be 3 assert.Equal(t, 3, cache.GetSize()) } func TestInvalidateByActivity(t *testing.T) { tmpDir := t.TempDir() cache := NewResultCache(tmpDir, 100, 0) // Add implementer entries for i := 0; i < 2; i++ { key := &CacheKey{ ActivityType: "implementer", TaskID: "T1.1", InputHash: string(rune(48 + i)), ModelID: "claude-opus", } cache.Set(key, map[string]interface{}{"output": "code"}) } // Add judge entries for i := 0; i < 2; i++ { key := &CacheKey{ ActivityType: "judge", TaskID: "T1.1", InputHash: string(rune(48 + i)), ModelID: "claude-opus", } cache.Set(key, map[string]interface{}{"output": "verdict"}) } assert.Equal(t, 4, cache.GetSize()) // Invalidate implementer entries cache.InvalidateByActivity("implementer") assert.Equal(t, 2, cache.GetSize()) } func TestInvalidateByTask(t *testing.T) { tmpDir := t.TempDir() cache := NewResultCache(tmpDir, 100, 0) // Add entries for T1.1 for i := 0; i < 2; i++ { key := &CacheKey{ ActivityType: "implementer", TaskID: "T1.1", InputHash: string(rune(48 + i)), ModelID: "claude-opus", } cache.Set(key, map[string]interface{}{"output": "code"}) } // Add entries for T1.2 for i := 0; i < 2; i++ { key := &CacheKey{ ActivityType: "implementer", TaskID: "T1.2", InputHash: string(rune(48 + i)), ModelID: "claude-opus", } cache.Set(key, map[string]interface{}{"output": "code"}) } assert.Equal(t, 4, cache.GetSize()) // Invalidate T1.1 entries cache.InvalidateByTask("T1.1") assert.Equal(t, 2, cache.GetSize()) } func TestGetHitRate(t *testing.T) { tmpDir := t.TempDir() cache := NewResultCache(tmpDir, 100, 0) key1 := &CacheKey{ ActivityType: "implementer", TaskID: "T1.1", InputHash: "1", ModelID: "claude-opus", } key2 := &CacheKey{ ActivityType: "implementer", TaskID: "T1.1", InputHash: "2", ModelID: "claude-opus", } cache.Set(key1, map[string]interface{}{"output": "code"}) cache.Set(key2, map[string]interface{}{"output": "code"}) cache.Get(key1) cache.Get(key1) cache.Get(key2) hitRate, count := cache.GetHitRate() assert.Equal(t, 2, count) assert.GreaterOrEqual(t, hitRate, 1.0) } func TestPersistence(t *testing.T) { tmpDir := t.TempDir() cache1 := NewResultCache(tmpDir, 100, 0) key := &CacheKey{ ActivityType: "implementer", TaskID: "T1.1", InputHash: "abc123", ModelID: "claude-opus", } cache1.Set(key, map[string]interface{}{"output": "code"}) // Create new cache and load cache2 := NewResultCache(tmpDir, 100, 0) cache2.Load() retrieved, found, _ := cache2.Get(key) assert.True(t, found) assert.Equal(t, "code", retrieved["output"]) }