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

196 lines
6.4 KiB
Go

package mcp
import (
"context"
"encoding/json"
"testing"
mcplib "github.com/mark3labs/mcp-go/mcp"
"github.com/zzet/gortex/internal/graph"
)
func callAnalyze(t *testing.T, srv *Server, kind string, extra map[string]any) map[string]any {
t.Helper()
args := map[string]any{"kind": kind}
for k, v := range extra {
args[k] = v
}
req := mcplib.CallToolRequest{}
req.Params.Name = "analyze"
req.Params.Arguments = args
res, err := srv.handleAnalyze(context.Background(), req)
if err != nil {
t.Fatalf("handleAnalyze(%s): %v", kind, err)
}
if res.IsError {
t.Fatalf("analyze(%s) returned error: %+v", kind, res.Content)
}
textBlock := res.Content[0].(mcplib.TextContent)
var out map[string]any
if err := json.Unmarshal([]byte(textBlock.Text), &out); err != nil {
t.Fatalf("analyze(%s) json: %v\n%s", kind, err, textBlock.Text)
}
return out
}
// addEmitToKindString builds a (caller, KindString) emit pair with
// the given context and meta. Used by the registry-downstream
// analyzers' tests.
func addEmitToKindString(g graph.Store, caller, strID, value, ctx string, nodeMeta, edgeMeta map[string]any) {
meta := map[string]any{
"context": ctx,
"value": value,
}
for k, v := range nodeMeta {
meta[k] = v
}
g.AddNode(&graph.Node{
ID: strID,
Kind: graph.KindString,
Name: value,
Language: "go",
Meta: meta,
})
em := map[string]any{"context": ctx}
for k, v := range edgeMeta {
em[k] = v
}
g.AddEdge(&graph.Edge{
From: caller,
To: strID,
Kind: graph.EdgeEmits,
Meta: em,
})
}
// analyze kind=log_events aggregates EdgeEmits → KindString
// context="log_message" by literal value.
func TestAnalyzeLogEvents_AggregatesByValue(t *testing.T) {
srv, _ := setupTestServer(t)
g := srv.graph
addEmitToKindString(g, "f.go::A", "string::log_message::user.signup", "user.signup", "log_message",
map[string]any{"level": "log", "event": "event::log::user.signup"},
map[string]any{"method": "Info", "level": "log"})
addEmitToKindString(g, "f.go::B", "string::log_message::user.signup", "user.signup", "log_message",
map[string]any{"level": "log"},
map[string]any{"method": "Info", "level": "log"})
out := callAnalyze(t, srv, "log_events", nil)
events, _ := out["events"].([]any)
if len(events) != 1 {
t.Fatalf("expected 1 log_event row, got %d", len(events))
}
row := events[0].(map[string]any)
if row["value"] != "user.signup" {
t.Errorf("value = %v", row["value"])
}
if emits, _ := row["emits"].(float64); emits != 2 {
t.Errorf("emits = %v, want 2", emits)
}
if level, _ := row["level"].(string); level != "log" {
t.Errorf("level = %q", level)
}
}
func TestAnalyzeLogEvents_IgnoresNonLogContexts(t *testing.T) {
srv, _ := setupTestServer(t)
g := srv.graph
addEmitToKindString(g, "f.go::A", "string::error_msg::bad", "bad", "error_msg", nil, nil)
out := callAnalyze(t, srv, "log_events", nil)
events, _ := out["events"].([]any)
if len(events) != 0 {
t.Errorf("expected 0 rows (no log_message strings), got %d", len(events))
}
}
func TestAnalyzeLogEvents_LevelFilter(t *testing.T) {
srv, _ := setupTestServer(t)
g := srv.graph
addEmitToKindString(g, "f.go::A", "string::log_message::user.signup", "user.signup", "log_message",
nil, map[string]any{"method": "Info", "level": "info"})
addEmitToKindString(g, "f.go::B", "string::log_message::payment.failed", "payment.failed", "log_message",
nil, map[string]any{"method": "Error", "level": "error"})
out := callAnalyze(t, srv, "log_events", map[string]any{"level": "error"})
events, _ := out["events"].([]any)
if len(events) != 1 {
t.Fatalf("expected 1 row for level=error, got %d", len(events))
}
row := events[0].(map[string]any)
if row["value"] != "payment.failed" {
t.Errorf("value = %v", row["value"])
}
}
// analyze kind=sql_rebuild walks KindString context="sql" and
// derives KindTable / EdgeQueries.
func TestAnalyzeSQLRebuild_DerivesTables(t *testing.T) {
srv, _ := setupTestServer(t)
g := srv.graph
g.AddNode(&graph.Node{ID: "f.go::Run", Kind: graph.KindFunction, Name: "Run"})
addEmitToKindString(g, "f.go::Run", "string::sql::SELECT id FROM users", "SELECT id FROM users", "sql",
map[string]any{"dialect": "postgres"},
map[string]any{"dialect": "postgres"})
out := callAnalyze(t, srv, "sql_rebuild", nil)
if got, _ := out["strings_visited"].(float64); got != 1 {
t.Errorf("strings_visited = %v, want 1", got)
}
if got, _ := out["tables_created"].(float64); got != 1 {
t.Errorf("tables_created = %v, want 1", got)
}
if got, _ := out["query_edges_created"].(float64); got != 1 {
t.Errorf("query_edges_created = %v, want 1", got)
}
// Verify the table actually landed in the graph.
if n := g.GetNode("db::postgres::users"); n == nil {
t.Errorf("expected db::postgres::users node to be created")
}
}
// error_surface now includes error_msg KindString literals.
func TestAnalyzeErrorSurface_IncludesErrorMsgRegistry(t *testing.T) {
srv, _ := setupTestServer(t)
g := srv.graph
addThrowsEdge(g, "f.go::DoThing", "external::error", "f.go", 10)
addEmitToKindString(g, "f.go::DoThing", "string::error_msg::bad token",
"bad token", "error_msg", nil, nil)
addEmitToKindString(g, "f.go::DoThing", "string::error_msg::unauthorized",
"unauthorized", "error_msg", nil, nil)
// A non-error_msg KindString shouldn't leak into the registry.
addEmitToKindString(g, "f.go::DoThing", "string::metric::api.calls",
"api.calls", "metric", nil, nil)
out := callAnalyzeErrorSurface(t, srv, map[string]any{})
throwers, _ := out["throwers"].([]any)
if len(throwers) != 1 {
t.Fatalf("expected 1 thrower, got %d", len(throwers))
}
row := throwers[0].(map[string]any)
msgs, _ := row["error_msgs"].([]any)
if len(msgs) != 2 {
t.Fatalf("expected 2 error_msgs, got %d (%v)", len(msgs), msgs)
}
// Sorted alphabetically.
if msgs[0] != "bad token" || msgs[1] != "unauthorized" {
t.Errorf("error_msgs = %v, want [bad token, unauthorized] sorted", msgs)
}
}
func TestAnalyzeErrorSurface_ErrorMsgsOmittedWhenEmpty(t *testing.T) {
srv, _ := setupTestServer(t)
g := srv.graph
addThrowsEdge(g, "f.go::DoThing", "external::error", "f.go", 10)
out := callAnalyzeErrorSurface(t, srv, map[string]any{})
throwers, _ := out["throwers"].([]any)
if len(throwers) != 1 {
t.Fatalf("expected 1 thrower, got %d", len(throwers))
}
row := throwers[0].(map[string]any)
if _, present := row["error_msgs"]; present {
t.Errorf("error_msgs should be omitted when empty, got %v", row["error_msgs"])
}
}