package registry import ( "os" "path/filepath" "testing" ) const functionMD = `--- name: filter_slice kind: function lang: go domain: core version: "1.0.0" purity: pure signature: "func FilterSlice[T any](xs []T, pred func(T) bool) []T" description: "Filtra un slice aplicando un predicado sin mutar el original." tags: [slice, functional, generic] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "" imports: [] tested: false tests: [] test_file_path: "" file_path: "functions/core/filter_slice.go" --- ## Ejemplo ` + "```go" + ` evens := FilterSlice([]int{1, 2, 3, 4}, func(n int) bool { return n%2 == 0 }) ` + "```" + ` ` const typeMD = `--- name: ohlcv lang: go domain: finance version: "1.0.0" algebraic: product definition: | type OHLCV struct { Open float64 High float64 Low float64 Close float64 Volume float64 } description: "Vela de mercado con precios OHLCV." tags: [finance, market, candle] uses_types: [] file_path: "types/finance/ohlcv.go" --- ## Notas Tipo producto. ` const componentMD = `--- name: DataTable kind: component lang: typescript domain: core version: "1.0.0" purity: impure signature: "DataTable(props: { data: T[] }): JSX.Element" description: "Tabla de datos generica." tags: [table, ui] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "" imports: [react] tested: false tests: [] test_file_path: "" file_path: "frontend/functions/ui/data_table.tsx" props: - name: data type: "T[]" required: true description: "Array de datos" emits: [onRowClick] has_state: true framework: react variant: [default, compact] --- ` func writeTempFile(t *testing.T, dir, name, content string) string { t.Helper() path := filepath.Join(dir, name) os.MkdirAll(filepath.Dir(path), 0o755) if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatal(err) } return path } func TestParseFunctionMD(t *testing.T) { path := writeTempFile(t, t.TempDir(), "filter_slice.md", functionMD) f, err := ParseFunctionMD(path, "") if err != nil { t.Fatal(err) } if f.ID != "filter_slice_go_core" { t.Errorf("id: got %q", f.ID) } if f.Kind != KindFunction { t.Errorf("kind: got %q", f.Kind) } if f.Purity != PurityPure { t.Errorf("purity: got %q", f.Purity) } if len(f.Tags) != 3 { t.Errorf("tags: got %d, want 3", len(f.Tags)) } if f.Example == "" { t.Error("example should be extracted from body") } } func TestParseTypeMD(t *testing.T) { path := writeTempFile(t, t.TempDir(), "ohlcv.md", typeMD) typ, err := ParseTypeMD(path, "") if err != nil { t.Fatal(err) } if typ.ID != "ohlcv_go_finance" { t.Errorf("id: got %q", typ.ID) } if typ.Algebraic != AlgebraicProduct { t.Errorf("algebraic: got %q", typ.Algebraic) } if typ.Definition == "" { t.Error("definition should not be empty") } } func TestParseComponentMD(t *testing.T) { path := writeTempFile(t, t.TempDir(), "DataTable.md", componentMD) f, err := ParseFunctionMD(path, "") if err != nil { t.Fatal(err) } if f.Kind != KindComponent { t.Errorf("kind: got %q", f.Kind) } if f.Framework != "react" { t.Errorf("framework: got %q", f.Framework) } if len(f.Props) != 1 { t.Errorf("props: got %d, want 1", len(f.Props)) } if f.HasState == nil || !*f.HasState { t.Error("has_state should be true") } if len(f.Emits) != 1 || f.Emits[0] != "onRowClick" { t.Errorf("emits: got %v", f.Emits) } } func TestParseMissingFrontmatter(t *testing.T) { path := writeTempFile(t, t.TempDir(), "bad.md", "# No frontmatter here\n") _, err := ParseFunctionMD(path, "") if err == nil { t.Error("expected error for missing frontmatter") } } func TestIndexFullCycle(t *testing.T) { root := t.TempDir() // Create function .md writeTempFile(t, root, "functions/core/filter_slice.md", functionMD) // Create type .md writeTempFile(t, root, "types/finance/ohlcv.md", typeMD) dbPath := filepath.Join(root, "registry.db") db, err := Open(dbPath) if err != nil { t.Fatal(err) } defer db.Close() result, err := Index(db, root) if err != nil { t.Fatal(err) } if result.Functions != 1 { t.Errorf("functions indexed: got %d, want 1", result.Functions) } if result.Types != 1 { t.Errorf("types indexed: got %d, want 1", result.Types) } if len(result.Errors) != 0 { t.Errorf("unexpected errors: %v", result.Errors) } if len(result.ValidationErrors) != 0 { t.Errorf("unexpected validation errors: %v", result.ValidationErrors) } // Verify searchable fns, err := db.SearchFunctions("filter", "", "", "", "") if err != nil { t.Fatal(err) } if len(fns) != 1 { t.Errorf("search 'filter': got %d, want 1", len(fns)) } ts, err := db.SearchTypes("ohlcv", "", "") if err != nil { t.Fatal(err) } if len(ts) != 1 { t.Errorf("search 'ohlcv': got %d, want 1", len(ts)) } // Re-index should be idempotent result2, err := Index(db, root) if err != nil { t.Fatal(err) } if result2.Functions != 1 || result2.Types != 1 { t.Error("re-index should produce same counts") } } const invalidPipelineMD = `--- name: bad_pipeline kind: pipeline lang: go domain: core version: "1.0.0" purity: pure description: "Pipeline puro sin uses_functions — debe fallar." tags: [] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "" imports: [] tested: false tests: [] test_file_path: "" file_path: "functions/pipelines/bad.go" --- ` func TestIndexRejectsInvalid(t *testing.T) { root := t.TempDir() // Valid function writeTempFile(t, root, "functions/core/filter_slice.md", functionMD) // Invalid pipeline (pure + empty uses_functions) writeTempFile(t, root, "functions/pipelines/bad.md", invalidPipelineMD) // Valid type writeTempFile(t, root, "types/finance/ohlcv.md", typeMD) dbPath := filepath.Join(root, "registry.db") db, err := Open(dbPath) if err != nil { t.Fatal(err) } defer db.Close() result, err := Index(db, root) if err != nil { t.Fatal(err) } // Valid entries should be indexed if result.Functions != 1 { t.Errorf("functions: got %d, want 1 (only the valid one)", result.Functions) } if result.Types != 1 { t.Errorf("types: got %d, want 1", result.Types) } // Invalid pipeline should produce validation error if len(result.ValidationErrors) == 0 { t.Error("expected validation errors for invalid pipeline") } }