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
151 lines
5.5 KiB
Go
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")
|
|
}
|