Files
zzet--gortex/internal/mcp/tools_analyze_connectivity_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

159 lines
6.1 KiB
Go

package mcp
import (
"context"
"encoding/json"
"testing"
mcplib "github.com/mark3labs/mcp-go/mcp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zzet/gortex/internal/graph"
)
// connectivityReport is the JSON shape of an analyze
// kind=connectivity_health response.
type connectivityReport struct {
NominalNodes int `json:"nominal_nodes"`
EffectiveNodes int `json:"effective_nodes"`
EffectiveRatio float64 `json:"effective_ratio"`
Isolated int `json:"isolated"`
Leaf int `json:"leaf"`
SourceOnly int `json:"source_only"`
SinkOnly int `json:"sink_only"`
ByKind []struct {
Kind string `json:"kind"`
Total int `json:"total"`
Isolated int `json:"isolated"`
Leaf int `json:"leaf"`
} `json:"by_kind"`
DeadWeightByFile []struct {
FilePath string `json:"file_path"`
Isolated int `json:"isolated"`
Leaf int `json:"leaf"`
DeadWeight int `json:"dead_weight"`
} `json:"dead_weight_by_file"`
Note string `json:"note"`
}
// connectivityFixture wires a Server around a hand-built graph with a
// known mix of connected, leaf, and isolated nodes:
//
// - good.go holds a call chain A->B->C: B is degree-2 (non-leaf),
// A and C are degree-1 leaves.
// - gap.go holds three functions with zero edges of any kind —
// three extraction gaps.
func connectivityFixture(t *testing.T) *Server {
t.Helper()
srv := concurrencyServer(t)
g := srv.graph
addFn(g, "good.go::A", "A", "good.go")
addFn(g, "good.go::B", "B", "good.go")
addFn(g, "good.go::C", "C", "good.go")
addEdge(g, "good.go::A", "good.go::B", graph.EdgeCalls, "good.go", 1)
addEdge(g, "good.go::B", "good.go::C", graph.EdgeCalls, "good.go", 2)
addFn(g, "gap.go::Lost1", "Lost1", "gap.go")
addFn(g, "gap.go::Lost2", "Lost2", "gap.go")
addFn(g, "gap.go::Lost3", "Lost3", "gap.go")
return srv
}
// TestAnalyzeConnectivityHealth_StructuredResult asserts the
// connectivity_health kind returns the structured report with the
// expected isolated / leaf / effective-vs-nominal counts and the
// per-file dead-weight attribution.
func TestAnalyzeConnectivityHealth_StructuredResult(t *testing.T) {
srv := connectivityFixture(t)
req := mcplib.CallToolRequest{}
req.Params.Name = "analyze"
req.Params.Arguments = map[string]any{"kind": "connectivity_health"}
res, err := srv.handleAnalyze(context.Background(), req)
require.NoError(t, err)
require.False(t, res.IsError)
var report connectivityReport
require.NoError(t, json.Unmarshal([]byte(res.Content[0].(mcplib.TextContent).Text), &report))
assert.Equal(t, 6, report.NominalNodes, "A,B,C + Lost1,Lost2,Lost3")
assert.Equal(t, 3, report.EffectiveNodes, "the A->B->C chain is connected")
assert.InDelta(t, 0.5, report.EffectiveRatio, 1e-9)
assert.Equal(t, 3, report.Isolated, "the three gap.go functions have zero edges")
assert.Equal(t, 2, report.Leaf, "chain ends A and C each have degree 1")
assert.Equal(t, 1, report.SourceOnly, "A (only outgoing)")
assert.Equal(t, 1, report.SinkOnly, "C (only incoming)")
// The dead-weight ranking localises the gap to gap.go: 3 isolated
// nodes outrank good.go's 2 leaves.
require.Len(t, report.DeadWeightByFile, 2)
assert.Equal(t, "gap.go", report.DeadWeightByFile[0].FilePath,
"the file with the most isolated/leaf nodes ranks first")
assert.Equal(t, 3, report.DeadWeightByFile[0].Isolated)
assert.Equal(t, 3, report.DeadWeightByFile[0].DeadWeight)
assert.Equal(t, "good.go", report.DeadWeightByFile[1].FilePath)
assert.Equal(t, 2, report.DeadWeightByFile[1].Leaf)
// The note must spell out the extraction-vs-dead-code distinction.
assert.Contains(t, report.Note, "dead_code")
assert.Contains(t, report.Note, "extract", "note must frame this as an extraction diagnostic")
}
// TestAnalyzeConnectivityHealth_DispatcherRoutes asserts the analyze
// switch accepts kind=connectivity_health — regression-protects the
// dispatcher wiring against a stray rename.
func TestAnalyzeConnectivityHealth_DispatcherRoutes(t *testing.T) {
srv, _ := setupTestServer(t)
req := mcplib.CallToolRequest{}
req.Params.Name = "analyze"
req.Params.Arguments = map[string]any{"kind": "connectivity_health"}
res, err := srv.handleAnalyze(context.Background(), req)
require.NoError(t, err)
require.False(t, res.IsError,
"dispatcher must route kind=connectivity_health without error; got %v", res)
}
// TestAnalyzeConnectivityHealth_GCXEncodesRecord asserts the GCX1 wire
// output carries the connectivity_health header and its scalar
// fields, so wire-format clients can decode the report.
func TestAnalyzeConnectivityHealth_GCXEncodesRecord(t *testing.T) {
srv := connectivityFixture(t)
req := mcplib.CallToolRequest{}
req.Params.Name = "analyze"
req.Params.Arguments = map[string]any{"kind": "connectivity_health", "format": "gcx"}
res, err := srv.handleAnalyze(context.Background(), req)
require.NoError(t, err)
require.False(t, res.IsError)
text := res.Content[0].(mcplib.TextContent).Text
assert.Contains(t, text, "analyze.connectivity_health")
assert.Contains(t, text, "isolated")
assert.Contains(t, text, "effective_nodes")
assert.Contains(t, text, "nominal_nodes")
}
// TestAnalyzeConnectivityHealth_LimitCapsFileRanking asserts the limit
// argument truncates the dead_weight_by_file ranking.
func TestAnalyzeConnectivityHealth_LimitCapsFileRanking(t *testing.T) {
srv := concurrencyServer(t)
g := srv.graph
// Three isolated functions, one per file.
addFn(g, "a.go::A", "A", "a.go")
addFn(g, "b.go::B", "B", "b.go")
addFn(g, "c.go::C", "C", "c.go")
req := mcplib.CallToolRequest{}
req.Params.Name = "analyze"
req.Params.Arguments = map[string]any{"kind": "connectivity_health", "limit": float64(2)}
res, err := srv.handleAnalyze(context.Background(), req)
require.NoError(t, err)
require.False(t, res.IsError)
var report connectivityReport
require.NoError(t, json.Unmarshal([]byte(res.Content[0].(mcplib.TextContent).Text), &report))
assert.Len(t, report.DeadWeightByFile, 2, "limit=2 caps the file ranking")
assert.Equal(t, 3, report.Isolated, "the isolated count is not affected by the file-ranking cap")
}