// // SPDX-License-Identifier: AGPL-3.0-or-later // Sprint: SPRINT_20260112_004_POLICY_unknowns_determinization_greyqueue (POLICY-UNK-006) // using Microsoft.Extensions.Time.Testing; using StellaOps.Policy.Determinization.Models; using StellaOps.TestKit; using Xunit; namespace StellaOps.Policy.Determinization.Tests.Models; [Trait("Category", TestCategories.Unit)] public class ReanalysisFingerprintTests { private readonly FakeTimeProvider _timeProvider; public ReanalysisFingerprintTests() { _timeProvider = new FakeTimeProvider(new DateTimeOffset(2026, 1, 15, 12, 0, 0, TimeSpan.Zero)); } [Fact] public void Build_WithAllInputs_GeneratesDeterministicFingerprint() { // Arrange var builder1 = new ReanalysisFingerprintBuilder(_timeProvider) .WithDsseBundleDigest("sha256:bundle123") .AddEvidenceDigest("sha256:evidence1") .AddEvidenceDigest("sha256:evidence2") .WithToolVersion("scanner", "1.0.0") .WithToolVersion("policy-engine", "2.0.0") .WithProductVersion("myapp@1.2.3") .WithPolicyConfigHash("sha256:config456") .WithSignalWeightsHash("sha256:weights789"); var builder2 = new ReanalysisFingerprintBuilder(_timeProvider) .WithDsseBundleDigest("sha256:bundle123") .AddEvidenceDigest("sha256:evidence1") .AddEvidenceDigest("sha256:evidence2") .WithToolVersion("scanner", "1.0.0") .WithToolVersion("policy-engine", "2.0.0") .WithProductVersion("myapp@1.2.3") .WithPolicyConfigHash("sha256:config456") .WithSignalWeightsHash("sha256:weights789"); // Act var fingerprint1 = builder1.Build(); var fingerprint2 = builder2.Build(); // Assert - same inputs produce same fingerprint ID Assert.Equal(fingerprint1.FingerprintId, fingerprint2.FingerprintId); Assert.StartsWith("sha256:", fingerprint1.FingerprintId); } [Fact] public void Build_WithDifferentInputs_GeneratesDifferentFingerprint() { // Arrange var builder1 = new ReanalysisFingerprintBuilder(_timeProvider) .WithDsseBundleDigest("sha256:bundle123") .WithProductVersion("myapp@1.2.3"); var builder2 = new ReanalysisFingerprintBuilder(_timeProvider) .WithDsseBundleDigest("sha256:bundle456") // Different .WithProductVersion("myapp@1.2.3"); // Act var fingerprint1 = builder1.Build(); var fingerprint2 = builder2.Build(); // Assert - different inputs produce different fingerprint IDs Assert.NotEqual(fingerprint1.FingerprintId, fingerprint2.FingerprintId); } [Fact] public void Build_EvidenceDigests_AreSortedDeterministically() { // Arrange - add in random order var builder = new ReanalysisFingerprintBuilder(_timeProvider) .AddEvidenceDigest("sha256:zzz") .AddEvidenceDigest("sha256:aaa") .AddEvidenceDigest("sha256:mmm"); // Act var fingerprint = builder.Build(); // Assert - sorted alphabetically Assert.Equal(3, fingerprint.EvidenceDigests.Count); Assert.Equal("sha256:aaa", fingerprint.EvidenceDigests[0]); Assert.Equal("sha256:mmm", fingerprint.EvidenceDigests[1]); Assert.Equal("sha256:zzz", fingerprint.EvidenceDigests[2]); } [Fact] public void Build_ToolVersions_AreSortedDeterministically() { // Arrange - add in random order var builder = new ReanalysisFingerprintBuilder(_timeProvider) .WithToolVersion("zebra-tool", "1.0.0") .WithToolVersion("alpha-tool", "2.0.0") .WithToolVersion("mike-tool", "3.0.0"); // Act var fingerprint = builder.Build(); // Assert - sorted by key var keys = fingerprint.ToolVersions.Keys.ToList(); Assert.Equal("alpha-tool", keys[0]); Assert.Equal("mike-tool", keys[1]); Assert.Equal("zebra-tool", keys[2]); } [Fact] public void Build_Triggers_AreSortedByEventTypeThenTime() { // Arrange var builder = new ReanalysisFingerprintBuilder(_timeProvider) .AddTrigger("vex.changed", 1, "excititor") .AddTrigger("epss.updated", 1, "signals") .AddTrigger("runtime.detected", 1, "zastava"); // Act var fingerprint = builder.Build(); // Assert - sorted by event type Assert.Equal(3, fingerprint.Triggers.Count); Assert.Equal("epss.updated", fingerprint.Triggers[0].EventType); Assert.Equal("runtime.detected", fingerprint.Triggers[1].EventType); Assert.Equal("vex.changed", fingerprint.Triggers[2].EventType); } [Fact] public void Build_DuplicateEvidenceDigests_AreDeduped() { // Arrange var builder = new ReanalysisFingerprintBuilder(_timeProvider) .AddEvidenceDigest("sha256:abc") .AddEvidenceDigest("sha256:abc") // duplicate .AddEvidenceDigest("sha256:def"); // Act var fingerprint = builder.Build(); // Assert Assert.Equal(2, fingerprint.EvidenceDigests.Count); } [Fact] public void Build_NextActions_AreSortedAndDeduped() { // Arrange var builder = new ReanalysisFingerprintBuilder(_timeProvider) .AddNextAction("rescan") .AddNextAction("notify") .AddNextAction("rescan") // duplicate .AddNextAction("adjudicate"); // Act var fingerprint = builder.Build(); // Assert Assert.Equal(3, fingerprint.NextActions.Count); Assert.Equal("adjudicate", fingerprint.NextActions[0]); Assert.Equal("notify", fingerprint.NextActions[1]); Assert.Equal("rescan", fingerprint.NextActions[2]); } [Fact] public void Build_SetsComputedAtFromTimeProvider() { // Arrange var builder = new ReanalysisFingerprintBuilder(_timeProvider); // Act var fingerprint = builder.Build(); // Assert Assert.Equal(_timeProvider.GetUtcNow(), fingerprint.ComputedAt); } }