Files
zzet--gortex/internal/indexer/capability_edges_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

127 lines
5.0 KiB
Go

package indexer
import (
"testing"
"github.com/zzet/gortex/internal/graph"
)
func TestSynthesizeCapabilityEdges(t *testing.T) {
g := graph.New()
// reads_env: a function reads $AWS_SECRET via reads_config -> cfg::env.
g.AddNode(&graph.Node{ID: "svc.go::Load", Kind: graph.KindFunction, Name: "Load", FilePath: "svc.go", Language: "go"})
g.AddNode(&graph.Node{ID: "cfg::env::AWS_SECRET", Kind: graph.KindConfigKey, Name: "AWS_SECRET", Meta: map[string]any{"source": "env"}})
g.AddEdge(&graph.Edge{From: "svc.go::Load", To: "cfg::env::AWS_SECRET", Kind: graph.EdgeReadsConfig, FilePath: "svc.go", Line: 3})
// A non-env config read must NOT produce a reads_env edge.
g.AddNode(&graph.Node{ID: "cfg::viper::db.host", Kind: graph.KindConfigKey, Name: "db.host", Meta: map[string]any{"source": "viper"}})
g.AddEdge(&graph.Edge{From: "svc.go::Load", To: "cfg::viper::db.host", Kind: graph.EdgeReadsConfig, FilePath: "svc.go", Line: 4})
// accesses_field: a function writes a struct field.
g.AddNode(&graph.Node{ID: "svc.go::Server.cache", Kind: graph.KindField, Name: "cache", FilePath: "svc.go"})
g.AddEdge(&graph.Edge{From: "svc.go::Load", To: "svc.go::Server.cache", Kind: graph.EdgeWrites, FilePath: "svc.go", Line: 5})
// A write to a non-field target must NOT produce accesses_field.
g.AddEdge(&graph.Edge{From: "svc.go::Load", To: "unresolved::someGlobal", Kind: graph.EdgeWrites, FilePath: "svc.go", Line: 6})
// executes_process: a function shells out via exec.Command.
g.AddEdge(&graph.Edge{From: "svc.go::Load", To: "unresolved::exec.Command", Kind: graph.EdgeCalls, FilePath: "svc.go", Line: 7})
// A normal call must NOT produce executes_process.
g.AddEdge(&graph.Edge{From: "svc.go::Load", To: "unresolved::fmt.Println", Kind: graph.EdgeCalls, FilePath: "svc.go", Line: 8})
re, ep, fa := synthesizeCapabilityEdges(g)
if re != 1 {
t.Errorf("reads_env count = %d, want 1", re)
}
if ep != 1 {
t.Errorf("executes_process count = %d, want 1", ep)
}
if fa != 1 {
t.Errorf("accesses_field count = %d, want 1", fa)
}
want := map[graph.EdgeKind]string{
graph.EdgeReadsEnv: "cfg::env::AWS_SECRET",
graph.EdgeAccessesField: "svc.go::Server.cache",
graph.EdgeExecutesProcess: "string::process::exec.Command",
}
got := map[graph.EdgeKind]string{}
for _, e := range g.AllEdges() {
switch e.Kind {
case graph.EdgeReadsEnv, graph.EdgeAccessesField, graph.EdgeExecutesProcess:
if e.From != "svc.go::Load" {
t.Errorf("%s edge from %q, want svc.go::Load", e.Kind, e.From)
}
got[e.Kind] = e.To
}
}
for k, to := range want {
if got[k] != to {
t.Errorf("%s edge -> %q, want %q", k, got[k], to)
}
}
// The synthetic process node exists and is typed.
pn := g.GetNode("string::process::exec.Command")
if pn == nil {
t.Fatal("process node string::process::exec.Command not created")
}
if pn.Kind != graph.KindString || pn.Meta["context"] != "process" {
t.Errorf("process node kind/context = %v/%v, want string/process", pn.Kind, pn.Meta["context"])
}
// Idempotent at the graph level: a second pass produces no duplicate
// edges (AddEdge dedupes by edge key), even though the per-pass
// telemetry counters re-count from the current base edges.
countCap := func() int {
n := 0
for _, e := range g.AllEdges() {
switch e.Kind {
case graph.EdgeReadsEnv, graph.EdgeAccessesField, graph.EdgeExecutesProcess:
n++
}
}
return n
}
before := countCap()
synthesizeCapabilityEdges(g)
if after := countCap(); after != before {
t.Errorf("second pass created duplicate capability edges: before=%d after=%d", before, after)
}
}
// TestSynthesizeCapabilityEdges_ResolvedExecForms guards executes_process
// recognition of call targets the resolver has bound to fully-qualified
// external node IDs (the common case for Go os/exec) — not just the
// unresolved::exec.Command spelling.
func TestSynthesizeCapabilityEdges_ResolvedExecForms(t *testing.T) {
cases := map[string]string{
"unresolved::exec.Command": "string::process::exec.Command",
"stdlib::os/exec::Command": "string::process::exec.Command",
"gortex::stdlib::os/exec::Command": "string::process::exec.Command",
"stdlib::os/exec::CommandContext": "string::process::exec.CommandContext",
"stdlib::syscall::Exec": "string::process::syscall.Exec",
"dep::github.com/x/sh::Command::new": "", // not an exec API
}
for to, wantTarget := range cases {
g := graph.New()
g.AddNode(&graph.Node{ID: "a.go::F", Kind: graph.KindFunction, Name: "F", FilePath: "a.go"})
g.AddEdge(&graph.Edge{From: "a.go::F", To: to, Kind: graph.EdgeCalls, FilePath: "a.go", Line: 1})
_, ep, _ := synthesizeCapabilityEdges(g)
var got string
for _, e := range g.AllEdges() {
if e.Kind == graph.EdgeExecutesProcess {
got = e.To
}
}
if wantTarget == "" {
if ep != 0 || got != "" {
t.Errorf("callee %q: expected no executes_process edge, got %q (ep=%d)", to, got, ep)
}
continue
}
if ep != 1 || got != wantTarget {
t.Errorf("callee %q: executes_process -> %q (ep=%d), want %q", to, got, ep, wantTarget)
}
}
}