e3c8979e8d
- 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>
395 lines
10 KiB
Go
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
|
|
}())
|
|
}
|