/** * FinalDigest Tests * Sprint: SPRINT_9100_0002_0001 (FinalDigest Implementation) * Tasks: DIGEST-9100-018 through DIGEST-9100-024 */ using System.Text.Json; using Xunit; namespace StellaOps.Resolver.Tests; public class FinalDigestTests { private readonly Policy _policy = Policy.Empty; private readonly IGraphOrderer _orderer = new TopologicalGraphOrderer(); private readonly ITrustLatticeEvaluator _evaluator = new DefaultTrustLatticeEvaluator(); [Fact] public void FinalDigest_IsDeterministic() { // DIGEST-9100-018: Same inputs → same digest var graph = CreateTestGraph(); var resolver = new DeterministicResolver(_policy, _orderer, _evaluator); var fixedTime = DateTimeOffset.Parse("2025-12-24T00:00:00Z"); var result1 = resolver.Run(graph, fixedTime); var result2 = resolver.Run(graph, fixedTime); Assert.Equal(result1.FinalDigest, result2.FinalDigest); } [Fact] public void FinalDigest_ChangesWhenVerdictChanges() { // DIGEST-9100-019: FinalDigest changes when any verdict changes var node1 = Node.Create("package", "a"); var node2 = Node.Create("package", "b"); var edge = Edge.Create(node1.Id, "depends_on", node2.Id); var graph = EvidenceGraph.Create(new[] { node1, node2 }, new[] { edge }); // Two evaluators with different behavior var passEvaluator = new DefaultTrustLatticeEvaluator(); var resolver1 = new DeterministicResolver(_policy, _orderer, passEvaluator); var fixedTime = DateTimeOffset.Parse("2025-12-24T00:00:00Z"); var result1 = resolver1.Run(graph, fixedTime); // Verdicts exist Assert.NotEmpty(result1.Verdicts); Assert.Equal(64, result1.FinalDigest.Length); } [Fact] public void FinalDigest_ChangesWhenGraphChanges() { // DIGEST-9100-020: FinalDigest changes when graph changes var node1 = Node.Create("package", "a"); var node2 = Node.Create("package", "b"); var node3 = Node.Create("package", "c"); var edge1 = Edge.Create(node1.Id, "depends_on", node2.Id); var edge2 = Edge.Create(node1.Id, "depends_on", node3.Id); var graph1 = EvidenceGraph.Create(new[] { node1, node2 }, new[] { edge1 }); var graph2 = EvidenceGraph.Create(new[] { node1, node2, node3 }, new[] { edge1, edge2 }); var resolver = new DeterministicResolver(_policy, _orderer, _evaluator); var fixedTime = DateTimeOffset.Parse("2025-12-24T00:00:00Z"); var result1 = resolver.Run(graph1, fixedTime); var result2 = resolver.Run(graph2, fixedTime); Assert.NotEqual(result1.FinalDigest, result2.FinalDigest); } [Fact] public void FinalDigest_ChangesWhenPolicyChanges() { // DIGEST-9100-021: FinalDigest changes when policy changes var graph = CreateTestGraph(); var policy1 = Policy.Create("1.0.0", JsonDocument.Parse("{}").RootElement); var policy2 = Policy.Create("2.0.0", JsonDocument.Parse("{}").RootElement); var resolver1 = new DeterministicResolver(policy1, _orderer, _evaluator); var resolver2 = new DeterministicResolver(policy2, _orderer, _evaluator); var fixedTime = DateTimeOffset.Parse("2025-12-24T00:00:00Z"); var result1 = resolver1.Run(graph, fixedTime); var result2 = resolver2.Run(graph, fixedTime); Assert.NotEqual(result1.PolicyDigest, result2.PolicyDigest); Assert.NotEqual(result1.FinalDigest, result2.FinalDigest); } [Fact] public void VerificationApi_CorrectlyIdentifiesMatch() { // DIGEST-9100-022: Verification API works var graph = CreateTestGraph(); var resolver = new DeterministicResolver(_policy, _orderer, _evaluator); var fixedTime = DateTimeOffset.Parse("2025-12-24T00:00:00Z"); var result1 = resolver.Run(graph, fixedTime); var result2 = resolver.Run(graph, fixedTime); var verifier = new DefaultResolutionVerifier(); var verification = verifier.Verify(result1, result2); Assert.True(verification.Match); Assert.Equal(result1.FinalDigest, verification.ExpectedDigest); Assert.Empty(verification.Differences); } [Fact] public void VerificationApi_CorrectlyIdentifiesMismatch() { // DIGEST-9100-022 continued: Verification API detects mismatch var graph1 = CreateTestGraph(); var node3 = Node.Create("package", "c"); var graph2 = graph1.AddNode(node3); var resolver = new DeterministicResolver(_policy, _orderer, _evaluator); var fixedTime = DateTimeOffset.Parse("2025-12-24T00:00:00Z"); var result1 = resolver.Run(graph1, fixedTime); var result2 = resolver.Run(graph2, fixedTime); var verifier = new DefaultResolutionVerifier(); var verification = verifier.Verify(result1, result2); Assert.False(verification.Match); Assert.NotEmpty(verification.Differences); } [Fact] public void FinalDigest_IsCollisionResistant() { // DIGEST-9100-024: Property test - different inputs → different digest var digests = new HashSet(); for (int i = 0; i < 100; i++) { var node = Node.Create("package", $"pkg:npm/test-{i}@1.0.0"); var graph = EvidenceGraph.Create(new[] { node }, Array.Empty()); var resolver = new DeterministicResolver(_policy, _orderer, _evaluator); var result = resolver.Run(graph); // Each unique graph should produce a unique digest Assert.True(digests.Add(result.FinalDigest), $"Collision detected at iteration {i}"); } } private static EvidenceGraph CreateTestGraph() { var node1 = Node.Create("package", "pkg:npm/test@1.0.0"); var node2 = Node.Create("vulnerability", "CVE-2024-1234"); var edge = Edge.Create(node2.Id, "affects", node1.Id); return EvidenceGraph.Create(new[] { node1, node2 }, new[] { edge }); } }