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

151 lines
5.5 KiB
Go

package mcp
import (
"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"
)
// flowBetweenJSON is the minimal shape of the JSON response returned by
// handleFlowBetween — declared here so the tier-surfacing tests can
// assert on EdgeStep fields without depending on the full dataflow
// package internals.
type flowBetweenJSON struct {
Total int `json:"total"`
Paths []struct {
Confidence float64 `json:"confidence"`
Edges []struct {
From string `json:"from"`
To string `json:"to"`
Kind string `json:"kind"`
Origin string `json:"origin"`
Tier string `json:"tier"`
} `json:"edges"`
} `json:"paths"`
}
// TestTier_FlowBetweenSurfacesPerStepTier asserts that every EdgeStep in
// the JSON response from flow_between carries both the raw Origin tier
// and the coarse `tier` label (ast / lsp / heuristic). This is the
// dataflow half of the N1 per-edge resolver-provenance contract.
func TestTier_FlowBetweenSurfacesPerStepTier(t *testing.T) {
srv := dataflowTestServer(t)
driverID := findFunctionID(t, srv, "Driver")
req := mcplib.CallToolRequest{}
req.Params.Arguments = map[string]any{
"source_id": driverID + "#param:input",
"sink_id": findFunctionID(t, srv, "Sink") + "#param:payload",
"max_depth": float64(10),
}
res, err := srv.handleFlowBetween(t.Context(), req)
require.NoError(t, err)
require.False(t, res.IsError)
var payload flowBetweenJSON
text := res.Content[0].(mcplib.TextContent).Text
require.NoError(t, json.Unmarshal([]byte(text), &payload))
require.Greater(t, payload.Total, 0, "fixture should produce at least one path; got: %s", text)
validTiers := map[string]bool{"lsp": true, "ast": true, "heuristic": true}
for _, p := range payload.Paths {
require.Greater(t, len(p.Edges), 0)
for _, e := range p.Edges {
assert.NotEmpty(t, e.Origin, "step %s→%s must carry Origin", e.From, e.To)
assert.NotEmpty(t, e.Tier, "step %s→%s must carry Tier", e.From, e.To)
assert.True(t, validTiers[e.Tier],
"tier %q must be one of lsp/ast/heuristic", e.Tier)
assert.Equal(t, graph.ResolvedBy(e.Origin), e.Tier,
"tier must equal ResolvedBy(origin) for step %s→%s", e.From, e.To)
}
}
}
// TestTier_FlowBetweenMinTierFiltersTraversal asserts that the new
// min_tier param prunes edges below the requested tier during BFS. The
// fixture has no LSP enrichment so min_tier=lsp_resolved drops every
// path; min_tier=ast_resolved preserves the AST-grade paths.
func TestTier_FlowBetweenMinTierFiltersTraversal(t *testing.T) {
srv := dataflowTestServer(t)
driverID := findFunctionID(t, srv, "Driver")
args := func(minTier string) map[string]any {
m := map[string]any{
"source_id": driverID + "#param:input",
"sink_id": findFunctionID(t, srv, "Sink") + "#param:payload",
"max_depth": float64(10),
}
if minTier != "" {
m["min_tier"] = minTier
}
return m
}
unfilteredReq := mcplib.CallToolRequest{}
unfilteredReq.Params.Arguments = args("")
unfilteredRes, err := srv.handleFlowBetween(t.Context(), unfilteredReq)
require.NoError(t, err)
require.False(t, unfilteredRes.IsError)
var unfiltered flowBetweenJSON
require.NoError(t, json.Unmarshal([]byte(unfilteredRes.Content[0].(mcplib.TextContent).Text), &unfiltered))
require.Greater(t, unfiltered.Total, 0, "baseline should yield paths")
astReq := mcplib.CallToolRequest{}
astReq.Params.Arguments = args(graph.OriginASTResolved)
astRes, err := srv.handleFlowBetween(t.Context(), astReq)
require.NoError(t, err)
require.False(t, astRes.IsError)
var ast flowBetweenJSON
require.NoError(t, json.Unmarshal([]byte(astRes.Content[0].(mcplib.TextContent).Text), &ast))
for _, p := range ast.Paths {
for _, e := range p.Edges {
assert.True(t, graph.MeetsMinTier(e.Origin, graph.OriginASTResolved),
"min_tier=ast_resolved leaked step origin=%s", e.Origin)
}
}
assert.LessOrEqual(t, ast.Total, unfiltered.Total,
"AST-filter must not yield more paths than unfiltered")
lspReq := mcplib.CallToolRequest{}
lspReq.Params.Arguments = args(graph.OriginLSPResolved)
lspRes, err := srv.handleFlowBetween(t.Context(), lspReq)
require.NoError(t, err)
require.False(t, lspRes.IsError)
var lsp flowBetweenJSON
require.NoError(t, json.Unmarshal([]byte(lspRes.Content[0].(mcplib.TextContent).Text), &lsp))
for _, p := range lsp.Paths {
for _, e := range p.Edges {
assert.True(t, graph.MeetsMinTier(e.Origin, graph.OriginLSPResolved),
"min_tier=lsp_resolved leaked step origin=%s", e.Origin)
}
}
assert.LessOrEqual(t, lsp.Total, ast.Total,
"LSP-filter must not yield more paths than AST-filter")
}
// TestTier_FlowBetweenGCXEmitsTierColumns asserts the GCX1 encoder
// surfaces the per-step `tiers` and `origins` sequences plus the
// per-path `worst_tier` field — the wire-format columns N1 promises.
func TestTier_FlowBetweenGCXEmitsTierColumns(t *testing.T) {
srv := dataflowTestServer(t)
driverID := findFunctionID(t, srv, "Driver")
req := mcplib.CallToolRequest{}
req.Params.Name = "flow_between"
req.Params.Arguments = map[string]any{
"source_id": driverID + "#param:input",
"sink_id": findFunctionID(t, srv, "Sink") + "#param:payload",
"format": "gcx",
}
res, err := srv.handleFlowBetween(t.Context(), req)
require.NoError(t, err)
require.False(t, res.IsError)
text := res.Content[0].(mcplib.TextContent).Text
require.Contains(t, text, "worst_tier")
require.Contains(t, text, "tiers")
require.Contains(t, text, "origins")
}