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
127 lines
3.7 KiB
Go
127 lines
3.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"
|
|
)
|
|
|
|
// tracePathTestServer indexes a Go file with a known call chain
|
|
// A→B→C→D plus an isolated function so we can exercise both the found
|
|
// and not-found branches of trace_path.
|
|
func tracePathTestServer(t *testing.T) *Server {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
src := `package main
|
|
|
|
func D() {}
|
|
|
|
func C() { D() }
|
|
|
|
func B() { C() }
|
|
|
|
func A() { B() }
|
|
|
|
func Isolated() {}
|
|
`
|
|
require.NoError(t, os.WriteFile(filepath.Join(dir, "main.go"), []byte(src), 0o644))
|
|
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)
|
|
idx.ResolveAll()
|
|
eng := query.NewEngine(g)
|
|
return NewServer(eng, g, idx, nil, zap.NewNop(), nil)
|
|
}
|
|
|
|
func TestHandleTracePath_FoundChain(t *testing.T) {
|
|
srv := tracePathTestServer(t)
|
|
req := mcplib.CallToolRequest{}
|
|
req.Params.Arguments = map[string]any{
|
|
"source_id": findFunctionID(t, srv, "A"),
|
|
"sink_id": findFunctionID(t, srv, "D"),
|
|
}
|
|
res, err := srv.handleTracePath(t.Context(), req)
|
|
require.NoError(t, err)
|
|
require.False(t, res.IsError, "tool errored: %v", res)
|
|
|
|
var payload struct {
|
|
Found bool `json:"found"`
|
|
Paths []struct {
|
|
Nodes []string `json:"nodes"`
|
|
Length int `json:"length"`
|
|
} `json:"paths"`
|
|
}
|
|
text := res.Content[0].(mcplib.TextContent).Text
|
|
require.NoError(t, json.Unmarshal([]byte(text), &payload))
|
|
require.True(t, payload.Found, "expected a path; got %s", text)
|
|
require.Len(t, payload.Paths, 1)
|
|
require.Equal(t, 3, payload.Paths[0].Length, "A→B→C→D is 3 hops: %s", text)
|
|
require.Len(t, payload.Paths[0].Nodes, 4)
|
|
}
|
|
|
|
func TestHandleTracePath_NotFoundDiagnosis(t *testing.T) {
|
|
srv := tracePathTestServer(t)
|
|
// A reaches B,C,D; Isolated has no incoming call edges → sink_no_in.
|
|
req := mcplib.CallToolRequest{}
|
|
req.Params.Arguments = map[string]any{
|
|
"source_id": findFunctionID(t, srv, "A"),
|
|
"sink_id": findFunctionID(t, srv, "Isolated"),
|
|
}
|
|
res, err := srv.handleTracePath(t.Context(), req)
|
|
require.NoError(t, err)
|
|
require.False(t, res.IsError)
|
|
|
|
var payload struct {
|
|
Found bool `json:"found"`
|
|
Gap *struct {
|
|
Reason string `json:"reason"`
|
|
Message string `json:"message"`
|
|
} `json:"gap"`
|
|
}
|
|
text := res.Content[0].(mcplib.TextContent).Text
|
|
require.NoError(t, json.Unmarshal([]byte(text), &payload))
|
|
require.False(t, payload.Found)
|
|
require.NotNil(t, payload.Gap, "expected a gap diagnosis: %s", text)
|
|
require.Equal(t, "sink_no_in_edges", payload.Gap.Reason)
|
|
require.NotEmpty(t, payload.Gap.Message)
|
|
}
|
|
|
|
func TestHandleTracePath_MissingSource(t *testing.T) {
|
|
srv := tracePathTestServer(t)
|
|
req := mcplib.CallToolRequest{}
|
|
req.Params.Arguments = map[string]any{"sink_id": "anything"}
|
|
res, err := srv.handleTracePath(t.Context(), req)
|
|
require.NoError(t, err)
|
|
require.True(t, res.IsError, "expected error for missing source_id")
|
|
}
|
|
|
|
func TestHandleTracePath_GCXFormat(t *testing.T) {
|
|
srv := tracePathTestServer(t)
|
|
req := mcplib.CallToolRequest{}
|
|
req.Params.Name = "trace_path"
|
|
req.Params.Arguments = map[string]any{
|
|
"source_id": findFunctionID(t, srv, "A"),
|
|
"sink_id": findFunctionID(t, srv, "D"),
|
|
"format": "gcx",
|
|
}
|
|
res, err := srv.handleTracePath(t.Context(), req)
|
|
require.NoError(t, err)
|
|
require.False(t, res.IsError)
|
|
text := res.Content[0].(mcplib.TextContent).Text
|
|
require.Contains(t, text, "trace_path.summary")
|
|
require.Contains(t, text, "trace_path.paths")
|
|
}
|