package indexer import ( "os/exec" "path/filepath" "testing" "github.com/stretchr/testify/require" "go.uber.org/zap" "github.com/zzet/gortex/internal/config" "github.com/zzet/gortex/internal/graph" "github.com/zzet/gortex/internal/parser" "github.com/zzet/gortex/internal/parser/languages" ) func TestStripBOM(t *testing.T) { body := []byte("package main\n") require.Equal(t, body, stripBOM(append([]byte{0xEF, 0xBB, 0xBF}, body...))) // UTF-8 require.Equal(t, body, stripBOM(append([]byte{0xFF, 0xFE}, body...))) // UTF-16LE require.Equal(t, body, stripBOM(append([]byte{0xFE, 0xFF}, body...))) // UTF-16BE require.Equal(t, body, stripBOM(body)) // no BOM require.Equal(t, []byte{}, stripBOM([]byte{})) // empty } func TestTransformPipeline_BOMStripIsBuiltIn(t *testing.T) { p := newTransformPipeline(nil, zap.NewNop()) withBOM := append([]byte{0xEF, 0xBB, 0xBF}, []byte("package main\n")...) require.Equal(t, []byte("package main\n"), p.run("x.go", withBOM)) } func TestTransformPipeline_NilSafe(t *testing.T) { var p *transformPipeline require.Equal(t, []byte("x"), p.run("a.go", []byte("x"))) require.Equal(t, "", p.languageFor("a.go")) } func TestTransformPipeline_RuleWithNoCommandIgnored(t *testing.T) { p := newTransformPipeline([]config.TransformRule{{Name: "broken"}}, zap.NewNop()) require.Len(t, p.transforms, 1) // only the built-in BOM stripper } func TestCommandTransform_Matches(t *testing.T) { c := newCommandTransform(config.TransformRule{Extensions: []string{".SVG", ".pdf"}, Command: []string{"cat"}}) require.True(t, c.matches("logo.svg")) // case-insensitive require.True(t, c.matches("doc.pdf")) require.False(t, c.matches("main.go")) all := newCommandTransform(config.TransformRule{Command: []string{"cat"}}) // no extensions → all require.True(t, all.matches("anything.xyz")) } func TestCommandTransform_Apply(t *testing.T) { requireBin(t, "sed") c := newCommandTransform(config.TransformRule{ Command: []string{"sed", "s/foo/bar/g"}, }) out, err := c.apply("x.txt", []byte("foo foo")) require.NoError(t, err) require.Equal(t, "bar bar", string(out)) } func TestCommandTransform_ApplyError(t *testing.T) { c := newCommandTransform(config.TransformRule{Command: []string{"gortex-no-such-binary-xyz"}}) _, err := c.apply("x.txt", []byte("data")) require.Error(t, err) } func TestTransformPipeline_FailingCommandKeepsBytes(t *testing.T) { // A failing transform must not drop the file — earlier bytes win. p := newTransformPipeline([]config.TransformRule{{ Name: "broken", Command: []string{"gortex-no-such-binary-xyz"}, }}, zap.NewNop()) require.Equal(t, []byte("untouched"), p.run("a.go", []byte("untouched"))) } // TestIndex_TransformPipeline proves a transform rewrites bytes before // extraction: the renamed symbol is the one that lands in the graph. func TestIndex_TransformPipeline(t *testing.T) { requireBin(t, "sed") dir := t.TempDir() writeFile(t, filepath.Join(dir, "main.go"), "package main\n\nfunc Original() {}\n") g := graph.New() reg := parser.NewRegistry() reg.Register(languages.NewGoExtractor()) cfg := config.Default().Index cfg.Workers = 1 cfg.Transforms = []config.TransformRule{{ Name: "rename", Extensions: []string{".go"}, Command: []string{"sed", "s/Original/Renamed/g"}, }} idx := New(g, reg, cfg, zap.NewNop()) _, err := idx.Index(dir) require.NoError(t, err) require.NotEmpty(t, g.FindNodesByName("Renamed"), "transformed symbol must be indexed") require.Empty(t, g.FindNodesByName("Original"), "pre-transform symbol must not be indexed") } // TestIndex_TransformAsLanguage proves a transform can re-type a file // whose extension is not natively indexed. func TestIndex_TransformAsLanguage(t *testing.T) { requireBin(t, "cat") dir := t.TempDir() writeFile(t, filepath.Join(dir, "snippet.gocode"), "package main\n\nfunc Snippet() {}\n") g := graph.New() reg := parser.NewRegistry() reg.Register(languages.NewGoExtractor()) cfg := config.Default().Index cfg.Workers = 1 cfg.Transforms = []config.TransformRule{{ Name: "gocode-as-go", Extensions: []string{".gocode"}, Command: []string{"cat"}, AsLanguage: "go", }} idx := New(g, reg, cfg, zap.NewNop()) result, err := idx.Index(dir) require.NoError(t, err) require.Equal(t, 1, result.FileCount) require.NotEmpty(t, g.FindNodesByName("Snippet"), ".gocode file must be indexed as Go") } func TestEffectiveLanguage(t *testing.T) { g := graph.New() reg := parser.NewRegistry() reg.Register(languages.NewGoExtractor()) cfg := config.Default().Index cfg.Transforms = []config.TransformRule{{ Name: "pdf", Extensions: []string{".pdf"}, Command: []string{"cat"}, AsLanguage: "markdown", }} idx := New(g, reg, cfg, zap.NewNop()) lang, ok := idx.effectiveLanguage("main.go", nil) require.True(t, ok) require.Equal(t, "go", lang) lang, ok = idx.effectiveLanguage("doc.pdf", nil) require.True(t, ok) require.Equal(t, "markdown", lang) _, ok = idx.effectiveLanguage("mystery.zzz", nil) require.False(t, ok) } func requireBin(t *testing.T, name string) { t.Helper() if _, err := exec.LookPath(name); err != nil { t.Skipf("%s not available on PATH", name) } }