This commit is contained in:
StellaOps Bot
2025-12-14 23:20:14 +02:00
parent 3411e825cd
commit b058dbe031
356 changed files with 68310 additions and 1108 deletions

View File

@@ -0,0 +1,140 @@
using System.Collections.Generic;
using System.Linq;
using StellaOps.Scanner.Reachability.Ordering;
using Xunit;
namespace StellaOps.Scanner.Reachability.Tests;
public sealed class DeterministicGraphOrdererTests
{
[Fact]
public void Canonicalize_IsDeterministic_AcrossInputOrdering()
{
var orderer = new DeterministicGraphOrderer();
var graph1 = new RichGraph(
Nodes: new[] { Node("B"), Node("A"), Node("C") },
Edges: new[] { Edge("A", "C"), Edge("A", "B"), Edge("B", "C") },
Roots: new[] { new RichGraphRoot("A", "runtime", null) },
Analyzer: new RichGraphAnalyzer("test", "1.0", null));
var graph2 = new RichGraph(
Nodes: new[] { Node("C"), Node("A"), Node("B") },
Edges: new[] { Edge("B", "C"), Edge("A", "B"), Edge("A", "C") },
Roots: new[] { new RichGraphRoot("A", "runtime", null) },
Analyzer: new RichGraphAnalyzer("test", "1.0", null));
var canonical1 = orderer.Canonicalize(graph1, GraphOrderingStrategy.TopologicalLexicographic);
var canonical2 = orderer.Canonicalize(graph2, GraphOrderingStrategy.TopologicalLexicographic);
Assert.Equal(canonical1.ContentHash, canonical2.ContentHash);
Assert.Equal(canonical1.Nodes.Select(n => n.Id), canonical2.Nodes.Select(n => n.Id));
Assert.Equal(canonical1.Edges.Select(e => (e.SourceIndex, e.TargetIndex, e.EdgeType)),
canonical2.Edges.Select(e => (e.SourceIndex, e.TargetIndex, e.EdgeType)));
}
[Fact]
public void TopologicalLexicographic_UsesLexicographicTiebreakers()
{
var orderer = new DeterministicGraphOrderer();
var graph = new RichGraph(
Nodes: new[] { Node("C"), Node("B"), Node("A") },
Edges: new[] { Edge("A", "C"), Edge("B", "C") },
Roots: Array.Empty<RichGraphRoot>(),
Analyzer: new RichGraphAnalyzer("test", "1.0", null));
var order = orderer.OrderNodes(graph, GraphOrderingStrategy.TopologicalLexicographic);
Assert.Equal(new[] { "A", "B", "C" }, order);
}
[Fact]
public void TopologicalLexicographic_HandlesCyclesByAppendingRemainder()
{
var orderer = new DeterministicGraphOrderer();
var graph = new RichGraph(
Nodes: new[] { Node("B"), Node("A"), Node("C") },
Edges: new[] { Edge("A", "B"), Edge("B", "A") },
Roots: Array.Empty<RichGraphRoot>(),
Analyzer: new RichGraphAnalyzer("test", "1.0", null));
var order = orderer.OrderNodes(graph, GraphOrderingStrategy.TopologicalLexicographic);
Assert.Equal(new[] { "C", "A", "B" }, order);
}
[Fact]
public void BreadthFirstLexicographic_TraversesFromAnchors()
{
var orderer = new DeterministicGraphOrderer();
var graph = new RichGraph(
Nodes: new[] { Node("D"), Node("C"), Node("B"), Node("A") },
Edges: new[] { Edge("A", "C"), Edge("A", "B"), Edge("B", "D") },
Roots: new[] { new RichGraphRoot("A", "runtime", null) },
Analyzer: new RichGraphAnalyzer("test", "1.0", null));
var order = orderer.OrderNodes(graph, GraphOrderingStrategy.BreadthFirstLexicographic);
Assert.Equal(new[] { "A", "B", "C", "D" }, order);
}
[Fact]
public void DepthFirstLexicographic_TraversesFromAnchors()
{
var orderer = new DeterministicGraphOrderer();
var graph = new RichGraph(
Nodes: new[] { Node("D"), Node("C"), Node("B"), Node("A") },
Edges: new[] { Edge("A", "C"), Edge("A", "B"), Edge("B", "D") },
Roots: new[] { new RichGraphRoot("A", "runtime", null) },
Analyzer: new RichGraphAnalyzer("test", "1.0", null));
var order = orderer.OrderNodes(graph, GraphOrderingStrategy.DepthFirstLexicographic);
Assert.Equal(new[] { "A", "B", "D", "C" }, order);
}
[Fact]
public void OrderEdges_SortsByNodeOrderThenKind()
{
var orderer = new DeterministicGraphOrderer();
var graph = new RichGraph(
Nodes: new[] { Node("C"), Node("B"), Node("A") },
Edges: new[]
{
Edge("B", "C", kind: "import"),
Edge("A", "B", kind: "call"),
Edge("A", "C", kind: "call")
},
Roots: Array.Empty<RichGraphRoot>(),
Analyzer: new RichGraphAnalyzer("test", "1.0", null));
var order = new[] { "A", "B", "C" };
var edges = orderer.OrderEdges(graph, order).ToList();
Assert.Equal(("A", "B", "call"), (edges[0].From, edges[0].To, edges[0].Kind));
Assert.Equal(("A", "C", "call"), (edges[1].From, edges[1].To, edges[1].Kind));
Assert.Equal(("B", "C", "import"), (edges[2].From, edges[2].To, edges[2].Kind));
}
private static RichGraphNode Node(string id, IReadOnlyDictionary<string, string>? attributes = null)
=> new(
Id: id,
SymbolId: id,
CodeId: null,
Purl: null,
Lang: "dotnet",
Kind: "method",
Display: id,
BuildId: null,
Evidence: Array.Empty<string>(),
Attributes: attributes,
SymbolDigest: null);
private static RichGraphEdge Edge(string from, string to, string kind = "call")
=> new(
From: from,
To: to,
Kind: kind,
Purl: null,
SymbolDigest: null,
Evidence: Array.Empty<string>(),
Confidence: 1.0,
Candidates: Array.Empty<string>());
}

View File

@@ -0,0 +1,27 @@
using StellaOps.Scanner.Reachability;
using Xunit;
namespace StellaOps.Scanner.Reachability.Tests;
public sealed class SinkRegistryTests
{
[Theory]
[InlineData("dotnet", "System.Diagnostics.Process.Start", SinkCategory.CmdExec)]
[InlineData("dotnet", "SYSTEM.DIAGNOSTICS.PROCESS.START", SinkCategory.CmdExec)]
[InlineData("java", "java.io.ObjectInputStream.readObject", SinkCategory.UnsafeDeser)]
[InlineData("node", "child_process.exec", SinkCategory.CmdExec)]
[InlineData("python", "pickle.loads", SinkCategory.UnsafeDeser)]
public void MatchSink_ReturnsExpectedCategory(string language, string symbol, SinkCategory expectedCategory)
{
var sink = SinkRegistry.MatchSink(language, symbol);
Assert.NotNull(sink);
Assert.Equal(expectedCategory, sink!.Category);
}
[Fact]
public void MatchSink_ReturnsNull_WhenUnknownLanguage()
{
Assert.Null(SinkRegistry.MatchSink("unknown", "whatever"));
}
}