package infra import ( "os" "path/filepath" "testing" "time" ) // setupAggregateTestRepo creates a minimal repo layout: // // / // registry.db (SQLite, empty) // projects//vaults/vault.yaml // / (optionally with vault_index.db populated) func setupAggregateTestRepo(t *testing.T, vaultName, projectID, vaultPath string, withIndex bool) string { t.Helper() root := t.TempDir() // Create registry.db regDB, err := SQLiteOpen(filepath.Join(root, "registry.db"), "") if err != nil { t.Fatalf("create registry.db: %v", err) } regDB.Close() // Create project vault manifest projVaultsDir := filepath.Join(root, "projects", projectID, "vaults") if err := os.MkdirAll(projVaultsDir, 0755); err != nil { t.Fatalf("mkdir projects: %v", err) } manifestYAML := "vaults:\n - name: " + vaultName + "\n description: test\n path: " + vaultPath + "\n tags: []\n" if err := os.WriteFile(filepath.Join(projVaultsDir, "vault.yaml"), []byte(manifestYAML), 0644); err != nil { t.Fatalf("write vault.yaml: %v", err) } // Create vault dir if err := os.MkdirAll(vaultPath, 0755); err != nil { t.Fatalf("mkdir vault: %v", err) } if withIndex { // Create a vault_index.db with one file row vdb, err := VaultIndexOpen(vaultPath) if err != nil { t.Fatalf("VaultIndexOpen: %v", err) } now := time.Now().UTC().Unix() _, err = vdb.Exec(`INSERT INTO files (rel_path, size, mtime, sha256, mime, ext, bucket, sub_bucket, indexed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, "data/raw/sample.csv", 1024, now, "deadbeef", "text/csv", ".csv", "data", "raw", now) if err != nil { t.Fatalf("insert test file: %v", err) } vdb.Close() } return root } func TestVaultAggregateIndex_NoVaults(t *testing.T) { root := t.TempDir() // No manifests, just registry.db regDB, err := SQLiteOpen(filepath.Join(root, "registry.db"), "") if err != nil { t.Fatalf("create registry.db: %v", err) } regDB.Close() report, err := VaultAggregateIndex(root) if err != nil { t.Fatalf("unexpected error: %v", err) } if report.VaultsProcessed != 0 { t.Errorf("VaultsProcessed: want 0, got %d", report.VaultsProcessed) } if len(report.Errors) != 0 { t.Errorf("Errors: want empty, got %v", report.Errors) } } func TestVaultAggregateIndex_VaultWithoutIndex(t *testing.T) { vaultDir := t.TempDir() root := setupAggregateTestRepo(t, "my_vault", "my_proj", vaultDir, false /* no vault_index.db */) report, err := VaultAggregateIndex(root) if err != nil { t.Fatalf("unexpected error: %v", err) } if report.VaultsSkipped != 1 { t.Errorf("VaultsSkipped: want 1, got %d", report.VaultsSkipped) } if report.VaultsProcessed != 0 { t.Errorf("VaultsProcessed: want 0, got %d", report.VaultsProcessed) } } func TestVaultAggregateIndex_HappyPath(t *testing.T) { vaultDir := t.TempDir() root := setupAggregateTestRepo(t, "my_vault", "my_proj", vaultDir, true) report, err := VaultAggregateIndex(root) if err != nil { t.Fatalf("unexpected error: %v", err) } if report.VaultsProcessed != 1 { t.Errorf("VaultsProcessed: want 1, got %d", report.VaultsProcessed) } if report.TotalFiles != 1 { t.Errorf("TotalFiles: want 1, got %d", report.TotalFiles) } // Verify row exists in registry.db regDB, err := SQLiteOpen(filepath.Join(root, "registry.db"), "") if err != nil { t.Fatalf("open registry.db: %v", err) } defer regDB.Close() var count int if err := regDB.QueryRow(`SELECT COUNT(*) FROM vault_files`).Scan(&count); err != nil { t.Fatalf("count vault_files: %v", err) } if count != 1 { t.Errorf("vault_files count: want 1, got %d", count) } } func TestVaultAggregateIndex_ReRunReplaces(t *testing.T) { vaultDir := t.TempDir() root := setupAggregateTestRepo(t, "my_vault", "my_proj", vaultDir, true) // First run if _, err := VaultAggregateIndex(root); err != nil { t.Fatalf("first run: %v", err) } // Add a second file to vault_index.db vdb, err := VaultIndexOpen(vaultDir) if err != nil { t.Fatalf("reopen vault index: %v", err) } now := time.Now().UTC().Unix() _, err = vdb.Exec(`INSERT INTO files (rel_path, size, mtime, sha256, mime, ext, bucket, sub_bucket, indexed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, "data/raw/extra.csv", 512, now, "cafebabe", "text/csv", ".csv", "data", "raw", now) if err != nil { t.Fatalf("insert second file: %v", err) } vdb.Close() // Second run report, err := VaultAggregateIndex(root) if err != nil { t.Fatalf("second run: %v", err) } if report.TotalFiles != 2 { t.Errorf("TotalFiles: want 2, got %d", report.TotalFiles) } // Verify no duplicates — exactly 2 rows regDB, err := SQLiteOpen(filepath.Join(root, "registry.db"), "") if err != nil { t.Fatalf("open registry.db: %v", err) } defer regDB.Close() var count int if err := regDB.QueryRow(`SELECT COUNT(*) FROM vault_files`).Scan(&count); err != nil { t.Fatalf("count vault_files: %v", err) } if count != 2 { t.Errorf("vault_files count after re-run: want 2, got %d", count) } }