up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-13 18:08:55 +02:00
parent 6e45066e37
commit f1a39c4ce3
234 changed files with 24038 additions and 6910 deletions

View File

@@ -0,0 +1,484 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Xunit;
namespace StellaOps.Scanner.Reachability.Tests;
public class EdgeBundleTests
{
private const string TestGraphHash = "blake3:abc123def456";
[Fact]
public void EdgeBundle_Canonical_SortsEdgesDeterministically()
{
// Arrange - create bundle with unsorted edges
var edges = new List<BundledEdge>
{
new("func_z", "func_a", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null),
new("func_a", "func_c", "call", EdgeReason.RuntimeHit, false, 0.8, null, null, null),
new("func_a", "func_b", "call", EdgeReason.RuntimeHit, false, 0.7, null, null, null),
};
var bundle = new EdgeBundle("bundle:test", TestGraphHash, EdgeBundleReason.RuntimeHits, edges, DateTimeOffset.UtcNow);
// Act
var canonical = bundle.Canonical();
// Assert - edges should be sorted by From, then To, then Kind
Assert.Equal(3, canonical.Edges.Count);
Assert.Equal("func_a", canonical.Edges[0].From);
Assert.Equal("func_b", canonical.Edges[0].To);
Assert.Equal("func_a", canonical.Edges[1].From);
Assert.Equal("func_c", canonical.Edges[1].To);
Assert.Equal("func_z", canonical.Edges[2].From);
Assert.Equal("func_a", canonical.Edges[2].To);
}
[Fact]
public void EdgeBundle_ComputeContentHash_IsDeterministic()
{
// Arrange
var edges = new List<BundledEdge>
{
new("func_a", "func_b", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null),
new("func_b", "func_c", "call", EdgeReason.ThirdPartyCall, false, 0.8, null, null, null),
};
var bundle1 = new EdgeBundle("bundle:test", TestGraphHash, EdgeBundleReason.RuntimeHits, edges, DateTimeOffset.UtcNow);
var bundle2 = new EdgeBundle("bundle:test", TestGraphHash, EdgeBundleReason.RuntimeHits, edges, DateTimeOffset.UtcNow.AddMinutes(5));
// Act
var hash1 = bundle1.ComputeContentHash();
var hash2 = bundle2.ComputeContentHash();
// Assert - same content should produce same hash regardless of timestamp
Assert.Equal(hash1, hash2);
Assert.StartsWith("sha256:", hash1);
}
[Fact]
public void EdgeBundle_ComputeContentHash_DiffersWithDifferentEdges()
{
// Arrange
var edges1 = new List<BundledEdge>
{
new("func_a", "func_b", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null),
};
var edges2 = new List<BundledEdge>
{
new("func_a", "func_c", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null),
};
var bundle1 = new EdgeBundle("bundle:test", TestGraphHash, EdgeBundleReason.RuntimeHits, edges1, DateTimeOffset.UtcNow);
var bundle2 = new EdgeBundle("bundle:test", TestGraphHash, EdgeBundleReason.RuntimeHits, edges2, DateTimeOffset.UtcNow);
// Act
var hash1 = bundle1.ComputeContentHash();
var hash2 = bundle2.ComputeContentHash();
// Assert - different edges should produce different hashes
Assert.NotEqual(hash1, hash2);
}
[Fact]
public void EdgeBundleBuilder_EnforcesMaxEdgeLimit()
{
// Arrange
var builder = new EdgeBundleBuilder(TestGraphHash).WithReason(EdgeBundleReason.RuntimeHits);
// Act - add max edges
for (var i = 0; i < EdgeBundleConstants.MaxEdgesPerBundle; i++)
{
builder.AddEdge(new BundledEdge($"func_{i}", $"func_{i + 1}", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null));
}
// Assert - should throw when exceeding limit
Assert.Throws<InvalidOperationException>(() =>
builder.AddEdge(new BundledEdge("func_overflow", "func_target", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null)));
}
[Fact]
public void EdgeBundleBuilder_Build_CreatesDeterministicBundleId()
{
// Arrange
var builder1 = new EdgeBundleBuilder(TestGraphHash).WithReason(EdgeBundleReason.InitArray);
var builder2 = new EdgeBundleBuilder(TestGraphHash).WithReason(EdgeBundleReason.InitArray);
builder1.AddEdge(new BundledEdge("init_a", "func_b", "call", EdgeReason.InitArray, false, 1.0, null, null, null));
builder2.AddEdge(new BundledEdge("init_a", "func_b", "call", EdgeReason.InitArray, false, 1.0, null, null, null));
// Act
var bundle1 = builder1.Build();
var bundle2 = builder2.Build();
// Assert - same inputs should produce same bundle ID
Assert.Equal(bundle1.BundleId, bundle2.BundleId);
Assert.StartsWith("bundle:", bundle1.BundleId);
}
[Fact]
public void BundledEdge_Trimmed_NormalizesValues()
{
// Arrange
var edge = new BundledEdge(
From: " func_a ",
To: " func_b ",
Kind: " call ",
Reason: EdgeReason.RuntimeHit,
Revoked: false,
Confidence: 1.5, // Should be clamped to 1.0
Purl: " pkg:npm/test@1.0.0 ",
SymbolDigest: " sha256:abc ",
Evidence: " cas://evidence/123 ");
// Act
var trimmed = edge.Trimmed();
// Assert
Assert.Equal("func_a", trimmed.From);
Assert.Equal("func_b", trimmed.To);
Assert.Equal("call", trimmed.Kind);
Assert.Equal(1.0, trimmed.Confidence); // Clamped
Assert.Equal("pkg:npm/test@1.0.0", trimmed.Purl);
Assert.Equal("sha256:abc", trimmed.SymbolDigest);
Assert.Equal("cas://evidence/123", trimmed.Evidence);
}
[Fact]
public void BundledEdge_Trimmed_HandlesNullableFields()
{
// Arrange
var edge = new BundledEdge("func_a", "func_b", "", EdgeReason.RuntimeHit, false, 0.5, null, " ", null);
// Act
var trimmed = edge.Trimmed();
// Assert
Assert.Equal("call", trimmed.Kind); // Default when empty
Assert.Null(trimmed.Purl);
Assert.Null(trimmed.SymbolDigest); // Whitespace trimmed to null
Assert.Null(trimmed.Evidence);
}
}
public class EdgeBundleExtractorTests
{
private const string TestGraphHash = "blake3:abc123def456";
private static RichGraph CreateTestGraph(params RichGraphEdge[] edges)
{
var nodes = edges
.SelectMany(e => new[] { e.From, e.To })
.Distinct()
.Select(id => new RichGraphNode(id, id, null, null, "native", "function", id, null, null, null, null, null, null))
.ToList();
return new RichGraph(nodes, edges.ToList(), new List<RichGraphRoot>(), new RichGraphAnalyzer("test", "1.0", null));
}
[Fact]
public void ExtractContestedBundle_ReturnsLowConfidenceEdges()
{
// Arrange
var edges = new[]
{
new RichGraphEdge("func_a", "func_b", "call", null, null, null, 0.9, null),
new RichGraphEdge("func_b", "func_c", "call", null, null, null, 0.4, null), // Low confidence
new RichGraphEdge("func_c", "func_d", "call", null, null, null, 0.3, null), // Low confidence
};
var graph = CreateTestGraph(edges);
// Act
var bundle = EdgeBundleExtractor.ExtractContestedBundle(graph, TestGraphHash, confidenceThreshold: 0.5);
// Assert
Assert.NotNull(bundle);
Assert.Equal(EdgeBundleReason.Contested, bundle.BundleReason);
Assert.Equal(2, bundle.Edges.Count);
Assert.All(bundle.Edges, e => Assert.Equal(EdgeReason.LowConfidence, e.Reason));
}
[Fact]
public void ExtractContestedBundle_ReturnsNullWhenNoLowConfidenceEdges()
{
// Arrange
var edges = new[]
{
new RichGraphEdge("func_a", "func_b", "call", null, null, null, 0.9, null),
new RichGraphEdge("func_b", "func_c", "call", null, null, null, 0.8, null),
};
var graph = CreateTestGraph(edges);
// Act
var bundle = EdgeBundleExtractor.ExtractContestedBundle(graph, TestGraphHash, confidenceThreshold: 0.5);
// Assert
Assert.Null(bundle);
}
[Fact]
public void ExtractThirdPartyBundle_ReturnsEdgesWithPurl()
{
// Arrange
var edges = new[]
{
new RichGraphEdge("func_a", "func_b", "call", "pkg:npm/lodash@4.17.0", null, null, 0.9, null),
new RichGraphEdge("func_b", "func_c", "call", "pkg:unknown", null, null, 0.8, null), // Excluded
new RichGraphEdge("func_c", "func_d", "call", null, null, null, 0.7, null), // Excluded
new RichGraphEdge("func_d", "func_e", "call", "pkg:npm/express@4.0.0", null, null, 0.9, null),
};
var graph = CreateTestGraph(edges);
// Act
var bundle = EdgeBundleExtractor.ExtractThirdPartyBundle(graph, TestGraphHash);
// Assert
Assert.NotNull(bundle);
Assert.Equal(EdgeBundleReason.ThirdParty, bundle.BundleReason);
Assert.Equal(2, bundle.Edges.Count);
Assert.All(bundle.Edges, e => Assert.Equal(EdgeReason.ThirdPartyCall, e.Reason));
}
[Fact]
public void ExtractRevokedBundle_ReturnsEdgesToRevokedTargets()
{
// Arrange
var edges = new[]
{
new RichGraphEdge("func_a", "func_b", "call", null, null, null, 0.9, null),
new RichGraphEdge("func_b", "func_c", "call", null, null, null, 0.8, null),
new RichGraphEdge("func_c", "func_d", "call", null, null, null, 0.7, null),
};
var graph = CreateTestGraph(edges);
var revokedTargets = new HashSet<string> { "func_c", "func_d" };
// Act
var bundle = EdgeBundleExtractor.ExtractRevokedBundle(graph, TestGraphHash, revokedTargets);
// Assert
Assert.NotNull(bundle);
Assert.Equal(EdgeBundleReason.Revoked, bundle.BundleReason);
Assert.Equal(2, bundle.Edges.Count);
Assert.All(bundle.Edges, e =>
{
Assert.Equal(EdgeReason.Revoked, e.Reason);
Assert.True(e.Revoked);
});
}
[Fact]
public void ExtractRuntimeHitsBundle_ReturnsProvidedEdges()
{
// Arrange
var runtimeEdges = new List<BundledEdge>
{
new("func_a", "func_b", "call", EdgeReason.RuntimeHit, false, 1.0, null, null, "evidence_1"),
new("func_b", "func_c", "call", EdgeReason.RuntimeHit, false, 1.0, null, null, "evidence_2"),
};
// Act
var bundle = EdgeBundleExtractor.ExtractRuntimeHitsBundle(runtimeEdges, TestGraphHash);
// Assert
Assert.NotNull(bundle);
Assert.Equal(EdgeBundleReason.RuntimeHits, bundle.BundleReason);
Assert.Equal(2, bundle.Edges.Count);
Assert.All(bundle.Edges, e => Assert.Equal(EdgeReason.RuntimeHit, e.Reason));
}
[Fact]
public void ExtractRuntimeHitsBundle_ReturnsNullForEmptyList()
{
// Act
var bundle = EdgeBundleExtractor.ExtractRuntimeHitsBundle(new List<BundledEdge>(), TestGraphHash);
// Assert
Assert.Null(bundle);
}
[Fact]
public void ExtractInitArrayBundle_ReturnsEdgesFromInitRoots()
{
// Arrange
var edges = new[]
{
new RichGraphEdge("init_func", "target_a", "call", null, null, null, 1.0, null),
new RichGraphEdge("init_func", "target_b", "call", null, null, null, 1.0, null),
new RichGraphEdge("main_func", "target_c", "call", null, null, null, 0.9, null), // Not from init
};
var nodes = edges
.SelectMany(e => new[] { e.From, e.To })
.Distinct()
.Select(id => new RichGraphNode(id, id, null, null, "native", "function", id, null, null, null, null, null, null))
.ToList();
var roots = new List<RichGraphRoot>
{
new("init_func", "init", ".init_array")
};
var graph = new RichGraph(nodes, edges.ToList(), roots, new RichGraphAnalyzer("test", "1.0", null));
// Act
var bundle = EdgeBundleExtractor.ExtractInitArrayBundle(graph, TestGraphHash);
// Assert
Assert.NotNull(bundle);
Assert.Equal(EdgeBundleReason.InitArray, bundle.BundleReason);
Assert.Equal(2, bundle.Edges.Count);
}
}
public class EdgeBundlePublisherTests
{
private const string TestGraphHash = "blake3:abc123def456";
[Fact]
public async Task PublishAsync_StoresBundleAndDsseInCas()
{
// Arrange
var cas = new FakeFileContentAddressableStore();
var publisher = new EdgeBundlePublisher();
var edges = new List<BundledEdge>
{
new("func_a", "func_b", "call", EdgeReason.RuntimeHit, false, 0.9, "pkg:npm/test@1.0.0", "sha256:abc", null),
};
var bundle = new EdgeBundle("bundle:test123", TestGraphHash, EdgeBundleReason.RuntimeHits, edges, DateTimeOffset.UtcNow);
// Act
var result = await publisher.PublishAsync(bundle, cas);
// Assert
Assert.NotNull(result);
Assert.Equal("bundle:test123", result.BundleId);
Assert.Equal(TestGraphHash, result.GraphHash);
Assert.Equal(EdgeBundleReason.RuntimeHits, result.BundleReason);
Assert.Equal(1, result.EdgeCount);
Assert.StartsWith("sha256:", result.ContentHash);
Assert.StartsWith("sha256:", result.DsseDigest);
// Verify CAS paths
Assert.Contains("/edges/", result.CasUri);
Assert.Contains("/edges/", result.DsseCasUri);
Assert.EndsWith(".dsse", result.DsseCasUri);
}
[Fact]
public async Task PublishAsync_DsseContainsValidPayload()
{
// Arrange
var cas = new FakeFileContentAddressableStore();
var publisher = new EdgeBundlePublisher();
var edges = new List<BundledEdge>
{
new("func_a", "func_b", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null),
new("func_b", "func_c", "call", EdgeReason.ThirdPartyCall, true, 0.8, null, null, null),
};
var bundle = new EdgeBundle("bundle:test456", TestGraphHash, EdgeBundleReason.RuntimeHits, edges, DateTimeOffset.UtcNow);
// Act
var result = await publisher.PublishAsync(bundle, cas);
// Assert - verify DSSE was stored
var dsseKey = result.DsseRelativePath.Replace(".zip", "");
var dsseBytes = cas.GetBytes(dsseKey);
Assert.NotNull(dsseBytes);
// Parse DSSE envelope
var dsseJson = System.Text.Encoding.UTF8.GetString(dsseBytes);
var envelope = JsonDocument.Parse(dsseJson);
Assert.Equal("application/vnd.stellaops.edgebundle.predicate+json", envelope.RootElement.GetProperty("payloadType").GetString());
Assert.True(envelope.RootElement.TryGetProperty("payload", out _));
Assert.True(envelope.RootElement.TryGetProperty("signatures", out var signatures));
Assert.Single(signatures.EnumerateArray());
}
[Fact]
public async Task PublishAsync_BundleJsonContainsAllFields()
{
// Arrange
var cas = new FakeFileContentAddressableStore();
var publisher = new EdgeBundlePublisher();
var edges = new List<BundledEdge>
{
new("func_a", "func_b", "call", EdgeReason.Revoked, true, 0.5, "pkg:npm/test@1.0.0", "sha256:digest", "cas://evidence/123"),
};
var bundle = new EdgeBundle("bundle:revoked", TestGraphHash, EdgeBundleReason.Revoked, edges, DateTimeOffset.UtcNow);
// Act
var result = await publisher.PublishAsync(bundle, cas);
// Assert - verify bundle JSON was stored
var bundleKey = result.RelativePath.Replace(".zip", "");
var bundleBytes = cas.GetBytes(bundleKey);
Assert.NotNull(bundleBytes);
// Parse bundle JSON
var bundleJsonStr = System.Text.Encoding.UTF8.GetString(bundleBytes);
var bundleJson = JsonDocument.Parse(bundleJsonStr);
Assert.Equal("edge-bundle-v1", bundleJson.RootElement.GetProperty("schema").GetString());
Assert.Equal("Revoked", bundleJson.RootElement.GetProperty("bundleReason").GetString());
var edgesArray = bundleJson.RootElement.GetProperty("edges");
Assert.Single(edgesArray.EnumerateArray());
var edge = edgesArray[0];
Assert.Equal("func_a", edge.GetProperty("from").GetString());
Assert.Equal("func_b", edge.GetProperty("to").GetString());
Assert.Equal("Revoked", edge.GetProperty("reason").GetString());
Assert.True(edge.GetProperty("revoked").GetBoolean());
Assert.Equal("pkg:npm/test@1.0.0", edge.GetProperty("purl").GetString());
}
[Fact]
public async Task PublishAsync_CasPathFollowsContract()
{
// Arrange
var cas = new FakeFileContentAddressableStore();
var publisher = new EdgeBundlePublisher();
var edges = new List<BundledEdge>
{
new("func_a", "func_b", "call", EdgeReason.InitArray, false, 1.0, null, null, null),
};
var bundle = new EdgeBundle("bundle:init123", TestGraphHash, EdgeBundleReason.InitArray, edges, DateTimeOffset.UtcNow);
// Act
var result = await publisher.PublishAsync(bundle, cas);
// Assert - CAS path follows contract: cas://reachability/edges/{graph_hash}/{bundle_id}
var expectedGraphHashDigest = "abc123def456"; // Graph hash without prefix
Assert.StartsWith($"cas://reachability/edges/{expectedGraphHashDigest}/", result.CasUri);
Assert.StartsWith($"cas://reachability/edges/{expectedGraphHashDigest}/", result.DsseCasUri);
Assert.EndsWith(".dsse", result.DsseCasUri);
}
[Fact]
public async Task PublishAsync_ProducesDeterministicResults()
{
// Arrange
var cas1 = new FakeFileContentAddressableStore();
var cas2 = new FakeFileContentAddressableStore();
var publisher = new EdgeBundlePublisher();
var edges = new List<BundledEdge>
{
new("func_a", "func_b", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null),
};
var bundle1 = new EdgeBundle("bundle:det", TestGraphHash, EdgeBundleReason.RuntimeHits, edges, DateTimeOffset.UtcNow);
var bundle2 = new EdgeBundle("bundle:det", TestGraphHash, EdgeBundleReason.RuntimeHits, edges, DateTimeOffset.UtcNow.AddHours(1));
// Act
var result1 = await publisher.PublishAsync(bundle1, cas1);
var result2 = await publisher.PublishAsync(bundle2, cas2);
// Assert - content hash should be same for same content
Assert.Equal(result1.ContentHash, result2.ContentHash);
}
}