Files
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

166 lines
5.3 KiB
Go

package query
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zzet/gortex/internal/graph"
)
// distanceByID flattens a closure result into an id→distance map for
// terse assertions.
func distanceByID(res *ClosureResult) map[string]int {
out := make(map[string]int, len(res.Nodes))
for _, m := range res.Nodes {
out[m.Node.ID] = m.Distance
}
return out
}
func TestImportClosure_ExpandsOverDependencyEdges(t *testing.T) {
e := NewEngine(buildTestGraph())
// Default edge kinds (dependency allowlist) from main reach the
// whole call chain plus the referenced type.
res := e.ImportClosure([]string{"main.go::main"}, ClosureOptions{})
dist := distanceByID(res)
require.Contains(t, dist, "main.go::main")
assert.Equal(t, 0, dist["main.go::main"])
assert.Contains(t, dist, "pkg/server.go::Start")
assert.Contains(t, dist, "pkg/db.go::Connect")
assert.Contains(t, dist, "pkg/db.go::Ping")
// references edge is in the dependency allowlist.
assert.Contains(t, dist, "pkg/db.go::DBImpl")
}
func TestImportClosure_ExpandsOverImports(t *testing.T) {
g := buildTestGraph()
e := NewEngine(g)
// The pkg/server.go file imports pkg/db.go. Seeding the file node
// follows the imports edge into the dependency closure.
res := e.ImportClosure([]string{"pkg/server.go"}, ClosureOptions{
EdgeKinds: []graph.EdgeKind{graph.EdgeImports},
})
dist := distanceByID(res)
assert.Equal(t, 0, dist["pkg/server.go"])
if assert.Contains(t, dist, "pkg/db.go") {
assert.Equal(t, 1, dist["pkg/db.go"])
}
}
func TestImportClosure_DistanceRanking(t *testing.T) {
e := NewEngine(buildTestGraph())
res := e.ImportClosure([]string{"main.go::main"}, ClosureOptions{
EdgeKinds: []graph.EdgeKind{graph.EdgeCalls},
})
dist := distanceByID(res)
// main -> Start -> Connect -> Ping is a depth-3 call chain.
assert.Equal(t, 0, dist["main.go::main"])
assert.Equal(t, 1, dist["pkg/server.go::Start"])
assert.Equal(t, 2, dist["pkg/db.go::Connect"])
assert.Equal(t, 3, dist["pkg/db.go::Ping"])
// The result must be sorted by (distance, id) regardless of edge
// enumeration order.
for i := 1; i < len(res.Nodes); i++ {
prev, cur := res.Nodes[i-1], res.Nodes[i]
if prev.Distance == cur.Distance {
assert.LessOrEqual(t, prev.Node.ID, cur.Node.ID)
} else {
assert.Less(t, prev.Distance, cur.Distance)
}
}
}
func TestImportClosure_NodeBudgetCap(t *testing.T) {
e := NewEngine(buildTestGraph())
// Cap at two nodes: the seed plus the single nearest neighbour.
res := e.ImportClosure([]string{"main.go::main"}, ClosureOptions{
EdgeKinds: []graph.EdgeKind{graph.EdgeCalls},
MaxNodes: 2,
})
assert.LessOrEqual(t, len(res.Nodes), 2)
assert.True(t, res.Truncated, "expected truncation under a 2-node cap on a 4-node chain")
// The seed is always kept; the deepest node must have been dropped.
dist := distanceByID(res)
assert.Contains(t, dist, "main.go::main")
assert.NotContains(t, dist, "pkg/db.go::Ping")
}
func TestImportClosure_DepthCap(t *testing.T) {
e := NewEngine(buildTestGraph())
res := e.ImportClosure([]string{"main.go::main"}, ClosureOptions{
EdgeKinds: []graph.EdgeKind{graph.EdgeCalls},
MaxDepth: 1,
})
dist := distanceByID(res)
// Depth 1 reaches Start but not the deeper Connect / Ping.
assert.Contains(t, dist, "pkg/server.go::Start")
assert.NotContains(t, dist, "pkg/db.go::Connect")
assert.NotContains(t, dist, "pkg/db.go::Ping")
}
func TestImportClosure_MultiSeedMergeTakesNearest(t *testing.T) {
e := NewEngine(buildTestGraph())
// Seed both main (distance to Ping = 3) and Connect (distance to
// Ping = 1). The merged closure must record the NEAREST distance.
res := e.ImportClosure([]string{"main.go::main", "pkg/db.go::Connect"}, ClosureOptions{
EdgeKinds: []graph.EdgeKind{graph.EdgeCalls},
})
dist := distanceByID(res)
assert.Equal(t, 0, dist["main.go::main"])
assert.Equal(t, 0, dist["pkg/db.go::Connect"])
// Ping is 1 hop from Connect, 3 from main -> nearest wins.
assert.Equal(t, 1, dist["pkg/db.go::Ping"])
// Both seeds are recorded, de-duplicated and sorted.
assert.Equal(t, []string{"main.go::main", "pkg/db.go::Connect"}, res.SeedIDs)
}
func TestImportClosure_SkipsUnresolvedAndOutOfScope(t *testing.T) {
g := buildTestGraph()
for _, n := range g.AllNodes() {
n.WorkspaceID = "main"
}
g.AddNode(&graph.Node{
ID: "other/x.go::Foreign", Kind: graph.KindFunction, Name: "Foreign",
FilePath: "other/x.go", Language: "go", WorkspaceID: "other",
})
g.AddEdge(&graph.Edge{
From: "pkg/db.go::Ping", To: "other/x.go::Foreign",
Kind: graph.EdgeCalls, FilePath: "pkg/db.go", Line: 20,
})
// An unresolved target must never enter the closure.
g.AddEdge(&graph.Edge{
From: "main.go::main", To: "unresolved::Mystery",
Kind: graph.EdgeCalls, FilePath: "main.go", Line: 9,
})
e := NewEngine(g)
res := e.ImportClosure([]string{"main.go::main"}, ClosureOptions{
EdgeKinds: []graph.EdgeKind{graph.EdgeCalls},
WorkspaceID: "main",
})
dist := distanceByID(res)
assert.NotContains(t, dist, "other/x.go::Foreign", "out-of-scope neighbour must be dropped")
assert.NotContains(t, dist, "unresolved::Mystery", "unresolved target must be dropped")
}
func TestImportClosure_NoSeedsResolved(t *testing.T) {
e := NewEngine(buildTestGraph())
res := e.ImportClosure([]string{"does/not::Exist", "unresolved::Nope"}, ClosureOptions{})
assert.Empty(t, res.Nodes)
assert.Empty(t, res.SeedIDs)
}