Add determinism tests for verdict artifact generation and update SHA256 sums script

- Implemented comprehensive tests for verdict artifact generation to ensure deterministic outputs across various scenarios, including identical inputs, parallel execution, and change ordering.
- Created helper methods for generating sample verdict inputs and computing canonical hashes.
- Added tests to validate the stability of canonical hashes, proof spine ordering, and summary statistics.
- Introduced a new PowerShell script to update SHA256 sums for files, ensuring accurate hash generation and file integrity checks.
This commit is contained in:
StellaOps Bot
2025-12-24 02:17:34 +02:00
parent e59921374e
commit 7503c19b8f
390 changed files with 37389 additions and 5380 deletions

View File

@@ -0,0 +1,272 @@
// -----------------------------------------------------------------------------
// CveParserSnapshotTests.cs
// Sprint: SPRINT_5100_0007_0005
// Task: CONN-FIX-005
// Description: CVE parser snapshot tests for fixture validation
// -----------------------------------------------------------------------------
using System.Text;
using System.Text.Json;
using FluentAssertions;
using StellaOps.Canonical.Json;
using StellaOps.Concelier.Connector.Cve.Internal;
using StellaOps.Concelier.Storage;
using Xunit;
namespace StellaOps.Concelier.Connector.Cve.Tests.Cve;
/// <summary>
/// Parser snapshot tests for the CVE connector.
/// Verifies that raw CVE JSON fixtures parse to expected canonical Advisory output.
/// </summary>
public sealed class CveParserSnapshotTests
{
private static readonly string BaseDirectory = AppContext.BaseDirectory;
private static readonly string FixturesDirectory = Path.Combine(BaseDirectory, "Fixtures");
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Determinism")]
public void ParseCveRecord_IsDeterministic()
{
// Arrange
var rawJson = ReadFixture("cve-CVE-2024-0001.json");
// Act
var results = new List<string>();
for (int i = 0; i < 3; i++)
{
var advisory = ParseToAdvisory(rawJson);
results.Add(CanonJson.Serialize(advisory));
}
// Assert
results.Distinct().Should().HaveCount(1,
"parsing CVE record multiple times should produce identical output");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Parser")]
public void CveRecordParser_ExtractsCveId()
{
// Arrange
var rawJson = ReadFixture("cve-CVE-2024-0001.json");
var content = Encoding.UTF8.GetBytes(rawJson);
// Act
var dto = CveRecordParser.Parse(content);
// Assert
dto.CveId.Should().Be("CVE-2024-0001");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Parser")]
public void CveRecordParser_ExtractsTitle()
{
// Arrange
var rawJson = ReadFixture("cve-CVE-2024-0001.json");
var content = Encoding.UTF8.GetBytes(rawJson);
// Act
var dto = CveRecordParser.Parse(content);
// Assert
dto.Title.Should().Be("Example Product Remote Code Execution");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Parser")]
public void CveRecordParser_ExtractsAliases()
{
// Arrange
var rawJson = ReadFixture("cve-CVE-2024-0001.json");
var content = Encoding.UTF8.GetBytes(rawJson);
// Act
var dto = CveRecordParser.Parse(content);
// Assert
dto.Aliases.Should().Contain("CVE-2024-0001", "CVE ID should be in aliases");
dto.Aliases.Should().Contain("GHSA-xxxx-yyyy-zzzz", "GHSA alias should be in aliases");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Parser")]
public void CveRecordParser_ExtractsReferences()
{
// Arrange
var rawJson = ReadFixture("cve-CVE-2024-0001.json");
var content = Encoding.UTF8.GetBytes(rawJson);
// Act
var dto = CveRecordParser.Parse(content);
// Assert
dto.References.Should().HaveCount(2);
dto.References.Should().Contain(r => r.Url == "https://example.com/security/advisory");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Parser")]
public void CveRecordParser_ExtractsAffected()
{
// Arrange
var rawJson = ReadFixture("cve-CVE-2024-0001.json");
var content = Encoding.UTF8.GetBytes(rawJson);
// Act
var dto = CveRecordParser.Parse(content);
// Assert
dto.Affected.Should().HaveCount(1);
dto.Affected[0].Vendor.Should().Be("ExampleVendor");
dto.Affected[0].Product.Should().Be("ExampleProduct");
dto.Affected[0].Versions.Should().HaveCountGreaterThan(0);
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Parser")]
public void CveRecordParser_ExtractsMetrics()
{
// Arrange
var rawJson = ReadFixture("cve-CVE-2024-0001.json");
var content = Encoding.UTF8.GetBytes(rawJson);
// Act
var dto = CveRecordParser.Parse(content);
// Assert
dto.Metrics.Should().HaveCount(1);
dto.Metrics[0].CvssV31.Should().NotBeNull();
dto.Metrics[0].CvssV31!.BaseScore.Should().Be(9.8);
dto.Metrics[0].CvssV31.BaseSeverity.Should().Be("CRITICAL");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Parser")]
public void CveMapper_SetsSeverityFromCvss()
{
// Arrange
var rawJson = ReadFixture("cve-CVE-2024-0001.json");
// Act
var advisory = ParseToAdvisory(rawJson);
// Assert
advisory.Severity.Should().Be("critical");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Parser")]
public void CveMapper_CreatesCvssMetrics()
{
// Arrange
var rawJson = ReadFixture("cve-CVE-2024-0001.json");
// Act
var advisory = ParseToAdvisory(rawJson);
// Assert
advisory.CvssMetrics.Should().HaveCount(1);
advisory.CvssMetrics[0].BaseScore.Should().Be(9.8);
advisory.CvssMetrics[0].Version.Should().Be("3.1");
advisory.CvssMetrics[0].Vector.Should().Contain("CVSS:3.1");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void CveRecordParser_MissingMetadata_ThrowsJsonException()
{
// Arrange
var invalidJson = """{"dataType": "CVE_RECORD"}""";
var content = Encoding.UTF8.GetBytes(invalidJson);
// Act & Assert
var act = () => CveRecordParser.Parse(content);
act.Should().Throw<JsonException>().WithMessage("*cveMetadata*");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void CveRecordParser_MissingCveId_ThrowsJsonException()
{
// Arrange
var invalidJson = """{"cveMetadata": {"state": "PUBLISHED"}}""";
var content = Encoding.UTF8.GetBytes(invalidJson);
// Act & Assert
var act = () => CveRecordParser.Parse(content);
act.Should().Throw<JsonException>().WithMessage("*cveId*");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void CveRecordParser_MinimalRecord_ParsesSuccessfully()
{
// Arrange - minimal CVE record with only required fields
var minimalJson = """
{
"cveMetadata": {
"cveId": "CVE-2024-9999",
"state": "PUBLISHED"
}
}
""";
var content = Encoding.UTF8.GetBytes(minimalJson);
// Act
var dto = CveRecordParser.Parse(content);
// Assert
dto.CveId.Should().Be("CVE-2024-9999");
dto.Aliases.Should().Contain("CVE-2024-9999");
dto.References.Should().BeEmpty();
dto.Affected.Should().BeEmpty();
dto.Metrics.Should().BeEmpty();
}
private static Models.Advisory ParseToAdvisory(string rawJson)
{
var content = Encoding.UTF8.GetBytes(rawJson);
var dto = CveRecordParser.Parse(content);
// Use fixed recordedAt for deterministic output
var recordedAt = new DateTimeOffset(2024, 10, 1, 0, 0, 0, TimeSpan.Zero);
var document = CreateTestDocumentRecord(dto.CveId, recordedAt);
return CveMapper.Map(dto, document, recordedAt);
}
private static DocumentRecord CreateTestDocumentRecord(string cveId, DateTimeOffset recordedAt) =>
new(
Id: Guid.Parse("a1b2c3d4-e5f6-7890-abcd-ef1234567890"),
SourceName: CveConnectorPlugin.SourceName,
Uri: $"https://cveawg.mitre.org/api/cve/{cveId}",
FetchedAt: recordedAt,
Sha256: "sha256-test",
Status: "completed",
ContentType: "application/json",
Headers: null,
Metadata: null,
Etag: null,
LastModified: recordedAt,
PayloadId: null);
private static string ReadFixture(string fileName)
{
var path = Path.Combine(FixturesDirectory, fileName);
return File.ReadAllText(path);
}
}

View File

@@ -10,6 +10,10 @@
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Cve/StellaOps.Concelier.Connector.Cve.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Testing/StellaOps.Concelier.Testing.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Canonical.Json/StellaOps.Canonical.Json.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
</ItemGroup>
<ItemGroup>
<None Include="Fixtures/*.json" CopyToOutputDirectory="Always" />