zzet--gortex
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
139 行
5.6 KiB
Go
139 行
5.6 KiB
Go
package mcp
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
mcplib "github.com/mark3labs/mcp-go/mcp"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/zzet/gortex/internal/config"
|
|
"github.com/zzet/gortex/internal/graph"
|
|
"github.com/zzet/gortex/internal/query"
|
|
"github.com/zzet/gortex/internal/search"
|
|
)
|
|
|
|
// equivalenceTestServer builds a single-repo server with no LLM
|
|
// provider, so any expansion observed comes purely from the
|
|
// deterministic equivalence channel.
|
|
func equivalenceTestServer(t *testing.T, names []string, equivEnabled *bool) *Server {
|
|
t.Helper()
|
|
g := graph.New()
|
|
bm := search.NewBM25()
|
|
for i, n := range names {
|
|
id := "pkg/" + n + ".go::" + n
|
|
g.AddNode(&graph.Node{
|
|
ID: id, Kind: graph.KindFunction, Name: n,
|
|
FilePath: "pkg/" + n + ".go", StartLine: i + 1, EndLine: i + 5, Language: "go",
|
|
})
|
|
bm.Add(id, n, "pkg/"+n+".go", "")
|
|
}
|
|
eng := query.NewEngine(g)
|
|
eng.SetSearch(bm)
|
|
srv := NewServer(eng, g, nil, nil, zap.NewNop(), nil)
|
|
srv.SetSearchConfig(config.SearchConfig{EquivalenceClasses: equivEnabled})
|
|
srv.RunAnalysis() // builds the auto-concept vocabulary
|
|
return srv
|
|
}
|
|
|
|
func searchIDs(t *testing.T, srv *Server, args map[string]any) map[string]bool {
|
|
t.Helper()
|
|
req := mcplib.CallToolRequest{}
|
|
req.Params.Name = "search_symbols"
|
|
req.Params.Arguments = args
|
|
res, err := srv.handleSearchSymbols(context.Background(), req)
|
|
require.NoError(t, err)
|
|
require.Falsef(t, res.IsError, "search errored: %v", res.Content)
|
|
var resp map[string]any
|
|
require.NoError(t, json.Unmarshal([]byte(res.Content[0].(mcplib.TextContent).Text), &resp))
|
|
ids := map[string]bool{}
|
|
if results, ok := resp["results"].([]any); ok {
|
|
for _, r := range results {
|
|
ids[r.(map[string]any)["id"].(string)] = true
|
|
}
|
|
}
|
|
return ids
|
|
}
|
|
|
|
// TestSearchSymbols_EquivalenceBridgesVocabulary is the core feature
|
|
// test: a query for "auth" must reach a LoginService symbol via the
|
|
// curated equivalence class -- with NO LLM provider configured.
|
|
func TestSearchSymbols_EquivalenceBridgesVocabulary(t *testing.T) {
|
|
srv := equivalenceTestServer(t, []string{
|
|
"LoginService", "SigninController", "ParseConfig", "UnrelatedThing",
|
|
}, nil) // nil EquivalenceClasses == enabled by default
|
|
|
|
ids := searchIDs(t, srv, map[string]any{"query": "auth"})
|
|
require.Truef(t, ids["pkg/LoginService.go::LoginService"],
|
|
"equivalence expansion should bridge 'auth' to LoginService; got %v", ids)
|
|
require.Truef(t, ids["pkg/SigninController.go::SigninController"],
|
|
"equivalence expansion should bridge 'auth' to SigninController; got %v", ids)
|
|
}
|
|
|
|
// TestSearchSymbols_EquivalenceDeleteRemove confirms the delete/remove
|
|
// bridge works in both directions through the BM25 OR-merge.
|
|
func TestSearchSymbols_EquivalenceDeleteRemove(t *testing.T) {
|
|
srv := equivalenceTestServer(t, []string{
|
|
"RemoveUser", "DropTable", "FetchProfile",
|
|
}, nil)
|
|
ids := searchIDs(t, srv, map[string]any{"query": "delete"})
|
|
require.Truef(t, ids["pkg/RemoveUser.go::RemoveUser"],
|
|
"'delete' should bridge to RemoveUser; got %v", ids)
|
|
require.Truef(t, ids["pkg/DropTable.go::DropTable"],
|
|
"'delete' should bridge to DropTable; got %v", ids)
|
|
}
|
|
|
|
// TestSearchSymbols_ExpandOffDisablesEquivalence confirms expand:"off"
|
|
// suppresses the equivalence channel -- "auth" then matches nothing.
|
|
func TestSearchSymbols_ExpandOffDisablesEquivalence(t *testing.T) {
|
|
srv := equivalenceTestServer(t, []string{"LoginService", "SigninController"}, nil)
|
|
ids := searchIDs(t, srv, map[string]any{"query": "auth", "expand": "off"})
|
|
require.Falsef(t, ids["pkg/LoginService.go::LoginService"],
|
|
"expand:off must not bridge 'auth' to LoginService; got %v", ids)
|
|
}
|
|
|
|
// TestSearchSymbols_EquivalenceDisabledViaConfig confirms the config
|
|
// toggle (equivalence_classes:false) disables the channel.
|
|
func TestSearchSymbols_EquivalenceDisabledViaConfig(t *testing.T) {
|
|
off := false
|
|
srv := equivalenceTestServer(t, []string{"LoginService"}, &off)
|
|
ids := searchIDs(t, srv, map[string]any{"query": "auth"})
|
|
require.Falsef(t, ids["pkg/LoginService.go::LoginService"],
|
|
"equivalence_classes:false must disable the channel; got %v", ids)
|
|
}
|
|
|
|
// TestSearchSymbols_EquivalenceAutoConcepts confirms the per-repo
|
|
// auto-mined concept vocabulary bridges a domain phrase: a query for
|
|
// "blast" reaches a symbol named only with "radius" because the two
|
|
// words co-occur across the repo's symbol names.
|
|
func TestSearchSymbols_EquivalenceAutoConcepts(t *testing.T) {
|
|
srv := equivalenceTestServer(t, []string{
|
|
"handleBlastRadius", "blastRadiusOf", "BlastRadiusReport",
|
|
"computeBlastRadius", "radiusOnlySymbol",
|
|
}, nil)
|
|
ids := searchIDs(t, srv, map[string]any{"query": "blast"})
|
|
require.Truef(t, ids["pkg/radiusOnlySymbol.go::radiusOnlySymbol"],
|
|
"auto-concept mining should bridge 'blast' to a radius-named symbol; got %v", ids)
|
|
}
|
|
|
|
// TestSearchSymbols_ConceptThesaurusBridge confirms the concept-
|
|
// relatedness thesaurus bridges an adjacent (non-synonym) concept
|
|
// under expand:"equivalence" with NO LLM provider: a query for "auth"
|
|
// reaches a symbol named only with "token".
|
|
func TestSearchSymbols_ConceptThesaurusBridge(t *testing.T) {
|
|
srv := equivalenceTestServer(t, []string{
|
|
"LoginService", "TokenStore", "UnrelatedThing",
|
|
}, nil)
|
|
ids := searchIDs(t, srv, map[string]any{"query": "auth", "expand": "equivalence"})
|
|
// Direct synonym bridge still works.
|
|
require.Truef(t, ids["pkg/LoginService.go::LoginService"],
|
|
"synonym bridge auth -> LoginService should still hold; got %v", ids)
|
|
// Thesaurus (adjacent-concept) bridge: auth relates to the token
|
|
// class, so TokenStore is reachable even though "token" is not a
|
|
// synonym of "auth".
|
|
require.Truef(t, ids["pkg/TokenStore.go::TokenStore"],
|
|
"thesaurus bridge auth -> token -> TokenStore should hold; got %v", ids)
|
|
}
|