Files
fn_registry/functions/infra/vault_layout_ensure_test.go
T
egutierrez e3c8979e8d chore: auto-commit (95 archivos)
- cmd/fn/doctor.go
- cmd/fn/main.go
- cpp/apps/primitives_gallery/playground/tables/CMakeLists.txt
- cpp/apps/primitives_gallery/playground/tables/data_table.cpp
- cpp/apps/primitives_gallery/playground/tables/data_table_logic.cpp
- cpp/apps/primitives_gallery/playground/tables/data_table_logic.h
- cpp/apps/primitives_gallery/playground/tables/self_test.cpp
- cpp/apps/primitives_gallery/playground/tables/tql.cpp
- cpp/apps/primitives_gallery/playground/tables/viz.cpp
- cpp/apps/primitives_gallery/playground/tables/viz.h
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:50:34 +02:00

395 lines
10 KiB
Go

package infra
import (
"os"
"path/filepath"
"testing"
)
// mkVaultDir creates a temporary directory tree for tests.
// entries is a list of relative paths to create.
// Paths ending in "/" are directories; others are files with placeholder content.
func mkVaultDir(t *testing.T, entries []string) string {
t.Helper()
root := t.TempDir()
for _, e := range entries {
full := filepath.Join(root, filepath.FromSlash(e))
if e[len(e)-1] == '/' {
if err := os.MkdirAll(full, 0o755); err != nil {
t.Fatalf("mkVaultDir: mkdir %q: %v", full, err)
}
} else {
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
t.Fatalf("mkVaultDir: mkdir parent %q: %v", full, err)
}
if err := os.WriteFile(full, []byte("test\n"), 0o644); err != nil {
t.Fatalf("mkVaultDir: write %q: %v", full, err)
}
}
}
return root
}
func TestVaultLayoutEnsure_DryRun_NoChange(t *testing.T) {
root := mkVaultDir(t, []string{
"raw/",
"raw/file1.csv",
"processed/",
})
before := snapshotDir(t, root)
report, err := VaultLayoutEnsure(root, true)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !report.DryRun {
t.Error("DryRun flag not set in report")
}
after := snapshotDir(t, root)
if !mapEqual(before, after) {
t.Errorf("dry-run modified disk: before=%v after=%v", before, after)
}
// Should have planned a migration for raw and processed.
if len(report.Migrated) == 0 {
t.Error("expected Migrated to be non-empty in dry-run plan")
}
}
func TestVaultLayoutEnsure_FreshDir_CreatesLayout(t *testing.T) {
root := mkVaultDir(t, []string{}) // empty vault
report, err := VaultLayoutEnsure(root, false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// All standard dirs should be created.
wantCreated := []string{
"data", "knowledge",
filepath.Join("data", "raw"),
filepath.Join("data", "processed"),
filepath.Join("data", "exports"),
filepath.Join("knowledge", "decisions"),
filepath.Join("knowledge", "domains"),
filepath.Join("knowledge", "models"),
filepath.Join("knowledge", "benchmarks"),
filepath.Join("knowledge", "test_documents"),
}
createdSet := toSet(report.Created)
for _, w := range wantCreated {
if _, ok := createdSet[w]; !ok {
t.Errorf("expected Created to contain %q, got %v", w, report.Created)
}
}
// All directories must actually exist on disk.
for _, w := range wantCreated {
full := filepath.Join(root, w)
info, err := os.Stat(full)
if err != nil {
t.Errorf("expected %q to exist: %v", full, err)
continue
}
if !info.IsDir() {
t.Errorf("%q should be a directory", full)
}
}
}
func TestVaultLayoutEnsure_LegacyDataLayout_Migrates(t *testing.T) {
root := mkVaultDir(t, []string{
"raw/",
"raw/file1.parquet",
"raw/file2.parquet",
"processed/",
"processed/clean.csv",
"exports/",
})
report, err := VaultLayoutEnsure(root, false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// raw and processed should appear in Migrated (as dirs, top-level rename).
migratedSet := toSet(report.Migrated)
for _, pair := range []string{
"raw -> " + filepath.Join("data", "raw"),
"processed -> " + filepath.Join("data", "processed"),
} {
if _, ok := migratedSet[pair]; !ok {
t.Errorf("expected Migrated to contain %q, got %v", pair, report.Migrated)
}
}
// Files must have moved.
for _, f := range []string{
filepath.Join("data", "raw", "file1.parquet"),
filepath.Join("data", "raw", "file2.parquet"),
filepath.Join("data", "processed", "clean.csv"),
} {
if _, err := os.Stat(filepath.Join(root, f)); err != nil {
t.Errorf("expected %q to exist after migration: %v", f, err)
}
}
// Old dirs must be gone.
for _, d := range []string{"raw", "processed"} {
if pathExists(filepath.Join(root, d)) {
t.Errorf("expected legacy dir %q to be removed", d)
}
}
}
func TestVaultLayoutEnsure_LegacyKnowledgeLayout_Migrates(t *testing.T) {
root := mkVaultDir(t, []string{
"decisions/",
"decisions/2024-01.md",
"models/",
"models/ner_v1.pkl",
"README.md",
})
report, err := VaultLayoutEnsure(root, false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// decisions and models should appear in Migrated.
migratedSet := toSet(report.Migrated)
for _, pair := range []string{
"decisions -> " + filepath.Join("knowledge", "decisions"),
"models -> " + filepath.Join("knowledge", "models"),
"README.md -> " + filepath.Join("knowledge", "README.md"),
} {
if _, ok := migratedSet[pair]; !ok {
t.Errorf("expected Migrated to contain %q, got %v", pair, report.Migrated)
}
}
// Files must be at new location.
for _, f := range []string{
filepath.Join("knowledge", "decisions", "2024-01.md"),
filepath.Join("knowledge", "models", "ner_v1.pkl"),
filepath.Join("knowledge", "README.md"),
} {
if _, err := os.Stat(filepath.Join(root, f)); err != nil {
t.Errorf("expected %q to exist after migration: %v", f, err)
}
}
}
func TestVaultLayoutEnsure_AlreadyMigrated_Idempotent(t *testing.T) {
root := mkVaultDir(t, []string{
"data/",
"data/raw/",
"data/raw/file.csv",
"data/processed/",
"data/exports/",
"knowledge/",
"knowledge/decisions/",
"knowledge/domains/",
"knowledge/models/",
"knowledge/benchmarks/",
"knowledge/test_documents/",
})
report1, err := VaultLayoutEnsure(root, false)
if err != nil {
t.Fatalf("first run error: %v", err)
}
if len(report1.Migrated) != 0 {
t.Errorf("first run on fully-migrated vault should have no migrations, got %v", report1.Migrated)
}
before := snapshotDir(t, root)
report2, err := VaultLayoutEnsure(root, false)
if err != nil {
t.Fatalf("second run error: %v", err)
}
after := snapshotDir(t, root)
if !mapEqual(before, after) {
t.Error("second run modified disk (not idempotent)")
}
if len(report2.Migrated) != 0 {
t.Errorf("second run should produce no migrations, got %v", report2.Migrated)
}
if len(report2.AlreadyOK) == 0 {
t.Error("second run should report existing dirs as AlreadyOK")
}
}
func TestVaultLayoutEnsure_Mixed_PartialMigration(t *testing.T) {
// data/raw already migrated; exports still at root; knowledge dirs in legacy positions.
root := mkVaultDir(t, []string{
"data/",
"data/raw/",
"data/raw/already_here.csv",
"exports/",
"exports/report.pdf",
"decisions/",
"decisions/2023-note.md",
})
report, err := VaultLayoutEnsure(root, false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// data/raw should be AlreadyOK.
if !sliceContains(report.AlreadyOK, filepath.Join("data", "raw")) {
t.Errorf("data/raw should be AlreadyOK, got AlreadyOK=%v", report.AlreadyOK)
}
// exports should be migrated.
exportsMigrated := false
for _, m := range report.Migrated {
if m == "exports -> "+filepath.Join("data", "exports") {
exportsMigrated = true
}
}
if !exportsMigrated {
t.Errorf("exports should be migrated, Migrated=%v", report.Migrated)
}
// decisions should be migrated.
decisionsMigrated := false
for _, m := range report.Migrated {
if m == "decisions -> "+filepath.Join("knowledge", "decisions") {
decisionsMigrated = true
}
}
if !decisionsMigrated {
t.Errorf("decisions should be migrated, Migrated=%v", report.Migrated)
}
}
func TestVaultLayoutEnsure_MergeConflict_Errors(t *testing.T) {
// Both src (raw/) and dst (data/raw/) exist and have a file with the same name.
root := mkVaultDir(t, []string{
"raw/",
"raw/collision.csv",
"data/",
"data/raw/",
"data/raw/collision.csv", // same name -> conflict
})
_, err := VaultLayoutEnsure(root, false)
if err == nil {
t.Fatal("expected error for merge conflict, got nil")
}
if !contains(err.Error(), "conflict") && !contains(err.Error(), "collision.csv") {
t.Errorf("error should mention conflict or the file name, got: %v", err)
}
}
func TestVaultLayoutEnsure_UnknownFiles_Skipped(t *testing.T) {
root := mkVaultDir(t, []string{
".git/",
"vault_index.db",
"my_custom_notes.txt",
"raw/",
})
report, err := VaultLayoutEnsure(root, false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
skippedSet := toSet(report.Skipped)
for _, name := range []string{".git", "vault_index.db", "my_custom_notes.txt"} {
if _, ok := skippedSet[name]; !ok {
t.Errorf("expected %q in Skipped, got %v", name, report.Skipped)
}
}
// raw should NOT be in Skipped (it's a known bucket).
if _, ok := skippedSet["raw"]; ok {
t.Error("raw should not appear in Skipped — it is a known bucket")
}
}
func TestVaultLayoutEnsure_NotADir_Errors(t *testing.T) {
t.Run("non-existent path", func(t *testing.T) {
_, err := VaultLayoutEnsure("/tmp/does_not_exist_fn_registry_test_xyz", false)
if err == nil {
t.Fatal("expected error for non-existent path")
}
})
t.Run("path is a file", func(t *testing.T) {
f, err := os.CreateTemp("", "vault_layout_*.txt")
if err != nil {
t.Fatal(err)
}
f.Close()
defer os.Remove(f.Name())
_, err = VaultLayoutEnsure(f.Name(), false)
if err == nil {
t.Fatal("expected error when vaultPath is a file, not a dir")
}
if !contains(err.Error(), "not a directory") {
t.Errorf("error should mention 'not a directory', got: %v", err)
}
})
}
// --- helpers ---
// snapshotDir returns a map of relative path -> exists for all entries under root.
func snapshotDir(t *testing.T, root string) map[string]bool {
t.Helper()
snap := make(map[string]bool)
err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
rel, _ := filepath.Rel(root, path)
snap[rel] = true
return nil
})
if err != nil {
t.Fatalf("snapshotDir: %v", err)
}
return snap
}
func mapEqual(a, b map[string]bool) bool {
if len(a) != len(b) {
return false
}
for k := range a {
if !b[k] {
return false
}
}
return true
}
func toSet(ss []string) map[string]struct{} {
m := make(map[string]struct{}, len(ss))
for _, s := range ss {
m[s] = struct{}{}
}
return m
}
func sliceContains(ss []string, target string) bool {
for _, s := range ss {
if s == target {
return true
}
}
return false
}
func contains(s, sub string) bool {
return len(s) >= len(sub) && (s == sub || len(sub) == 0 ||
func() bool {
for i := 0; i <= len(s)-len(sub); i++ {
if s[i:i+len(sub)] == sub {
return true
}
}
return false
}())
}