Files
git.stella-ops.org/src/Policy/__Tests/StellaOps.Policy.Determinization.Tests/Models/ReanalysisFingerprintTests.cs

182 lines
6.2 KiB
C#

// <copyright file="ReanalysisFingerprintTests.cs" company="StellaOps">
// SPDX-License-Identifier: AGPL-3.0-or-later
// Sprint: SPRINT_20260112_004_POLICY_unknowns_determinization_greyqueue (POLICY-UNK-006)
// </copyright>
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);
}
}