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
127 lines
5.0 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|