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

173 lines
6.1 KiB
Go

package mcp
import (
"context"
"encoding/json"
"testing"
"github.com/mark3labs/mcp-go/mcp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zzet/gortex/internal/analysis"
"github.com/zzet/gortex/internal/graph"
)
// newArchHierarchyTestServer builds a server over a two-package fixture
// graph with cached communities, so get_architecture can be exercised
// with a resolution tier.
//
// pkg/auth/login.go :: Login, Logout (community-0)
// pkg/store/db.go :: Save, Load (community-1)
//
// auth -> store carries 3 underlying call edges.
func newArchHierarchyTestServer(t *testing.T) *Server {
t.Helper()
g := graph.New()
add := func(id, file string) {
g.AddNode(&graph.Node{ID: id, Name: id, Kind: graph.KindFunction, FilePath: file, Language: "go", RepoPrefix: "demo"})
}
add("Login", "pkg/auth/login.go")
add("Logout", "pkg/auth/login.go")
add("Save", "pkg/store/db.go")
add("Load", "pkg/store/db.go")
g.AddEdge(&graph.Edge{From: "Login", To: "Logout", Kind: graph.EdgeCalls})
g.AddEdge(&graph.Edge{From: "Login", To: "Save", Kind: graph.EdgeCalls})
g.AddEdge(&graph.Edge{From: "Login", To: "Load", Kind: graph.EdgeCalls})
g.AddEdge(&graph.Edge{From: "Logout", To: "Save", Kind: graph.EdgeCalls})
g.AddEdge(&graph.Edge{From: "Save", To: "Load", Kind: graph.EdgeCalls})
s := &Server{
graph: g,
session: newSessionState(),
tokenStats: &tokenStats{},
symHistory: &symbolHistory{entries: make(map[string][]SymbolModification)},
sessions: newSessionMap(),
toolScopes: newScopeRegistry(),
}
s.analysisMu.Lock()
s.communities = &analysis.CommunityResult{
Communities: []analysis.Community{
{ID: "community-0", Label: "auth", Members: []string{"Login", "Logout"}, Size: 2},
{ID: "community-1", Label: "storage", Members: []string{"Save", "Load"}, Size: 2},
},
NodeToComm: map[string]string{
"Login": "community-0", "Logout": "community-0",
"Save": "community-1", "Load": "community-1",
},
}
s.analysisMu.Unlock()
return s
}
func callGetArchitecture(t *testing.T, s *Server, args map[string]any) map[string]any {
t.Helper()
req := mcp.CallToolRequest{}
req.Params.Arguments = args
res, err := s.handleGetArchitecture(context.Background(), req)
require.NoError(t, err)
require.NotNil(t, res)
require.False(t, res.IsError)
tc, ok := res.Content[0].(mcp.TextContent)
require.True(t, ok)
var m map[string]any
require.NoError(t, json.Unmarshal([]byte(tc.Text), &m))
return m
}
func TestArchitecture_NoResolutionOmitsHierarchy(t *testing.T) {
s := newArchHierarchyTestServer(t)
out := callGetArchitecture(t, s, map[string]any{})
_, present := out["hierarchy"]
assert.False(t, present, "hierarchy block must be absent when no resolution is requested")
}
func TestArchitecture_PackageResolution(t *testing.T) {
s := newArchHierarchyTestServer(t)
out := callGetArchitecture(t, s, map[string]any{"resolution": "package"})
h, ok := out["hierarchy"].(map[string]any)
require.True(t, ok, "hierarchy block must be present for resolution=package")
assert.Equal(t, "package", h["level"])
assert.EqualValues(t, 4, h["leaf_count"])
assert.EqualValues(t, 2, h["node_count"])
nodes, _ := h["nodes"].([]any)
require.Len(t, nodes, 2)
ids := map[string]int{}
for _, n := range nodes {
row := n.(map[string]any)
ids[row["id"].(string)] = int(row["leaf_count"].(float64))
// No function-leaf node IDs in the rollup.
assert.Nil(t, s.graph.GetNode(row["id"].(string)),
"rollup node %q must not be a base-graph leaf", row["id"])
}
assert.Equal(t, 2, ids["package:pkg/auth"])
assert.Equal(t, 2, ids["package:pkg/store"])
edges, _ := h["edges"].([]any)
require.Len(t, edges, 1)
edge := edges[0].(map[string]any)
assert.Equal(t, "package:pkg/auth", edge["from"])
assert.Equal(t, "package:pkg/store", edge["to"])
// Weight = count of underlying function-level call edges crossing
// the two packages (Login->Save, Login->Load, Logout->Save).
assert.EqualValues(t, 3, edge["weight"])
selfLoops, _ := h["self_loops"].(map[string]any)
assert.EqualValues(t, 1, selfLoops["package:pkg/auth"])
assert.EqualValues(t, 1, selfLoops["package:pkg/store"])
}
func TestArchitecture_ServiceResolution(t *testing.T) {
s := newArchHierarchyTestServer(t)
out := callGetArchitecture(t, s, map[string]any{"resolution": "service"})
h, ok := out["hierarchy"].(map[string]any)
require.True(t, ok)
assert.Equal(t, "service", h["level"])
nodes, _ := h["nodes"].([]any)
require.Len(t, nodes, 2, "service tier groups the two communities")
for _, n := range nodes {
row := n.(map[string]any)
assert.Contains(t, []any{"service:community-0", "service:community-1"}, row["id"])
}
edges, _ := h["edges"].([]any)
require.Len(t, edges, 1)
edge := edges[0].(map[string]any)
assert.EqualValues(t, 3, edge["weight"],
"a module->module edge weight equals the count of underlying function-level call edges")
}
func TestArchitecture_SystemResolution(t *testing.T) {
s := newArchHierarchyTestServer(t)
out := callGetArchitecture(t, s, map[string]any{"resolution": "system"})
h := out["hierarchy"].(map[string]any)
assert.Equal(t, "system", h["level"])
nodes, _ := h["nodes"].([]any)
require.Len(t, nodes, 1, "single-repo fixture rolls up to one system node")
assert.Equal(t, "system:demo", nodes[0].(map[string]any)["id"])
}
func TestArchitecture_UnknownResolutionIsError(t *testing.T) {
s := newArchHierarchyTestServer(t)
req := mcp.CallToolRequest{}
req.Params.Arguments = map[string]any{"resolution": "galaxy"}
res, err := s.handleGetArchitecture(context.Background(), req)
require.NoError(t, err)
require.True(t, res.IsError, "an unknown resolution tier must return a clean error")
}
func TestArchitecture_HierarchyDeterministic(t *testing.T) {
s := newArchHierarchyTestServer(t)
first := callGetArchitecture(t, s, map[string]any{"resolution": "package"})
firstJSON, _ := json.Marshal(first["hierarchy"])
for i := 0; i < 5; i++ {
again := callGetArchitecture(t, s, map[string]any{"resolution": "package"})
againJSON, _ := json.Marshal(again["hierarchy"])
assert.JSONEq(t, string(firstJSON), string(againJSON),
"the hierarchy rollup must be stable across runs")
}
}