package infra import ( "os" "path/filepath" "testing" ) func writeTestFile(t *testing.T, dir, rel, content string) { t.Helper() full := filepath.Join(dir, filepath.FromSlash(rel)) if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil { t.Fatalf("mkdir %s: %v", filepath.Dir(full), err) } if err := os.WriteFile(full, []byte(content), 0o644); err != nil { t.Fatalf("write %s: %v", full, err) } } func TestVaultInventoryScan_Empty(t *testing.T) { t.Run("tmpdir vacio retorna slice vacio", func(t *testing.T) { dir := t.TempDir() files, err := VaultInventoryScan(dir, "v1", "test") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(files) != 0 { t.Errorf("expected 0 files, got %d", len(files)) } }) } func TestVaultInventoryScan_DataLayout(t *testing.T) { t.Run("data layout — bucket y sub_bucket correctos", func(t *testing.T) { dir := t.TempDir() writeTestFile(t, dir, "data/raw/a.csv", "col1,col2\n1,2\n") writeTestFile(t, dir, "data/processed/b.parquet", "PAR1fakedata") files, err := VaultInventoryScan(dir, "vid", "vname") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(files) != 2 { t.Fatalf("expected 2 files, got %d", len(files)) } // files are sorted: data/processed/b.parquet < data/raw/a.csv b := files[0] if b.RelPath != "data/processed/b.parquet" { t.Errorf("files[0].RelPath = %q, want data/processed/b.parquet", b.RelPath) } if b.Bucket != "data" { t.Errorf("files[0].Bucket = %q, want data", b.Bucket) } if b.SubBucket != "processed" { t.Errorf("files[0].SubBucket = %q, want processed", b.SubBucket) } if b.Mime != "application/parquet" { t.Errorf("files[0].Mime = %q, want application/parquet", b.Mime) } if b.Ext != ".parquet" { t.Errorf("files[0].Ext = %q, want .parquet", b.Ext) } if b.VaultID != "vid" { t.Errorf("VaultID = %q, want vid", b.VaultID) } a := files[1] if a.RelPath != "data/raw/a.csv" { t.Errorf("files[1].RelPath = %q, want data/raw/a.csv", a.RelPath) } if a.Mime != "text/csv" { t.Errorf("files[1].Mime = %q, want text/csv", a.Mime) } if a.Bucket != "data" || a.SubBucket != "raw" { t.Errorf("files[1]: bucket=%q subBucket=%q, want data/raw", a.Bucket, a.SubBucket) } }) } func TestVaultInventoryScan_KnowledgeLayout(t *testing.T) { t.Run("knowledge layout — bucket y sub_bucket correctos", func(t *testing.T) { dir := t.TempDir() writeTestFile(t, dir, "knowledge/decisions/x.md", "# Decision\n\ncontent") files, err := VaultInventoryScan(dir, "vid", "vname") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(files) != 1 { t.Fatalf("expected 1 file, got %d", len(files)) } f := files[0] if f.RelPath != "knowledge/decisions/x.md" { t.Errorf("RelPath = %q", f.RelPath) } if f.Bucket != "knowledge" { t.Errorf("Bucket = %q, want knowledge", f.Bucket) } if f.SubBucket != "decisions" { t.Errorf("SubBucket = %q, want decisions", f.SubBucket) } if f.Mime != "text/markdown" { t.Errorf("Mime = %q, want text/markdown", f.Mime) } }) } func TestVaultInventoryScan_SkipsIndexAndGit(t *testing.T) { t.Run("omite vault_index.db y .git", func(t *testing.T) { dir := t.TempDir() writeTestFile(t, dir, "vault_index.db", "sqlite data") writeTestFile(t, dir, "vault_index.db-wal", "wal data") writeTestFile(t, dir, ".git/HEAD", "ref: refs/heads/master") writeTestFile(t, dir, "data/raw/real.csv", "a,b\n1,2\n") files, err := VaultInventoryScan(dir, "vid", "vname") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(files) != 1 { t.Fatalf("expected 1 file (real.csv), got %d: %v", len(files), relPaths(files)) } if files[0].RelPath != "data/raw/real.csv" { t.Errorf("unexpected file: %q", files[0].RelPath) } }) } func TestVaultInventoryScan_Sha256Deterministic(t *testing.T) { t.Run("sha256 determinista para mismo contenido", func(t *testing.T) { dir1 := t.TempDir() dir2 := t.TempDir() content := "deterministic content 123\n" writeTestFile(t, dir1, "data/raw/f.csv", content) writeTestFile(t, dir2, "data/raw/f.csv", content) files1, err := VaultInventoryScan(dir1, "v1", "vault1") if err != nil { t.Fatal(err) } files2, err := VaultInventoryScan(dir2, "v2", "vault2") if err != nil { t.Fatal(err) } if files1[0].Sha256 != files2[0].Sha256 { t.Errorf("sha256 mismatch: %q vs %q", files1[0].Sha256, files2[0].Sha256) } if len(files1[0].Sha256) != 64 { t.Errorf("sha256 length = %d, want 64", len(files1[0].Sha256)) } }) } func TestVaultInventoryScan_Sorted(t *testing.T) { t.Run("orden lexicografico del resultado", func(t *testing.T) { dir := t.TempDir() writeTestFile(t, dir, "knowledge/decisions/z.md", "z") writeTestFile(t, dir, "data/raw/a.csv", "a") writeTestFile(t, dir, "data/processed/m.parquet", "m") writeTestFile(t, dir, "knowledge/domains/b.md", "b") files, err := VaultInventoryScan(dir, "v", "v") if err != nil { t.Fatal(err) } for i := 1; i < len(files); i++ { if files[i].RelPath < files[i-1].RelPath { t.Errorf("not sorted at index %d: %q < %q", i, files[i].RelPath, files[i-1].RelPath) } } }) } // relPaths is a helper for test error messages. func relPaths(files []VaultFile) []string { out := make([]string, len(files)) for i, f := range files { out[i] = f.RelPath } return out }