package ucxi import ( "context" "fmt" "io/ioutil" "os" "path/filepath" "strings" "testing" "time" ) func createTempStorageDir(t *testing.T) string { dir, err := ioutil.TempDir("", "ucxi-storage-test-*") if err != nil { t.Fatalf("Failed to create temp directory: %v", err) } return dir } func TestNewBasicContentStorage(t *testing.T) { tempDir := createTempStorageDir(t) defer os.RemoveAll(tempDir) storage, err := NewBasicContentStorage(tempDir) if err != nil { t.Errorf("NewBasicContentStorage failed: %v", err) } if storage == nil { t.Error("NewBasicContentStorage should not return nil") } if storage.basePath != tempDir { t.Errorf("Base path = %s, want %s", storage.basePath, tempDir) } // Verify directory was created if _, err := os.Stat(tempDir); os.IsNotExist(err) { t.Error("Storage directory should be created") } } func TestNewBasicContentStorageWithInvalidPath(t *testing.T) { // Try to create storage with invalid path (e.g., a file instead of directory) tempDir := createTempStorageDir(t) defer os.RemoveAll(tempDir) // Create a file at the path invalidPath := filepath.Join(tempDir, "file-not-dir") if err := ioutil.WriteFile(invalidPath, []byte("test"), 0644); err != nil { t.Fatalf("Failed to create test file: %v", err) } // This should fail because the path exists as a file, not a directory _, err := NewBasicContentStorage(invalidPath) if err == nil { t.Error("NewBasicContentStorage should fail with invalid path") } } func TestStorageStoreAndRetrieve(t *testing.T) { tempDir := createTempStorageDir(t) defer os.RemoveAll(tempDir) storage, err := NewBasicContentStorage(tempDir) if err != nil { t.Fatalf("Failed to create storage: %v", err) } ctx := context.Background() key := "test-key" content := &Content{ Data: []byte("test content data"), ContentType: "text/plain", Metadata: map[string]string{ "author": "test-author", "version": "1.0", }, Version: 1, CreatedAt: time.Now(), UpdatedAt: time.Now(), Author: "test-user", } // Test store err = storage.Store(ctx, key, content) if err != nil { t.Errorf("Store failed: %v", err) } // Test retrieve retrieved, err := storage.Retrieve(ctx, key) if err != nil { t.Errorf("Retrieve failed: %v", err) } if retrieved == nil { t.Error("Retrieved content should not be nil") } // Verify content matches if string(retrieved.Data) != string(content.Data) { t.Errorf("Data mismatch: got %s, want %s", string(retrieved.Data), string(content.Data)) } if retrieved.ContentType != content.ContentType { t.Errorf("ContentType mismatch: got %s, want %s", retrieved.ContentType, content.ContentType) } if retrieved.Author != content.Author { t.Errorf("Author mismatch: got %s, want %s", retrieved.Author, content.Author) } if retrieved.Version != content.Version { t.Errorf("Version mismatch: got %d, want %d", retrieved.Version, content.Version) } // Verify metadata if len(retrieved.Metadata) != len(content.Metadata) { t.Errorf("Metadata length mismatch: got %d, want %d", len(retrieved.Metadata), len(content.Metadata)) } for key, value := range content.Metadata { if retrieved.Metadata[key] != value { t.Errorf("Metadata[%s] mismatch: got %s, want %s", key, retrieved.Metadata[key], value) } } // Verify checksum is calculated if retrieved.Checksum == "" { t.Error("Checksum should be calculated and stored") } } func TestStorageChecksumValidation(t *testing.T) { tempDir := createTempStorageDir(t) defer os.RemoveAll(tempDir) storage, err := NewBasicContentStorage(tempDir) if err != nil { t.Fatalf("Failed to create storage: %v", err) } ctx := context.Background() key := "checksum-test" content := &Content{ Data: []byte("test content for checksum"), ContentType: "text/plain", } // Store content (checksum will be calculated automatically) err = storage.Store(ctx, key, content) if err != nil { t.Errorf("Store failed: %v", err) } // Retrieve and verify checksum validation works retrieved, err := storage.Retrieve(ctx, key) if err != nil { t.Errorf("Retrieve failed: %v", err) } if retrieved.Checksum == "" { t.Error("Checksum should be set after storing") } // Manually corrupt the file to test checksum validation filePath := storage.getFilePath(key) originalData, err := ioutil.ReadFile(filePath) if err != nil { t.Fatalf("Failed to read file: %v", err) } // Corrupt the data in the JSON by changing base64 encoded data // The content is base64 encoded in JSON, so we'll replace some characters corruptedData := strings.Replace(string(originalData), "dGVzdCBjb250ZW50IGZvciBjaGVja3N1bQ==", "Y29ycnVwdGVkIGNvbnRlbnQ=", 1) if corruptedData == string(originalData) { // If the base64 replacement didn't work, try a simpler corruption corruptedData = strings.Replace(string(originalData), "\"", "'", 1) if corruptedData == string(originalData) { t.Fatalf("Failed to corrupt data - no changes made") } } err = ioutil.WriteFile(filePath, []byte(corruptedData), 0644) if err != nil { t.Fatalf("Failed to write corrupted file: %v", err) } // Retrieve should fail due to checksum mismatch _, err = storage.Retrieve(ctx, key) if err == nil { t.Error("Retrieve should fail with corrupted content") } if !strings.Contains(err.Error(), "checksum mismatch") { t.Errorf("Error should mention checksum mismatch, got: %v", err) } } func TestStorageDelete(t *testing.T) { tempDir := createTempStorageDir(t) defer os.RemoveAll(tempDir) storage, err := NewBasicContentStorage(tempDir) if err != nil { t.Fatalf("Failed to create storage: %v", err) } ctx := context.Background() key := "delete-test" content := &Content{Data: []byte("content to delete")} // Store content err = storage.Store(ctx, key, content) if err != nil { t.Errorf("Store failed: %v", err) } // Verify it exists exists, err := storage.Exists(ctx, key) if err != nil { t.Errorf("Exists check failed: %v", err) } if !exists { t.Error("Content should exist after storing") } // Delete content err = storage.Delete(ctx, key) if err != nil { t.Errorf("Delete failed: %v", err) } // Verify it no longer exists exists, err = storage.Exists(ctx, key) if err != nil { t.Errorf("Exists check after delete failed: %v", err) } if exists { t.Error("Content should not exist after deletion") } // Verify retrieve fails _, err = storage.Retrieve(ctx, key) if err == nil { t.Error("Retrieve should fail for deleted content") } // Delete non-existent key should fail err = storage.Delete(ctx, "non-existent-key") if err == nil { t.Error("Delete should fail for non-existent key") } } func TestStorageList(t *testing.T) { tempDir := createTempStorageDir(t) defer os.RemoveAll(tempDir) storage, err := NewBasicContentStorage(tempDir) if err != nil { t.Fatalf("Failed to create storage: %v", err) } ctx := context.Background() // Store multiple pieces of content testKeys := []string{ "prefix1/key1", "prefix1/key2", "prefix2/key1", "prefix2/key2", "different-prefix/key1", } for i, key := range testKeys { content := &Content{Data: []byte(fmt.Sprintf("content-%d", i))} err = storage.Store(ctx, key, content) if err != nil { t.Errorf("Store failed for key %s: %v", key, err) } } // Test list all allKeys, err := storage.List(ctx, "") if err != nil { t.Errorf("List all failed: %v", err) } if len(allKeys) != len(testKeys) { t.Errorf("List all returned %d keys, want %d", len(allKeys), len(testKeys)) } // Test list with prefix prefix1Keys, err := storage.List(ctx, "prefix1/") if err != nil { t.Errorf("List with prefix failed: %v", err) } if len(prefix1Keys) != 2 { t.Errorf("List prefix1/ returned %d keys, want 2", len(prefix1Keys)) } // Verify the keys match the prefix for _, key := range prefix1Keys { if !strings.HasPrefix(key, "prefix1/") { t.Errorf("Key %s should have prefix 'prefix1/'", key) } } // Test list with non-existent prefix noKeys, err := storage.List(ctx, "nonexistent/") if err != nil { t.Errorf("List non-existent prefix failed: %v", err) } if len(noKeys) != 0 { t.Errorf("List non-existent prefix returned %d keys, want 0", len(noKeys)) } } func TestStorageExists(t *testing.T) { tempDir := createTempStorageDir(t) defer os.RemoveAll(tempDir) storage, err := NewBasicContentStorage(tempDir) if err != nil { t.Fatalf("Failed to create storage: %v", err) } ctx := context.Background() key := "exists-test" // Initially should not exist exists, err := storage.Exists(ctx, key) if err != nil { t.Errorf("Exists check failed: %v", err) } if exists { t.Error("Key should not exist initially") } // Store content content := &Content{Data: []byte("test")} err = storage.Store(ctx, key, content) if err != nil { t.Errorf("Store failed: %v", err) } // Should exist now exists, err = storage.Exists(ctx, key) if err != nil { t.Errorf("Exists check after store failed: %v", err) } if !exists { t.Error("Key should exist after storing") } // Delete content err = storage.Delete(ctx, key) if err != nil { t.Errorf("Delete failed: %v", err) } // Should not exist anymore exists, err = storage.Exists(ctx, key) if err != nil { t.Errorf("Exists check after delete failed: %v", err) } if exists { t.Error("Key should not exist after deletion") } } func TestStorageClear(t *testing.T) { tempDir := createTempStorageDir(t) defer os.RemoveAll(tempDir) storage, err := NewBasicContentStorage(tempDir) if err != nil { t.Fatalf("Failed to create storage: %v", err) } ctx := context.Background() // Store multiple pieces of content for i := 0; i < 5; i++ { key := fmt.Sprintf("key-%d", i) content := &Content{Data: []byte(fmt.Sprintf("content-%d", i))} err = storage.Store(ctx, key, content) if err != nil { t.Errorf("Store failed for key %s: %v", key, err) } } // Verify content exists keys, err := storage.List(ctx, "") if err != nil { t.Errorf("List failed: %v", err) } if len(keys) != 5 { t.Errorf("Expected 5 keys before clear, got %d", len(keys)) } // Clear all content err = storage.Clear(ctx) if err != nil { t.Errorf("Clear failed: %v", err) } // Verify all content is gone keys, err = storage.List(ctx, "") if err != nil { t.Errorf("List after clear failed: %v", err) } if len(keys) != 0 { t.Errorf("Expected 0 keys after clear, got %d", len(keys)) } // Verify directory still exists but is empty if _, err := os.Stat(tempDir); os.IsNotExist(err) { t.Error("Base directory should still exist after clear") } entries, err := ioutil.ReadDir(tempDir) if err != nil { t.Errorf("Failed to read directory after clear: %v", err) } if len(entries) != 0 { t.Errorf("Directory should be empty after clear, found %d entries", len(entries)) } } func TestStorageGetStorageStats(t *testing.T) { tempDir := createTempStorageDir(t) defer os.RemoveAll(tempDir) storage, err := NewBasicContentStorage(tempDir) if err != nil { t.Fatalf("Failed to create storage: %v", err) } ctx := context.Background() // Initially should have no files stats, err := storage.GetStorageStats() if err != nil { t.Errorf("GetStorageStats failed: %v", err) } if stats["file_count"].(int) != 0 { t.Errorf("Initial file count = %d, want 0", stats["file_count"]) } if stats["total_size"].(int64) != 0 { t.Errorf("Initial total size = %d, want 0", stats["total_size"]) } if stats["base_path"].(string) != tempDir { t.Errorf("Base path = %s, want %s", stats["base_path"], tempDir) } // Store some content for i := 0; i < 3; i++ { key := fmt.Sprintf("stats-key-%d", i) content := &Content{Data: []byte(fmt.Sprintf("test content %d", i))} err = storage.Store(ctx, key, content) if err != nil { t.Errorf("Store failed: %v", err) } } // Check stats again stats, err = storage.GetStorageStats() if err != nil { t.Errorf("GetStorageStats after store failed: %v", err) } if stats["file_count"].(int) != 3 { t.Errorf("File count after storing = %d, want 3", stats["file_count"]) } if stats["total_size"].(int64) <= 0 { t.Error("Total size should be greater than 0 after storing content") } } func TestStorageGetFilePath(t *testing.T) { tempDir := createTempStorageDir(t) defer os.RemoveAll(tempDir) storage, err := NewBasicContentStorage(tempDir) if err != nil { t.Fatalf("Failed to create storage: %v", err) } tests := []struct { name string key string shouldContain []string shouldNotContain []string }{ { name: "simple key", key: "simple-key", shouldContain: []string{"simple-key.json"}, shouldNotContain: []string{":"}, }, { name: "key with colons", key: "agent:role", shouldContain: []string{"agent_role.json"}, shouldNotContain: []string{":"}, }, { name: "key with at symbol", key: "agent@project", shouldContain: []string{"agent_at_project.json"}, shouldNotContain: []string{"@"}, }, { name: "key with slashes", key: "path/to/resource", shouldContain: []string{".json"}, // Should not contain the original slash as literal }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filePath := storage.getFilePath(tt.key) // Should always start with base path if !strings.HasPrefix(filePath, tempDir) { t.Errorf("File path should start with base path") } // Should always end with .json if !strings.HasSuffix(filePath, ".json") { t.Errorf("File path should end with .json") } // Check required substrings for _, required := range tt.shouldContain { if !strings.Contains(filePath, required) { t.Errorf("File path should contain '%s', got: %s", required, filePath) } } // Check forbidden substrings for _, forbidden := range tt.shouldNotContain { if strings.Contains(filePath, forbidden) { t.Errorf("File path should not contain '%s', got: %s", forbidden, filePath) } } }) } } func TestStorageErrorCases(t *testing.T) { tempDir := createTempStorageDir(t) defer os.RemoveAll(tempDir) storage, err := NewBasicContentStorage(tempDir) if err != nil { t.Fatalf("Failed to create storage: %v", err) } ctx := context.Background() // Test empty key content := &Content{Data: []byte("test")} err = storage.Store(ctx, "", content) if err == nil { t.Error("Store with empty key should fail") } _, err = storage.Retrieve(ctx, "") if err == nil { t.Error("Retrieve with empty key should fail") } err = storage.Delete(ctx, "") if err == nil { t.Error("Delete with empty key should fail") } _, err = storage.Exists(ctx, "") if err == nil { t.Error("Exists with empty key should fail") } // Test nil content err = storage.Store(ctx, "test-key", nil) if err == nil { t.Error("Store with nil content should fail") } // Test retrieve non-existent key _, err = storage.Retrieve(ctx, "non-existent-key") if err == nil { t.Error("Retrieve non-existent key should fail") } } // Test concurrent access to storage func TestStorageConcurrency(t *testing.T) { tempDir := createTempStorageDir(t) defer os.RemoveAll(tempDir) storage, err := NewBasicContentStorage(tempDir) if err != nil { t.Fatalf("Failed to create storage: %v", err) } ctx := context.Background() done := make(chan bool, 10) // Run multiple goroutines that store, retrieve, and delete content for i := 0; i < 10; i++ { go func(id int) { defer func() { done <- true }() key := fmt.Sprintf("concurrent-key-%d", id) content := &Content{Data: []byte(fmt.Sprintf("content-%d", id))} // Store if err := storage.Store(ctx, key, content); err != nil { t.Errorf("Goroutine %d store failed: %v", id, err) return } // Retrieve if _, err := storage.Retrieve(ctx, key); err != nil { t.Errorf("Goroutine %d retrieve failed: %v", id, err) return } // Delete if err := storage.Delete(ctx, key); err != nil { t.Errorf("Goroutine %d delete failed: %v", id, err) return } }(i) } // Wait for all goroutines to complete for i := 0; i < 10; i++ { <-done } // Verify final state - all content should be deleted keys, err := storage.List(ctx, "") if err != nil { t.Errorf("List after concurrent operations failed: %v", err) } if len(keys) != 0 { t.Errorf("Expected 0 keys after concurrent operations, got %d", len(keys)) } } // Benchmark tests func BenchmarkStorageStore(b *testing.B) { tempDir := createTempStorageDirForBench(b) defer os.RemoveAll(tempDir) storage, err := NewBasicContentStorage(tempDir) if err != nil { b.Fatalf("Failed to create storage: %v", err) } ctx := context.Background() content := &Content{ Data: []byte("benchmark test content"), ContentType: "text/plain", } b.ResetTimer() for i := 0; i < b.N; i++ { key := fmt.Sprintf("benchmark-key-%d", i) storage.Store(ctx, key, content) } } func BenchmarkStorageRetrieve(b *testing.B) { tempDir := createTempStorageDirForBench(b) defer os.RemoveAll(tempDir) storage, err := NewBasicContentStorage(tempDir) if err != nil { b.Fatalf("Failed to create storage: %v", err) } ctx := context.Background() content := &Content{ Data: []byte("benchmark test content"), ContentType: "text/plain", } // Pre-populate storage keys := make([]string, 1000) for i := 0; i < 1000; i++ { keys[i] = fmt.Sprintf("benchmark-key-%d", i) storage.Store(ctx, keys[i], content) } b.ResetTimer() for i := 0; i < b.N; i++ { key := keys[i%1000] storage.Retrieve(ctx, key) } } // Helper function for benchmark that creates temp directory func createTempStorageDirForBench(t testing.TB) string { dir, err := ioutil.TempDir("", "ucxi-storage-test-*") if err != nil { t.Fatalf("Failed to create temp directory: %v", err) } return dir }