246 lines
7.5 KiB
C#
246 lines
7.5 KiB
C#
using FluentAssertions;
|
|
using StellaOps.Policy.Deltas;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Policy.Tests.Deltas;
|
|
|
|
public sealed class DeltaVerdictTests
|
|
{
|
|
[Fact]
|
|
public void Build_WithNoDrivers_ReturnsPass()
|
|
{
|
|
var verdict = new DeltaVerdictBuilder()
|
|
.Build("delta:sha256:test");
|
|
|
|
verdict.Status.Should().Be(DeltaVerdictStatus.Pass);
|
|
verdict.Explanation.Should().Contain("No blocking");
|
|
}
|
|
|
|
[Fact]
|
|
public void Build_WithWarningDriver_ReturnsWarn()
|
|
{
|
|
var driver = new DeltaDriver
|
|
{
|
|
Type = "new-package",
|
|
Severity = DeltaDriverSeverity.Low,
|
|
Description = "New package added"
|
|
};
|
|
|
|
var verdict = new DeltaVerdictBuilder()
|
|
.AddWarningDriver(driver)
|
|
.Build("delta:sha256:test");
|
|
|
|
verdict.Status.Should().Be(DeltaVerdictStatus.Warn);
|
|
verdict.WarningDrivers.Should().HaveCount(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void Build_WithBlockingDriver_ReturnsFail()
|
|
{
|
|
var driver = new DeltaDriver
|
|
{
|
|
Type = "new-reachable-cve",
|
|
Severity = DeltaDriverSeverity.Critical,
|
|
Description = "Critical CVE is now reachable",
|
|
CveId = "CVE-2024-001"
|
|
};
|
|
|
|
var verdict = new DeltaVerdictBuilder()
|
|
.AddBlockingDriver(driver)
|
|
.Build("delta:sha256:test");
|
|
|
|
verdict.Status.Should().Be(DeltaVerdictStatus.Fail);
|
|
verdict.BlockingDrivers.Should().HaveCount(1);
|
|
verdict.RecommendedGate.Should().Be(DeltaGateLevel.G4);
|
|
}
|
|
|
|
[Fact]
|
|
public void Build_WithBlockingDriverAndException_ReturnsPassWithExceptions()
|
|
{
|
|
var driver = new DeltaDriver
|
|
{
|
|
Type = "new-reachable-cve",
|
|
Severity = DeltaDriverSeverity.Critical,
|
|
Description = "Critical CVE is now reachable",
|
|
CveId = "CVE-2024-001"
|
|
};
|
|
|
|
var verdict = new DeltaVerdictBuilder()
|
|
.AddBlockingDriver(driver)
|
|
.AddException("exception-123")
|
|
.Build("delta:sha256:test");
|
|
|
|
verdict.Status.Should().Be(DeltaVerdictStatus.PassWithExceptions);
|
|
verdict.AppliedExceptions.Should().Contain("exception-123");
|
|
}
|
|
|
|
[Fact]
|
|
public void Build_CriticalDriver_EscalatesToG4()
|
|
{
|
|
var driver = new DeltaDriver
|
|
{
|
|
Type = "critical-issue",
|
|
Severity = DeltaDriverSeverity.Critical,
|
|
Description = "Critical issue"
|
|
};
|
|
|
|
var verdict = new DeltaVerdictBuilder()
|
|
.AddBlockingDriver(driver)
|
|
.Build("delta:sha256:test");
|
|
|
|
verdict.RecommendedGate.Should().Be(DeltaGateLevel.G4);
|
|
}
|
|
|
|
[Fact]
|
|
public void Build_HighDriver_EscalatesToG3()
|
|
{
|
|
var driver = new DeltaDriver
|
|
{
|
|
Type = "high-issue",
|
|
Severity = DeltaDriverSeverity.High,
|
|
Description = "High severity issue"
|
|
};
|
|
|
|
var verdict = new DeltaVerdictBuilder()
|
|
.AddBlockingDriver(driver)
|
|
.Build("delta:sha256:test");
|
|
|
|
verdict.RecommendedGate.Should().Be(DeltaGateLevel.G3);
|
|
}
|
|
|
|
[Fact]
|
|
public void Build_WithRiskPoints_SetsCorrectValue()
|
|
{
|
|
var verdict = new DeltaVerdictBuilder()
|
|
.WithRiskPoints(25)
|
|
.Build("delta:sha256:test");
|
|
|
|
verdict.RiskPoints.Should().Be(25);
|
|
}
|
|
|
|
[Fact]
|
|
public void Build_WithRecommendations_IncludesAll()
|
|
{
|
|
var verdict = new DeltaVerdictBuilder()
|
|
.AddRecommendation("Review CVE-2024-001")
|
|
.AddRecommendation("Update dependency")
|
|
.Build("delta:sha256:test");
|
|
|
|
verdict.Recommendations.Should().HaveCount(2);
|
|
verdict.Recommendations.Should().Contain("Review CVE-2024-001");
|
|
}
|
|
|
|
[Fact]
|
|
public void Build_WithCustomExplanation_UsesProvided()
|
|
{
|
|
var verdict = new DeltaVerdictBuilder()
|
|
.WithExplanation("Custom explanation")
|
|
.Build("delta:sha256:test");
|
|
|
|
verdict.Explanation.Should().Be("Custom explanation");
|
|
}
|
|
|
|
[Fact]
|
|
public void Build_GeneratesDeterministicVerdictId_ForIdenticalInputs()
|
|
{
|
|
var verdict1 = new DeltaVerdictBuilder().Build("delta:sha256:test");
|
|
var verdict2 = new DeltaVerdictBuilder().Build("delta:sha256:test");
|
|
|
|
// Content-addressed IDs are deterministic
|
|
verdict1.VerdictId.Should().StartWith("verdict:sha256:");
|
|
verdict1.VerdictId.Should().Be(verdict2.VerdictId, "identical inputs must produce identical VerdictId");
|
|
}
|
|
|
|
[Fact]
|
|
public void Build_GeneratesDifferentVerdictId_ForDifferentInputs()
|
|
{
|
|
var verdict1 = new DeltaVerdictBuilder().Build("delta:sha256:test1");
|
|
var verdict2 = new DeltaVerdictBuilder().Build("delta:sha256:test2");
|
|
|
|
verdict1.VerdictId.Should().StartWith("verdict:sha256:");
|
|
verdict2.VerdictId.Should().StartWith("verdict:sha256:");
|
|
verdict1.VerdictId.Should().NotBe(verdict2.VerdictId, "different inputs must produce different VerdictId");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(10)]
|
|
public void Build_IsIdempotent_AcrossMultipleIterations(int iterations)
|
|
{
|
|
var driver = new DeltaDriver
|
|
{
|
|
Type = "new-reachable-cve",
|
|
Severity = DeltaDriverSeverity.High,
|
|
Description = "High severity CVE",
|
|
CveId = "CVE-2024-999"
|
|
};
|
|
|
|
var expected = new DeltaVerdictBuilder()
|
|
.AddBlockingDriver(driver)
|
|
.Build("delta:sha256:determinism-test")
|
|
.VerdictId;
|
|
|
|
for (int i = 0; i < iterations; i++)
|
|
{
|
|
var verdict = new DeltaVerdictBuilder()
|
|
.AddBlockingDriver(driver)
|
|
.Build("delta:sha256:determinism-test");
|
|
|
|
verdict.VerdictId.Should().Be(expected, $"iteration {i}: VerdictId must be stable");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Build_VerdictIdIsDeterministic_RegardlessOfDriverAddOrder()
|
|
{
|
|
var driver1 = new DeltaDriver
|
|
{
|
|
Type = "aaa-first",
|
|
Severity = DeltaDriverSeverity.Medium,
|
|
Description = "First driver"
|
|
};
|
|
|
|
var driver2 = new DeltaDriver
|
|
{
|
|
Type = "zzz-last",
|
|
Severity = DeltaDriverSeverity.Low,
|
|
Description = "Second driver"
|
|
};
|
|
|
|
// Add in one order
|
|
var verdict1 = new DeltaVerdictBuilder()
|
|
.AddWarningDriver(driver1)
|
|
.AddWarningDriver(driver2)
|
|
.Build("delta:sha256:order-test");
|
|
|
|
// Add in reverse order
|
|
var verdict2 = new DeltaVerdictBuilder()
|
|
.AddWarningDriver(driver2)
|
|
.AddWarningDriver(driver1)
|
|
.Build("delta:sha256:order-test");
|
|
|
|
// Content-addressed IDs should be same because drivers are sorted by Type
|
|
verdict1.VerdictId.Should().Be(verdict2.VerdictId, "drivers are sorted by Type before hashing");
|
|
}
|
|
|
|
[Fact]
|
|
public void VerdictIdGenerator_ComputeFromVerdict_MatchesOriginal()
|
|
{
|
|
var driver = new DeltaDriver
|
|
{
|
|
Type = "recompute-test",
|
|
Severity = DeltaDriverSeverity.Critical,
|
|
Description = "Test driver"
|
|
};
|
|
|
|
var verdict = new DeltaVerdictBuilder()
|
|
.AddBlockingDriver(driver)
|
|
.AddException("EXCEPTION-001")
|
|
.Build("delta:sha256:recompute-test");
|
|
|
|
var generator = new VerdictIdGenerator();
|
|
var recomputed = generator.ComputeVerdictId(verdict);
|
|
|
|
recomputed.Should().Be(verdict.VerdictId, "recomputed VerdictId must match original");
|
|
}
|
|
}
|