Files
git.stella-ops.org/src/RiskEngine/StellaOps.RiskEngine/StellaOps.RiskEngine.Tests/FixChainRiskIntegrationTests.cs

344 lines
11 KiB
C#

// Licensed under BUSL-1.1. Copyright (C) 2026 StellaOps Contributors.
// Sprint: SPRINT_20260110_012_007_RISK
// Task: FCR-009 - Integration Tests
using System.Collections.Immutable;
using FluentAssertions;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.RiskEngine.Core.Contracts;
using StellaOps.RiskEngine.Core.Providers.FixChain;
using Xunit;
namespace StellaOps.RiskEngine.Tests;
[Trait("Category", "Integration")]
public sealed class FixChainRiskIntegrationTests
{
private readonly FixChainRiskOptions _options;
private readonly InMemoryFixChainAttestationClient _attestationClient;
private readonly FixChainRiskProvider _provider;
public FixChainRiskIntegrationTests()
{
_options = new FixChainRiskOptions
{
Enabled = true,
FixedReduction = 0.90,
PartialReduction = 0.50,
MinConfidenceThreshold = 0.60m
};
_attestationClient = new InMemoryFixChainAttestationClient();
_provider = new FixChainRiskProvider(
_options,
_attestationClient,
NullLogger<FixChainRiskProvider>.Instance);
}
[Fact]
public async Task FullWorkflow_FixedVerdict_ReducesRisk()
{
// Arrange
var cveId = "CVE-2024-12345";
var binarySha256 = new string('a', 64);
var attestation = new FixChainAttestationData
{
ContentDigest = "sha256:abc123",
CveId = cveId,
ComponentPurl = "pkg:deb/debian/openssl@3.0.11",
BinarySha256 = binarySha256,
Verdict = new FixChainVerdictData
{
Status = "fixed",
Confidence = 0.97m,
Rationale = ["3 vulnerable functions removed", "All paths eliminated"]
},
GoldenSetId = "gs-openssl-0727",
VerifiedAt = DateTimeOffset.UtcNow
};
_attestationClient.AddAttestation(cveId, binarySha256, attestation);
// Act
var status = await _provider.GetFixStatusAsync(cveId, binarySha256);
// Assert
status.Should().NotBeNull();
status!.Verdict.Should().Be("fixed");
status.Confidence.Should().Be(0.97m);
status.Rationale.Should().HaveCount(2);
status.GoldenSetId.Should().Be("gs-openssl-0727");
// Verify risk adjustment
var adjustment = _provider.ComputeRiskAdjustment(status);
adjustment.Should().BeLessThan(0.3); // Significant reduction
}
[Fact]
public async Task FullWorkflow_CreateRiskFactor_ProducesValidFactor()
{
// Arrange
var cveId = "CVE-2024-67890";
var binarySha256 = new string('b', 64);
var attestation = new FixChainAttestationData
{
ContentDigest = "sha256:def456",
CveId = cveId,
ComponentPurl = "pkg:npm/lodash@4.17.21",
BinarySha256 = binarySha256,
Verdict = new FixChainVerdictData
{
Status = "partial",
Confidence = 0.75m,
Rationale = ["2 paths eliminated", "1 path remaining"]
},
VerifiedAt = DateTimeOffset.UtcNow
};
_attestationClient.AddAttestation(cveId, binarySha256, attestation);
// Act
var status = await _provider.GetFixStatusAsync(cveId, binarySha256);
var factor = _provider.CreateRiskFactor(status!);
// Assert
factor.Verdict.Should().Be(FixChainVerdictStatus.Partial);
factor.Confidence.Should().Be(0.75m);
factor.RiskModifier.Should().BeLessThan(0);
factor.AttestationRef.Should().StartWith("fixchain://");
factor.Rationale.Should().HaveCount(2);
}
[Fact]
public async Task FullWorkflow_DisplayModel_HasCorrectValues()
{
// Arrange
var cveId = "CVE-2024-99999";
var binarySha256 = new string('c', 64);
var attestation = new FixChainAttestationData
{
ContentDigest = "sha256:ghi789",
CveId = cveId,
ComponentPurl = "pkg:maven/org.example/lib@1.0.0",
BinarySha256 = binarySha256,
Verdict = new FixChainVerdictData
{
Status = "fixed",
Confidence = 0.95m,
Rationale = ["Fix verified"]
},
GoldenSetId = "gs-example",
VerifiedAt = DateTimeOffset.UtcNow
};
_attestationClient.AddAttestation(cveId, binarySha256, attestation);
// Act
var status = await _provider.GetFixStatusAsync(cveId, binarySha256);
var factor = _provider.CreateRiskFactor(status!);
var display = factor.ToDisplay();
// Assert
display.Label.Should().Be("Fix Verification");
display.Value.Should().Contain("Fixed");
display.Value.Should().Contain("95");
display.ImpactDirection.Should().Be("decrease");
display.EvidenceRef.Should().Contain("fixchain://");
display.Details.Should().ContainKey("golden_set_id");
}
[Fact]
public async Task FullWorkflow_Badge_HasCorrectStyle()
{
// Arrange
var cveId = "CVE-2024-11111";
var binarySha256 = new string('d', 64);
var attestation = new FixChainAttestationData
{
ContentDigest = "sha256:jkl012",
CveId = cveId,
ComponentPurl = "pkg:pypi/requests@2.28.0",
BinarySha256 = binarySha256,
Verdict = new FixChainVerdictData
{
Status = "inconclusive",
Confidence = 0.45m,
Rationale = ["Could not determine"]
},
VerifiedAt = DateTimeOffset.UtcNow
};
_attestationClient.AddAttestation(cveId, binarySha256, attestation);
// Act
var status = await _provider.GetFixStatusAsync(cveId, binarySha256);
var factor = _provider.CreateRiskFactor(status!);
var badge = factor.ToBadge();
// Assert
badge.Status.Should().Be("Inconclusive");
badge.Color.Should().Be("gray");
}
[Fact]
public async Task FullWorkflow_MultipleAttestations_SameComponent()
{
// Arrange - add multiple CVE attestations for same component
var binarySha256 = new string('e', 64);
var cveIds = new[] { "CVE-2024-001", "CVE-2024-002", "CVE-2024-003" };
foreach (var cveId in cveIds)
{
_attestationClient.AddAttestation(cveId, binarySha256, new FixChainAttestationData
{
ContentDigest = $"sha256:{cveId}",
CveId = cveId,
ComponentPurl = "pkg:deb/debian/openssl@3.0.11",
BinarySha256 = binarySha256,
Verdict = new FixChainVerdictData
{
Status = "fixed",
Confidence = 0.95m,
Rationale = [$"Fix for {cveId}"]
},
VerifiedAt = DateTimeOffset.UtcNow
});
}
// Act & Assert - each CVE can be queried individually
foreach (var cveId in cveIds)
{
var status = await _provider.GetFixStatusAsync(cveId, binarySha256);
status.Should().NotBeNull();
status!.Verdict.Should().Be("fixed");
}
}
[Fact]
public async Task FullWorkflow_ScoreRequest_AppliesAdjustment()
{
// Arrange
var signals = new Dictionary<string, double>
{
[FixChainRiskProvider.SignalFixConfidence] = 0.90,
[FixChainRiskProvider.SignalFixStatus] = FixChainRiskProvider.EncodeStatus("fixed")
};
var request = new ScoreRequest("fixchain", "test-subject", signals);
// Act
var score = await _provider.ScoreAsync(request, CancellationToken.None);
// Assert
score.Should().BeLessThan(0.5); // Significant reduction applied
}
[Fact]
public async Task FullWorkflow_DisabledProvider_NoAdjustment()
{
// Arrange
var disabledOptions = new FixChainRiskOptions { Enabled = false };
var disabledProvider = new FixChainRiskProvider(disabledOptions);
var signals = new Dictionary<string, double>
{
[FixChainRiskProvider.SignalFixConfidence] = 1.0,
[FixChainRiskProvider.SignalFixStatus] = FixChainRiskProvider.EncodeStatus("fixed")
};
var request = new ScoreRequest("fixchain", "test-subject", signals);
// Act
var score = await disabledProvider.ScoreAsync(request, CancellationToken.None);
// Assert
score.Should().Be(1.0); // No adjustment when disabled
}
[Fact]
public async Task FullWorkflow_NoAttestation_ReturnsNull()
{
// Act
var status = await _provider.GetFixStatusAsync(
"CVE-NONEXISTENT",
new string('x', 64));
// Assert
status.Should().BeNull();
}
[Fact]
public async Task FullWorkflow_GetForComponent_ReturnsMultiple()
{
// Arrange
var componentPurl = "pkg:deb/debian/test@1.0.0";
var cves = new[] { "CVE-2024-A", "CVE-2024-B" };
foreach (var cveId in cves)
{
_attestationClient.AddAttestation(cveId, new string('f', 64), new FixChainAttestationData
{
ContentDigest = $"sha256:{cveId}",
CveId = cveId,
ComponentPurl = componentPurl,
BinarySha256 = new string('f', 64),
Verdict = new FixChainVerdictData
{
Status = "fixed",
Confidence = 0.90m,
Rationale = []
},
VerifiedAt = DateTimeOffset.UtcNow
});
}
// Act
var attestations = await _attestationClient.GetForComponentAsync(componentPurl);
// Assert
attestations.Should().HaveCount(2);
}
}
/// <summary>
/// In-memory attestation client for testing.
/// </summary>
internal sealed class InMemoryFixChainAttestationClient : IFixChainAttestationClient
{
private readonly Dictionary<string, FixChainAttestationData> _store = new();
private readonly Dictionary<string, List<FixChainAttestationData>> _byComponent = new();
public void AddAttestation(string cveId, string binarySha256, FixChainAttestationData attestation)
{
var key = $"{cveId}:{binarySha256}";
_store[key] = attestation;
if (!string.IsNullOrEmpty(attestation.ComponentPurl))
{
if (!_byComponent.TryGetValue(attestation.ComponentPurl, out var list))
{
list = [];
_byComponent[attestation.ComponentPurl] = list;
}
list.Add(attestation);
}
}
public Task<FixChainAttestationData?> GetFixChainAsync(
string cveId,
string binarySha256,
string? componentPurl = null,
CancellationToken ct = default)
{
var key = $"{cveId}:{binarySha256}";
return Task.FromResult(_store.GetValueOrDefault(key));
}
public Task<ImmutableArray<FixChainAttestationData>> GetForComponentAsync(
string componentPurl,
CancellationToken ct = default)
{
if (_byComponent.TryGetValue(componentPurl, out var list))
{
return Task.FromResult(list.ToImmutableArray());
}
return Task.FromResult(ImmutableArray<FixChainAttestationData>.Empty);
}
}