audit work, fixed StellaOps.sln warnings/errors, fixed tests, sprints work, new advisories
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// SignalSnapshotBuilderTests.cs
|
||||
// Sprint: SPRINT_20260106_001_004_BE_determinization_integration
|
||||
// Task: DBI-019 - Write unit tests for SignalSnapshotBuilder
|
||||
// Description: Unit tests for parallel signal snapshot building
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using Moq;
|
||||
using StellaOps.Findings.Ledger.Observations;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Findings.Ledger.Tests.Observations;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class SignalSnapshotBuilderTests
|
||||
{
|
||||
private readonly FakeTimeProvider _timeProvider = new();
|
||||
private readonly Mock<ISignalProvider> _epssMock = new();
|
||||
private readonly Mock<ISignalProvider> _kevMock = new();
|
||||
private readonly Mock<ISignalProvider> _vexMock = new();
|
||||
private readonly Mock<ISignalProvider> _reachabilityMock = new();
|
||||
|
||||
public SignalSnapshotBuilderTests()
|
||||
{
|
||||
_timeProvider.SetUtcNow(new DateTimeOffset(2026, 1, 7, 12, 0, 0, TimeSpan.Zero));
|
||||
|
||||
_epssMock.Setup(x => x.SignalType).Returns("epss");
|
||||
_kevMock.Setup(x => x.SignalType).Returns("kev");
|
||||
_vexMock.Setup(x => x.SignalType).Returns("vex");
|
||||
_reachabilityMock.Setup(x => x.SignalType).Returns("reachability");
|
||||
}
|
||||
|
||||
private SignalSnapshotBuilder CreateBuilder(params ISignalProvider[] providers)
|
||||
{
|
||||
return new SignalSnapshotBuilder(
|
||||
providers,
|
||||
_timeProvider,
|
||||
NullLogger<SignalSnapshotBuilder>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_FetchesAllSignalsInParallel()
|
||||
{
|
||||
// Arrange
|
||||
var cveId = "CVE-2024-1234";
|
||||
var product = "pkg:npm/lodash@4.17.0";
|
||||
|
||||
_epssMock
|
||||
.Setup(x => x.FetchAsync(cveId, product, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(SignalResult.Available("Score: 0.85", new { Score = 0.85 }));
|
||||
|
||||
_kevMock
|
||||
.Setup(x => x.FetchAsync(cveId, product, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(SignalResult.Available("IN KEV", new { IsInKev = true }));
|
||||
|
||||
_vexMock
|
||||
.Setup(x => x.FetchAsync(cveId, product, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(SignalResult.Available("not_affected"));
|
||||
|
||||
_reachabilityMock
|
||||
.Setup(x => x.FetchAsync(cveId, product, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(SignalResult.Available("Reachable"));
|
||||
|
||||
var builder = CreateBuilder(_epssMock.Object, _kevMock.Object, _vexMock.Object, _reachabilityMock.Object);
|
||||
|
||||
// Act
|
||||
var snapshot = await builder.BuildAsync(cveId, product);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(cveId, snapshot.CveId);
|
||||
Assert.Equal(product, snapshot.Product);
|
||||
Assert.Equal(SignalResultStatus.Available, snapshot.Epss.Status);
|
||||
Assert.Equal(SignalResultStatus.Available, snapshot.Kev.Status);
|
||||
Assert.Equal(SignalResultStatus.Available, snapshot.Vex.Status);
|
||||
Assert.Equal(SignalResultStatus.Available, snapshot.Reachability.Status);
|
||||
Assert.Empty(snapshot.FailedSignals);
|
||||
|
||||
_epssMock.Verify(x => x.FetchAsync(cveId, product, It.IsAny<CancellationToken>()), Times.Once);
|
||||
_kevMock.Verify(x => x.FetchAsync(cveId, product, It.IsAny<CancellationToken>()), Times.Once);
|
||||
_vexMock.Verify(x => x.FetchAsync(cveId, product, It.IsAny<CancellationToken>()), Times.Once);
|
||||
_reachabilityMock.Verify(x => x.FetchAsync(cveId, product, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_MissingProvider_ReturnsNotConfigured()
|
||||
{
|
||||
// Arrange - only EPSS provider available
|
||||
_epssMock
|
||||
.Setup(x => x.FetchAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(SignalResult.Available("Score: 0.5"));
|
||||
|
||||
var builder = CreateBuilder(_epssMock.Object);
|
||||
|
||||
// Act
|
||||
var snapshot = await builder.BuildAsync("CVE-2024-1234", "pkg:npm/test@1.0.0");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(SignalResultStatus.Available, snapshot.Epss.Status);
|
||||
Assert.Equal(SignalResultStatus.NotConfigured, snapshot.Kev.Status);
|
||||
Assert.Equal(SignalResultStatus.NotConfigured, snapshot.Vex.Status);
|
||||
Assert.Equal(SignalResultStatus.NotConfigured, snapshot.Reachability.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_ProviderThrows_ReturnsFailed()
|
||||
{
|
||||
// Arrange
|
||||
_epssMock
|
||||
.Setup(x => x.FetchAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ThrowsAsync(new InvalidOperationException("Connection refused"));
|
||||
|
||||
var builder = CreateBuilder(_epssMock.Object);
|
||||
|
||||
// Act
|
||||
var snapshot = await builder.BuildAsync("CVE-2024-1234", "pkg:npm/test@1.0.0");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(SignalResultStatus.Failed, snapshot.Epss.Status);
|
||||
Assert.Equal("Connection refused", snapshot.Epss.Error);
|
||||
Assert.Contains("epss", snapshot.FailedSignals);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_ProviderNotFound_ReturnsNotFound()
|
||||
{
|
||||
// Arrange
|
||||
_epssMock
|
||||
.Setup(x => x.FetchAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(SignalResult.NotFound("epss-feed"));
|
||||
|
||||
var builder = CreateBuilder(_epssMock.Object);
|
||||
|
||||
// Act
|
||||
var snapshot = await builder.BuildAsync("CVE-2099-9999", "pkg:npm/test@1.0.0");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(SignalResultStatus.NotFound, snapshot.Epss.Status);
|
||||
Assert.DoesNotContain("epss", snapshot.FailedSignals); // NotFound is not a failure
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_ComputesUncertaintyScore()
|
||||
{
|
||||
// Arrange - all signals available = low uncertainty
|
||||
SetupAllSignalsAvailable();
|
||||
|
||||
var builder = CreateBuilder(_epssMock.Object, _kevMock.Object, _vexMock.Object, _reachabilityMock.Object);
|
||||
|
||||
// Act
|
||||
var snapshot = await builder.BuildAsync("CVE-2024-1234", "pkg:npm/test@1.0.0");
|
||||
|
||||
// Assert - with all signals, uncertainty should be low
|
||||
Assert.True(snapshot.UncertaintyScore < 0.2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_NoSignals_HighUncertainty()
|
||||
{
|
||||
// Arrange - no providers configured
|
||||
var builder = CreateBuilder();
|
||||
|
||||
// Act
|
||||
var snapshot = await builder.BuildAsync("CVE-2024-1234", "pkg:npm/test@1.0.0");
|
||||
|
||||
// Assert - with no signals, uncertainty should be high
|
||||
Assert.Equal(1.0, snapshot.UncertaintyScore);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_SnapshotAtUsesTimeProvider()
|
||||
{
|
||||
// Arrange
|
||||
var expectedTime = new DateTimeOffset(2026, 1, 7, 12, 0, 0, TimeSpan.Zero);
|
||||
var builder = CreateBuilder();
|
||||
|
||||
// Act
|
||||
var snapshot = await builder.BuildAsync("CVE-2024-1234", "pkg:npm/test@1.0.0");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedTime, snapshot.SnapshotAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildBatchAsync_ProcessesMultiplePairs()
|
||||
{
|
||||
// Arrange
|
||||
SetupAllSignalsAvailable();
|
||||
|
||||
var builder = CreateBuilder(_epssMock.Object, _kevMock.Object, _vexMock.Object, _reachabilityMock.Object);
|
||||
|
||||
var pairs = new List<(string CveId, string Product)>
|
||||
{
|
||||
("CVE-2024-0001", "pkg:npm/foo@1.0.0"),
|
||||
("CVE-2024-0002", "pkg:npm/bar@2.0.0"),
|
||||
("CVE-2024-0003", "pkg:npm/baz@3.0.0")
|
||||
};
|
||||
|
||||
// Act
|
||||
var results = await builder.BuildBatchAsync(pairs);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, results.Count);
|
||||
Assert.All(pairs, pair => Assert.True(results.ContainsKey(pair)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_TracksFailedSignals()
|
||||
{
|
||||
// Arrange
|
||||
_epssMock
|
||||
.Setup(x => x.FetchAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ThrowsAsync(new Exception("EPSS error"));
|
||||
|
||||
_kevMock
|
||||
.Setup(x => x.FetchAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(SignalResult.Available("Not in KEV"));
|
||||
|
||||
_vexMock
|
||||
.Setup(x => x.FetchAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ThrowsAsync(new Exception("VEX error"));
|
||||
|
||||
_reachabilityMock
|
||||
.Setup(x => x.FetchAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(SignalResult.Available("Reachable"));
|
||||
|
||||
var builder = CreateBuilder(_epssMock.Object, _kevMock.Object, _vexMock.Object, _reachabilityMock.Object);
|
||||
|
||||
// Act
|
||||
var snapshot = await builder.BuildAsync("CVE-2024-1234", "pkg:npm/test@1.0.0");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, snapshot.FailedSignals.Count);
|
||||
Assert.Contains("epss", snapshot.FailedSignals);
|
||||
Assert.Contains("vex", snapshot.FailedSignals);
|
||||
}
|
||||
|
||||
private void SetupAllSignalsAvailable()
|
||||
{
|
||||
_epssMock
|
||||
.Setup(x => x.FetchAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(SignalResult.Available("Score: 0.5"));
|
||||
|
||||
_kevMock
|
||||
.Setup(x => x.FetchAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(SignalResult.Available("Not in KEV"));
|
||||
|
||||
_vexMock
|
||||
.Setup(x => x.FetchAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(SignalResult.Available("not_affected"));
|
||||
|
||||
_reachabilityMock
|
||||
.Setup(x => x.FetchAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(SignalResult.Available("Reachable"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user