chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
package analysis
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zzet/gortex/internal/graph"
|
||||
)
|
||||
|
||||
func buildTestGraph() *graph.Graph {
|
||||
g := graph.New()
|
||||
|
||||
// auth module
|
||||
g.AddNode(&graph.Node{ID: "auth.go", Kind: graph.KindFile, Name: "auth.go", FilePath: "pkg/auth/auth.go", Language: "go"})
|
||||
g.AddNode(&graph.Node{ID: "auth.go::ValidateToken", Kind: graph.KindFunction, Name: "ValidateToken", FilePath: "pkg/auth/auth.go", Language: "go", StartLine: 10, EndLine: 30})
|
||||
g.AddNode(&graph.Node{ID: "auth.go::ParseClaims", Kind: graph.KindFunction, Name: "ParseClaims", FilePath: "pkg/auth/auth.go", Language: "go", StartLine: 32, EndLine: 50})
|
||||
|
||||
// handler module
|
||||
g.AddNode(&graph.Node{ID: "handler.go", Kind: graph.KindFile, Name: "handler.go", FilePath: "pkg/api/handler.go", Language: "go"})
|
||||
g.AddNode(&graph.Node{ID: "handler.go::HandleLogin", Kind: graph.KindFunction, Name: "HandleLogin", FilePath: "pkg/api/handler.go", Language: "go", StartLine: 5, EndLine: 40})
|
||||
g.AddNode(&graph.Node{ID: "handler.go::HandleLogout", Kind: graph.KindFunction, Name: "HandleLogout", FilePath: "pkg/api/handler.go", Language: "go", StartLine: 42, EndLine: 60})
|
||||
|
||||
// main
|
||||
g.AddNode(&graph.Node{ID: "main.go", Kind: graph.KindFile, Name: "main.go", FilePath: "cmd/main.go", Language: "go"})
|
||||
g.AddNode(&graph.Node{ID: "main.go::main", Kind: graph.KindFunction, Name: "main", FilePath: "cmd/main.go", Language: "go", StartLine: 1, EndLine: 20})
|
||||
|
||||
// db module
|
||||
g.AddNode(&graph.Node{ID: "db.go::QueryUser", Kind: graph.KindFunction, Name: "QueryUser", FilePath: "pkg/db/db.go", Language: "go", StartLine: 5, EndLine: 20})
|
||||
|
||||
// Edges
|
||||
g.AddEdge(&graph.Edge{From: "main.go::main", To: "handler.go::HandleLogin", Kind: graph.EdgeCalls})
|
||||
g.AddEdge(&graph.Edge{From: "main.go::main", To: "handler.go::HandleLogout", Kind: graph.EdgeCalls})
|
||||
g.AddEdge(&graph.Edge{From: "handler.go::HandleLogin", To: "auth.go::ValidateToken", Kind: graph.EdgeCalls})
|
||||
g.AddEdge(&graph.Edge{From: "handler.go::HandleLogin", To: "db.go::QueryUser", Kind: graph.EdgeCalls})
|
||||
g.AddEdge(&graph.Edge{From: "auth.go::ValidateToken", To: "auth.go::ParseClaims", Kind: graph.EdgeCalls})
|
||||
g.AddEdge(&graph.Edge{From: "handler.go::HandleLogout", To: "auth.go::ValidateToken", Kind: graph.EdgeCalls})
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
func TestDetectCommunities(t *testing.T) {
|
||||
g := buildTestGraph()
|
||||
result := DetectCommunities(g)
|
||||
|
||||
require.NotNil(t, result)
|
||||
// With this small graph, we should get at least 1 community
|
||||
assert.GreaterOrEqual(t, len(result.Communities), 1)
|
||||
assert.NotEmpty(t, result.NodeToComm)
|
||||
|
||||
// Check that communities have members
|
||||
for _, c := range result.Communities {
|
||||
assert.NotEmpty(t, c.ID)
|
||||
assert.NotEmpty(t, c.Members)
|
||||
assert.Greater(t, c.Size, 1)
|
||||
assert.GreaterOrEqual(t, c.Cohesion, 0.0)
|
||||
assert.LessOrEqual(t, c.Cohesion, 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscoverProcesses(t *testing.T) {
|
||||
g := buildTestGraph()
|
||||
result := DiscoverProcesses(g)
|
||||
|
||||
require.NotNil(t, result)
|
||||
// main and HandleLogin should be discovered as entry points
|
||||
assert.GreaterOrEqual(t, len(result.Processes), 1)
|
||||
|
||||
// Check process structure
|
||||
for _, p := range result.Processes {
|
||||
assert.NotEmpty(t, p.ID)
|
||||
assert.NotEmpty(t, p.Name)
|
||||
assert.NotEmpty(t, p.EntryPoint)
|
||||
assert.GreaterOrEqual(t, p.StepCount, 2)
|
||||
assert.NotEmpty(t, p.Files)
|
||||
}
|
||||
|
||||
// main should trace through handler → auth → parseclaims
|
||||
found := false
|
||||
for _, p := range result.Processes {
|
||||
if p.EntryPoint == "main.go::main" {
|
||||
found = true
|
||||
assert.GreaterOrEqual(t, p.StepCount, 3)
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "main should be discovered as a process entry point")
|
||||
}
|
||||
|
||||
func TestAnalyzeImpact(t *testing.T) {
|
||||
g := buildTestGraph()
|
||||
communities := DetectCommunities(g)
|
||||
processes := DiscoverProcesses(g)
|
||||
|
||||
result := AnalyzeImpact(g, []string{"auth.go::ValidateToken"}, communities, processes)
|
||||
|
||||
require.NotNil(t, result)
|
||||
assert.NotEmpty(t, result.Risk)
|
||||
assert.NotEmpty(t, result.Summary)
|
||||
|
||||
// ValidateToken is called by HandleLogin and HandleLogout (depth 1)
|
||||
d1 := result.ByDepth[1]
|
||||
assert.GreaterOrEqual(t, len(d1), 1, "should have at least 1 direct dependent")
|
||||
|
||||
// At depth 2, main should appear
|
||||
d2 := result.ByDepth[2]
|
||||
assert.GreaterOrEqual(t, len(d2), 0)
|
||||
|
||||
assert.Greater(t, result.TotalAffected, 0)
|
||||
}
|
||||
|
||||
// TestAnalyzeImpact_DropsHeuristicNoiseAtTransitiveDepths pins the
|
||||
// regression: a leaf symbol used to surface as "90 transitively
|
||||
// affected" because a heuristic / text-matched edge at d=1 fanned
|
||||
// out through high-confidence d=2 edges into hundreds of unrelated
|
||||
// downstream methods. Now d=2 and d=3 drop entries whose
|
||||
// representative edge has confidence == 0 + label "INFERRED"
|
||||
// (resolution edges without type info — the noise class). d=1
|
||||
// stays untouched.
|
||||
func TestAnalyzeImpact_DropsHeuristicNoiseAtTransitiveDepths(t *testing.T) {
|
||||
g := graph.New()
|
||||
target := &graph.Node{ID: "p/target.go::Target", Name: "Target", Kind: graph.KindFunction, FilePath: "p/target.go"}
|
||||
caller := &graph.Node{ID: "p/caller.go::Caller", Name: "Caller", Kind: graph.KindFunction, FilePath: "p/caller.go"}
|
||||
intermediate := &graph.Node{ID: "p/middle.go::Middle", Name: "Middle", Kind: graph.KindFunction, FilePath: "p/middle.go"}
|
||||
heuristic := &graph.Node{ID: "p/noise.go::Noise", Name: "Noise", Kind: graph.KindFunction, FilePath: "p/noise.go"}
|
||||
g.AddNode(target)
|
||||
g.AddNode(caller)
|
||||
g.AddNode(intermediate)
|
||||
g.AddNode(heuristic)
|
||||
// Direct caller: real call, high-conf.
|
||||
g.AddEdge(&graph.Edge{From: caller.ID, To: target.ID, Kind: graph.EdgeCalls, Confidence: 0.95})
|
||||
// d=2 caller arriving via a CONFIDENT edge.
|
||||
g.AddEdge(&graph.Edge{From: intermediate.ID, To: caller.ID, Kind: graph.EdgeCalls, Confidence: 0.95})
|
||||
// d=2 NOISE entry: heuristic edge with no type info — exactly
|
||||
// the shape the user reported as flooding the response.
|
||||
g.AddEdge(&graph.Edge{From: heuristic.ID, To: caller.ID, Kind: graph.EdgeCalls, Confidence: 0})
|
||||
|
||||
result := AnalyzeImpact(g, []string{target.ID}, nil, nil)
|
||||
require.NotNil(t, result)
|
||||
require.GreaterOrEqual(t, len(result.ByDepth[1]), 1, "direct caller must survive")
|
||||
for _, e := range result.ByDepth[2] {
|
||||
require.False(t, e.EdgeConfidence == 0 && e.ConfidenceLabel == "INFERRED",
|
||||
"d=2 must not include heuristic / text-matched edges (got %s via confidence=%v label=%s)",
|
||||
e.ID, e.EdgeConfidence, e.ConfidenceLabel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeImpact_RiskLevels(t *testing.T) {
|
||||
assert.Equal(t, RiskLow, assessRisk(0, 0))
|
||||
assert.Equal(t, RiskLow, assessRisk(1, 1))
|
||||
assert.Equal(t, RiskMedium, assessRisk(2, 3))
|
||||
assert.Equal(t, RiskHigh, assessRisk(5, 5))
|
||||
assert.Equal(t, RiskCritical, assessRisk(10, 10))
|
||||
}
|
||||
|
||||
func TestScoreEntryPoint(t *testing.T) {
|
||||
main := &graph.Node{Name: "main", Language: "go", Kind: graph.KindFunction}
|
||||
handler := &graph.Node{Name: "HandleLogin", Language: "go", Kind: graph.KindFunction}
|
||||
getter := &graph.Node{Name: "getName", Language: "go", Kind: graph.KindFunction}
|
||||
|
||||
// main with 3 callees, 0 callers should score high
|
||||
mainScore := scoreEntryPoint(main, 3, 0)
|
||||
handlerScore := scoreEntryPoint(handler, 2, 1)
|
||||
getterScore := scoreEntryPoint(getter, 1, 5)
|
||||
|
||||
assert.Greater(t, mainScore, handlerScore)
|
||||
assert.Greater(t, handlerScore, getterScore)
|
||||
}
|
||||
Reference in New Issue
Block a user