From a34e4109a8e79dfba4521d02d7be226d1f0e8b8d Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 28 Mar 2026 02:07:10 +0100 Subject: [PATCH] test: tests para parser de frontmatter e indexer completo 5 tests nuevos que validan: - Parseo de function con extraccion de ejemplo del body - Parseo de type algebraico con definition multilinea - Parseo de component con props, emits, has_state, framework - Error en archivos sin frontmatter valido - Ciclo completo: escribir .md -> indexar -> buscar por FTS Co-Authored-By: Claude Opus 4.6 (1M context) --- registry/parser_test.go | 238 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 registry/parser_test.go diff --git a/registry/parser_test.go b/registry/parser_test.go new file mode 100644 index 00000000..2cbbbaf8 --- /dev/null +++ b/registry/parser_test.go @@ -0,0 +1,238 @@ +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: "functions/components/DataTable.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) + } + + // 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") + } +}