using System.IO; using System.Threading.Tasks; using StellaOps.Cryptography; using StellaOps.Scanner.Reachability; using StellaOps.Scanner.Reachability.Gates; using Xunit; namespace StellaOps.Scanner.Reachability.Tests; public class RichGraphWriterTests { [Fact] public async Task WritesCanonicalGraphAndMeta() { var writer = new RichGraphWriter(CryptoHashFactory.CreateDefault()); using var temp = new TempDir(); var union = new ReachabilityUnionGraph( Nodes: new[] { new ReachabilityUnionNode("sym:dotnet:B", "dotnet", "method", "B"), new ReachabilityUnionNode("sym:dotnet:A", "dotnet", "method", "A") }, Edges: new[] { new ReachabilityUnionEdge("sym:dotnet:A", "sym:dotnet:B", "call", "high") }); var rich = RichGraphBuilder.FromUnion(union, "test-analyzer", "1.0.0"); var result = await writer.WriteAsync(rich, temp.Path, "analysis-1"); Assert.True(File.Exists(result.GraphPath)); Assert.True(File.Exists(result.MetaPath)); var json = await File.ReadAllTextAsync(result.GraphPath); Assert.Contains("richgraph-v1", json); Assert.Contains(":", result.GraphHash); // hash format: algorithm:digest Assert.Equal(2, result.NodeCount); Assert.Equal(1, result.EdgeCount); } [Fact] public async Task CarriesSymbolMetadataToRichGraph() { var writer = new RichGraphWriter(CryptoHashFactory.CreateDefault()); using var temp = new TempDir(); var union = new ReachabilityUnionGraph( Nodes: new[] { new ReachabilityUnionNode( "sym:binary:target", "binary", "function", "ssl_read", CodeBlockHash: "sha256:blockhash", Symbol: new ReachabilitySymbol("_Zssl_read", "ssl_read", "DWARF", 0.9)) }, Edges: Array.Empty()); var rich = RichGraphBuilder.FromUnion(union, "test-analyzer", "1.0.0"); var result = await writer.WriteAsync(rich, temp.Path, "analysis-symbol-rich"); var json = await File.ReadAllTextAsync(result.GraphPath); Assert.Contains("\"code_block_hash\":\"sha256:blockhash\"", json); Assert.Contains("\"symbol\":{\"mangled\":\"_Zssl_read\",\"demangled\":\"ssl_read\",\"source\":\"DWARF\",\"confidence\":0.9}", json); } [Fact] public async Task WritesGatesOnEdgesWhenPresent() { var writer = new RichGraphWriter(CryptoHashFactory.CreateDefault()); using var temp = new TempDir(); var union = new ReachabilityUnionGraph( Nodes: new[] { new ReachabilityUnionNode("sym:dotnet:B", "dotnet", "method", "B"), new ReachabilityUnionNode("sym:dotnet:A", "dotnet", "method", "A") }, Edges: new[] { new ReachabilityUnionEdge("sym:dotnet:A", "sym:dotnet:B", "call", "high") }); var rich = RichGraphBuilder.FromUnion(union, "test-analyzer", "1.0.0"); var gate = new DetectedGate { Type = GateType.AuthRequired, Detail = "Auth required: ASP.NET Core Authorize attribute", GuardSymbol = "sym:dotnet:B", Confidence = 0.95, DetectionMethod = "annotation:\\[Authorize\\]" }; rich = rich with { Edges = new[] { rich.Edges[0] with { Gates = new[] { gate }, GateMultiplierBps = 3000 } } }; var result = await writer.WriteAsync(rich, temp.Path, "analysis-gates"); var json = await File.ReadAllTextAsync(result.GraphPath); Assert.Contains("\"gate_multiplier_bps\":3000", json); Assert.Contains("\"gates\":[", json); Assert.Contains("\"type\":\"authRequired\"", json); Assert.Contains("\"guard_symbol\":\"sym:dotnet:B\"", json); } }