Files
zzet--gortex/internal/mcp/tools_find_files_test.go
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

143 lines
4.7 KiB
Go

package mcp
import (
"encoding/json"
"os"
"path/filepath"
"testing"
mcplib "github.com/mark3labs/mcp-go/mcp"
"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/indexer"
"github.com/zzet/gortex/internal/query"
)
type findFilesResult struct {
Files []struct {
Path string `json:"path"`
Language string `json:"language"`
ID string `json:"id"`
} `json:"files"`
Count int `json:"count"`
}
func setupFindFilesServer(t *testing.T) *Server {
t.Helper()
dir := t.TempDir()
write := func(rel, body string) {
full := filepath.Join(dir, filepath.FromSlash(rel))
require.NoError(t, os.MkdirAll(filepath.Dir(full), 0o755))
require.NoError(t, os.WriteFile(full, []byte(body), 0o644))
}
write("main.go", "package app\n\nfunc Main() {}\n")
write("internal/handler.go", "package internal\n\nfunc Handle() {}\n")
write("internal/sub/handler_test.go", "package sub\n\nfunc TestHandle() {}\n")
g := graph.New()
reg := testRegistry()
cfg := config.Default()
idx := indexer.New(g, reg, cfg.Index, zap.NewNop())
_, err := idx.Index(dir)
require.NoError(t, err)
eng := query.NewEngine(g)
return NewServer(eng, g, idx, nil, zap.NewNop(), nil)
}
func decodeFindFiles(t *testing.T, res *mcplib.CallToolResult) findFilesResult {
t.Helper()
require.False(t, res.IsError)
var out findFilesResult
require.NoError(t, json.Unmarshal([]byte(res.Content[0].(mcplib.TextContent).Text), &out))
return out
}
func TestFindFiles_ByName(t *testing.T) {
srv := setupFindFilesServer(t)
// A basename substring matches both handler files; the shallower
// one ranks first (same score, fewer path segments).
resp := decodeFindFiles(t, callTool(t, srv, "find_files", map[string]any{"query": "handler"}))
require.GreaterOrEqual(t, resp.Count, 2)
require.Equal(t, "internal/handler.go", resp.Files[0].Path)
// An exact basename only matches the one file.
exact := decodeFindFiles(t, callTool(t, srv, "find_files", map[string]any{"query": "handler.go"}))
require.Equal(t, 1, exact.Count)
require.Equal(t, "internal/handler.go", exact.Files[0].Path)
// main.go is reachable by name.
main := decodeFindFiles(t, callTool(t, srv, "find_files", map[string]any{"query": "main"}))
require.Equal(t, 1, main.Count)
require.Equal(t, "main.go", main.Files[0].Path)
}
func TestFindFiles_Glob(t *testing.T) {
srv := setupFindFilesServer(t)
resp := decodeFindFiles(t, callTool(t, srv, "find_files", map[string]any{"glob": "*_test.go"}))
require.Equal(t, 1, resp.Count)
require.Equal(t, "internal/sub/handler_test.go", resp.Files[0].Path)
}
func TestFindFiles_Fuzzy(t *testing.T) {
srv := setupFindFilesServer(t)
// "hndlr" is a subsequence of "handler.go" but not a substring.
none := decodeFindFiles(t, callTool(t, srv, "find_files", map[string]any{"query": "hndlr"}))
require.Equal(t, 0, none.Count)
fuzzy := decodeFindFiles(t, callTool(t, srv, "find_files",
map[string]any{"query": "hndlr", "fuzzy": true}))
require.GreaterOrEqual(t, fuzzy.Count, 1)
require.Equal(t, "internal/handler.go", fuzzy.Files[0].Path)
}
func TestFindFiles_PathScoping(t *testing.T) {
srv := setupFindFilesServer(t)
scoped := decodeFindFiles(t, callTool(t, srv, "find_files",
map[string]any{"query": "handler", "path": "internal/sub"}))
require.Equal(t, 1, scoped.Count)
require.Equal(t, "internal/sub/handler_test.go", scoped.Files[0].Path)
}
func TestFindFiles_RequiresArg(t *testing.T) {
srv := setupFindFilesServer(t)
bad := callTool(t, srv, "find_files", map[string]any{})
require.True(t, bad.IsError)
}
func TestScoreFilenameMatch(t *testing.T) {
cases := []struct {
query, base, rel string
fuzzy bool
want int
ok bool
}{
{"handler.go", "handler.go", "internal/handler.go", false, 100, true},
{"hand", "handler.go", "internal/handler.go", false, 70, true},
{"ndl", "handler.go", "internal/handler.go", false, 50, true},
{"internal", "handler.go", "internal/handler.go", false, 30, true},
{"nomatch", "handler.go", "internal/handler.go", false, 0, false},
{"hndlr", "handler.go", "internal/handler.go", true, 10, true},
{"hndlr", "handler.go", "internal/handler.go", false, 0, false},
}
for _, c := range cases {
got, ok := scoreFilenameMatch(c.query, c.base, c.rel, c.fuzzy)
require.Equal(t, c.ok, ok, "match? query=%q", c.query)
require.Equal(t, c.want, got, "score query=%q", c.query)
}
}
func TestIsSubsequence(t *testing.T) {
require.True(t, isSubsequence("", "anything"))
require.True(t, isSubsequence("abc", "aXbYcZ"))
require.True(t, isSubsequence("hndlr", "handler.go"))
require.False(t, isSubsequence("abc", "acb"))
require.False(t, isSubsequence("xyz", "abc"))
}