182 lines
6.2 KiB
C#
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);
|
|
}
|
|
}
|