Files
fn_registry/cmd/fn/cmd_vault_test.go
T
egutierrez a802f59f55 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

319 lines
9.5 KiB
Go

package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"fn-registry/functions/infra"
"fn-registry/registry"
)
// fnBinDir holds the temp directory for the compiled fn binary.
// It is created by TestMain and cleaned up at test end.
var fnBinDir string
var fnBinPath string
// TestMain compiles the fn binary once before all tests.
func TestMain(m *testing.M) {
var err error
fnBinDir, err = os.MkdirTemp("", "fn-vault-test-*")
if err != nil {
fmt.Fprintf(os.Stderr, "create temp dir: %v\n", err)
os.Exit(1)
}
defer os.RemoveAll(fnBinDir)
fnBinPath = filepath.Join(fnBinDir, "fn")
// Find registry root by walking up from current directory.
regRoot, err := findRoot()
if err != nil {
fmt.Fprintf(os.Stderr, "find root: %v\n", err)
os.Exit(1)
}
cmd := exec.Command("go", "build", "-tags", "fts5", "-o", fnBinPath, ".")
cmd.Dir = filepath.Join(regRoot, "cmd", "fn")
if out, errB := cmd.CombinedOutput(); errB != nil {
fmt.Fprintf(os.Stderr, "build fn: %v\n%s\n", errB, out)
os.Exit(1)
}
os.Exit(m.Run())
}
func findRoot() (string, error) {
dir, err := os.Getwd()
if err != nil {
return "", err
}
for {
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir, nil
}
parent := filepath.Dir(dir)
if parent == dir {
return "", fmt.Errorf("could not find go.mod from %s", dir)
}
dir = parent
}
}
func ensureFnBin(t *testing.T) string {
t.Helper()
return fnBinPath
}
// setupTestRegistry creates a minimal registry root with:
// - registry.db (opened + migrations applied via registry.Open)
// - a project with a vault declared in vault.yaml
// - a vault directory with some test files
// - a symlink from projects/test_proj/vaults/test_vault -> vault dir
//
// Returns (repoRoot, vaultDir).
func setupTestRegistry(t *testing.T) (string, string) {
t.Helper()
repoRoot := t.TempDir()
// Create vault directory with files.
vaultDir := filepath.Join(t.TempDir(), "test_vault")
if err := os.MkdirAll(filepath.Join(vaultDir, "data", "raw"), 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(vaultDir, "data", "raw", "report.csv"),
[]byte("name,value\nfoo,1"), 0644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(vaultDir, "data", "raw", "notes.md"),
[]byte("# Notes\nsome text"), 0644); err != nil {
t.Fatal(err)
}
// Create project directory structure.
projDir := filepath.Join(repoRoot, "projects", "test_proj")
vaultsDir := filepath.Join(projDir, "vaults")
if err := os.MkdirAll(vaultsDir, 0755); err != nil {
t.Fatal(err)
}
// Create vault.yaml.
vaultYAML := "vaults:\n - name: test_vault\n description: Test vault for unit tests\n path: " + vaultDir + "\n tags: [test]\n"
if err := os.WriteFile(filepath.Join(vaultsDir, "vault.yaml"), []byte(vaultYAML), 0644); err != nil {
t.Fatal(err)
}
// Create project.md.
projMD := "---\nname: test_proj\ndescription: Test project\ntags: [test]\n---\n"
if err := os.WriteFile(filepath.Join(projDir, "project.md"), []byte(projMD), 0644); err != nil {
t.Fatal(err)
}
// Open registry.db (creates schema + runs migrations).
db, err := registry.Open(filepath.Join(repoRoot, "registry.db"))
if err != nil {
t.Fatalf("registry.Open: %v", err)
}
// Index so the vault is registered in registry.db.
if _, err := registry.Index(db, repoRoot); err != nil {
t.Fatalf("registry.Index: %v", err)
}
db.Close()
return repoRoot, vaultDir
}
// runFn runs the fn binary in repoRoot with the given args.
func runFn(t *testing.T, repoRoot string, args ...string) (string, string, int) {
t.Helper()
bin := ensureFnBin(t)
cmd := exec.Command(bin, args...)
cmd.Dir = repoRoot
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
code := 0
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
code = exitErr.ExitCode()
} else {
t.Logf("cmd error: %v", err)
}
}
return stdout.String(), stderr.String(), code
}
// TestVaultList verifies that 'fn vault list' shows the indexed vault.
func TestVaultList(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
out, stderr, code := runFn(t, repoRoot, "vault", "list")
if code != 0 {
t.Fatalf("fn vault list exit %d\nstderr: %s", code, stderr)
}
if !strings.Contains(out, "test_vault") {
t.Errorf("expected 'test_vault' in output, got:\n%s", out)
}
}
// TestVaultIndex verifies that 'fn vault index <name>' runs without error.
func TestVaultIndex(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
out, stderr, code := runFn(t, repoRoot, "vault", "index", "test_vault")
if code != 0 {
t.Fatalf("fn vault index exit %d\nstderr: %s\nstdout: %s", code, stderr, out)
}
if !strings.Contains(out, "indexed") {
t.Errorf("expected 'indexed' in output, got:\n%s", out)
}
}
// TestVaultSearchJSON verifies that 'fn vault search --json' returns valid JSON array.
func TestVaultSearchJSON(t *testing.T) {
repoRoot, vaultDir := setupTestRegistry(t)
// First index the vault so there is something to search.
if _, _, code := runFn(t, repoRoot, "vault", "index", "test_vault"); code != 0 {
t.Fatal("fn vault index failed")
}
// Seed some content into the vault index for the search to find.
db, err := infra.VaultIndexOpen(vaultDir)
if err != nil {
t.Fatalf("VaultIndexOpen: %v", err)
}
// Update content_text for FTS search.
db.Exec(`DELETE FROM files_fts WHERE rel_path = 'data/raw/report.csv'`)
db.Exec(`INSERT INTO files_fts(rel_path, content_text) VALUES ('data/raw/report.csv', 'foo report data')`)
db.Close()
out, stderr, code := runFn(t, repoRoot, "vault", "search", "report", "--json", "--vault", "test_vault")
if code != 0 {
t.Fatalf("fn vault search exit %d\nstderr: %s", code, stderr)
}
var result []map[string]interface{}
if err := json.Unmarshal([]byte(out), &result); err != nil {
t.Fatalf("output is not valid JSON: %v\nraw: %s", err, out)
}
// Should be a JSON array (possibly empty if search finds nothing, but must be valid).
t.Logf("search returned %d hits", len(result))
}
// TestVaultInfo verifies that 'fn vault info <name>' outputs vault stats.
func TestVaultInfo(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
// Index first.
if _, _, code := runFn(t, repoRoot, "vault", "index", "test_vault"); code != 0 {
t.Fatal("fn vault index failed")
}
out, stderr, code := runFn(t, repoRoot, "vault", "info", "test_vault")
if code != 0 {
t.Fatalf("fn vault info exit %d\nstderr: %s", code, stderr)
}
if !strings.Contains(out, "test_vault") {
t.Errorf("expected vault name in output, got:\n%s", out)
}
if !strings.Contains(out, "Files:") {
t.Errorf("expected 'Files:' in output, got:\n%s", out)
}
}
// TestFormatBytes verifies the formatBytes helper.
func TestFormatBytes(t *testing.T) {
cases := []struct {
input int64
expected string
}{
{500, "500 B"},
{1024, "1.0 KB"},
{1536, "1.5 KB"},
{1048576, "1.0 MB"},
{1073741824, "1.0 GB"},
}
for _, tc := range cases {
got := formatBytes(tc.input)
if got != tc.expected {
t.Errorf("formatBytes(%d) = %q, want %q", tc.input, got, tc.expected)
}
}
}
// TestVaultLayoutEnsure verifies that 'fn vault layout-ensure --dry-run' works.
func TestVaultLayoutEnsure(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
out, stderr, code := runFn(t, repoRoot, "vault", "layout-ensure", "test_vault", "--dry-run")
if code != 0 {
t.Fatalf("fn vault layout-ensure exit %d\nstderr: %s\nstdout: %s", code, stderr, out)
}
if !strings.Contains(out, "test_vault") {
t.Errorf("expected vault name in output, got:\n%s", out)
}
}
// TestVaultAggregate verifies that 'fn vault aggregate' runs without error on a clean registry.
func TestVaultAggregate(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
// Index first so there is something to aggregate.
if _, _, code := runFn(t, repoRoot, "vault", "index", "test_vault"); code != 0 {
t.Fatal("fn vault index failed")
}
_, stderr, code := runFn(t, repoRoot, "vault", "aggregate")
if code != 0 {
t.Fatalf("fn vault aggregate exit %d\nstderr: %s", code, stderr)
}
}
// TestVaultDoctor verifies that 'fn vault doctor' runs and reports on vaults.
func TestVaultDoctor(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
out, stderr, code := runFn(t, repoRoot, "vault", "doctor")
if code != 0 {
t.Fatalf("fn vault doctor exit %d\nstderr: %s", code, stderr)
}
if !strings.Contains(out, "test_vault") {
t.Errorf("expected 'test_vault' in doctor output, got:\n%s", out)
}
}
// TestVaultDedupe verifies that 'fn vault dedupe' runs without error after indexing.
func TestVaultDedupe(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
if _, _, code := runFn(t, repoRoot, "vault", "index", "test_vault"); code != 0 {
t.Fatal("fn vault index failed")
}
out, stderr, code := runFn(t, repoRoot, "vault", "dedupe", "test_vault")
if code != 0 {
t.Fatalf("fn vault dedupe exit %d\nstderr: %s", code, stderr)
}
// Should say "No duplicates" or show a table — either is fine.
_ = out
}
// TestVaultAuditDryRun verifies that 'fn vault audit --dry-run-layout --skip-profilers' works.
func TestVaultAuditDryRun(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
out, stderr, code := runFn(t, repoRoot, "vault", "audit", "test_vault",
"--dry-run-layout", "--skip-profilers")
// Exit 0 = fully ok; exit 4 = warnings (layout issues) — both acceptable here.
if code != 0 && code != 4 {
t.Fatalf("fn vault audit exit %d\nstderr: %s\nstdout: %s", code, stderr, out)
}
if !strings.Contains(out, "summary") {
t.Errorf("expected 'summary' section in audit output, got:\n%s", out)
}
}
// Suppress unused import for time.
var _ = time.Now