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,119 @@
{
"advisoryKey": "CVE-2024-0001",
"affectedPackages": [
{
"type": "cpe",
"identifier": "cpe:2.3:a:example:product_one:1.0:*:*:*:*:*:*:*",
"platform": null,
"versionRanges": [
{
"fixedVersion": null,
"introducedVersion": null,
"lastAffectedVersion": null,
"primitives": {
"evr": null,
"hasVendorExtensions": true,
"nevra": null,
"semVer": null,
"vendorExtensions": {
"cpe": "cpe:2.3:a:example:product_one:1.0:*:*:*:*:*:*:*"
}
},
"provenance": {
"source": "nvd",
"kind": "cpe",
"value": "cpe:2.3:a:example:product_one:1.0:*:*:*:*:*:*:*",
"decisionReason": null,
"recordedAt": "2024-01-02T10:00:00+00:00",
"fieldMask": ["affectedpackages[].versionranges[]"]
},
"rangeExpression": "cpe:2.3:a:example:product_one:1.0:*:*:*:*:*:*:*",
"rangeKind": "cpe"
}
],
"normalizedVersions": [],
"statuses": [],
"provenance": [
{
"source": "nvd",
"kind": "cpe",
"value": "cpe:2.3:a:example:product_one:1.0:*:*:*:*:*:*:*",
"decisionReason": null,
"recordedAt": "2024-01-02T10:00:00+00:00",
"fieldMask": ["affectedpackages[]"]
}
]
}
],
"aliases": ["CVE-2024-0001"],
"canonicalMetricId": "3.1|CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"credits": [],
"cvssMetrics": [
{
"baseScore": 9.8,
"baseSeverity": "critical",
"provenance": {
"source": "nvd",
"kind": "cvss",
"value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"decisionReason": null,
"recordedAt": "2024-01-02T10:00:00+00:00",
"fieldMask": ["cvssmetrics[]"]
},
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"version": "3.1"
}
],
"cwes": [
{
"taxonomy": "cwe",
"identifier": "CWE-79",
"name": "Improper Neutralization of Input",
"uri": "https://cwe.mitre.org/data/definitions/79.html",
"provenance": [
{
"source": "nvd",
"kind": "weakness",
"value": "CWE-79",
"decisionReason": null,
"recordedAt": "2024-01-02T10:00:00+00:00",
"fieldMask": ["cwes[]"]
}
]
}
],
"description": "Example vulnerability one.",
"exploitKnown": false,
"language": "en",
"modified": "2024-01-02T10:00:00+00:00",
"provenance": [
{
"source": "nvd",
"kind": "document",
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
"decisionReason": null,
"recordedAt": "2024-01-02T10:00:00+00:00",
"fieldMask": ["advisory"]
}
],
"published": "2024-01-01T10:00:00+00:00",
"references": [
{
"kind": "vendor advisory",
"provenance": {
"source": "nvd",
"kind": "reference",
"value": "https://vendor.example.com/advisories/0001",
"decisionReason": null,
"recordedAt": "2024-01-02T10:00:00+00:00",
"fieldMask": ["references[]"]
},
"sourceTag": "Vendor",
"summary": null,
"url": "https://vendor.example.com/advisories/0001"
}
],
"severity": "critical",
"summary": "Example vulnerability one.",
"title": "CVE-2024-0001"
}

View File

@@ -0,0 +1,119 @@
{
"advisoryKey": "CVE-2024-0002",
"affectedPackages": [
{
"type": "cpe",
"identifier": "cpe:2.3:a:example:product_two:2.0:*:*:*:*:*:*:*",
"platform": null,
"versionRanges": [
{
"fixedVersion": null,
"introducedVersion": null,
"lastAffectedVersion": null,
"primitives": {
"evr": null,
"hasVendorExtensions": true,
"nevra": null,
"semVer": null,
"vendorExtensions": {
"cpe": "cpe:2.3:a:example:product_two:2.0:*:*:*:*:*:*:*"
}
},
"provenance": {
"source": "nvd",
"kind": "cpe",
"value": "cpe:2.3:a:example:product_two:2.0:*:*:*:*:*:*:*",
"decisionReason": null,
"recordedAt": "2024-01-02T11:00:00+00:00",
"fieldMask": ["affectedpackages[].versionranges[]"]
},
"rangeExpression": "cpe:2.3:a:example:product_two:2.0:*:*:*:*:*:*:*",
"rangeKind": "cpe"
}
],
"normalizedVersions": [],
"statuses": [],
"provenance": [
{
"source": "nvd",
"kind": "cpe",
"value": "cpe:2.3:a:example:product_two:2.0:*:*:*:*:*:*:*",
"decisionReason": null,
"recordedAt": "2024-01-02T11:00:00+00:00",
"fieldMask": ["affectedpackages[]"]
}
]
}
],
"aliases": ["CVE-2024-0002"],
"canonicalMetricId": "3.0|CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L",
"credits": [],
"cvssMetrics": [
{
"baseScore": 4.6,
"baseSeverity": "medium",
"provenance": {
"source": "nvd",
"kind": "cvss",
"value": "CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L",
"decisionReason": null,
"recordedAt": "2024-01-02T11:00:00+00:00",
"fieldMask": ["cvssmetrics[]"]
},
"vector": "CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L",
"version": "3.0"
}
],
"cwes": [
{
"taxonomy": "cwe",
"identifier": "CWE-89",
"name": "SQL Injection",
"uri": "https://cwe.mitre.org/data/definitions/89.html",
"provenance": [
{
"source": "nvd",
"kind": "weakness",
"value": "CWE-89",
"decisionReason": null,
"recordedAt": "2024-01-02T11:00:00+00:00",
"fieldMask": ["cwes[]"]
}
]
}
],
"description": "Example vulnerability two.",
"exploitKnown": false,
"language": "en",
"modified": "2024-01-02T11:00:00+00:00",
"provenance": [
{
"source": "nvd",
"kind": "document",
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
"decisionReason": null,
"recordedAt": "2024-01-02T11:00:00+00:00",
"fieldMask": ["advisory"]
}
],
"published": "2024-01-01T11:00:00+00:00",
"references": [
{
"kind": "us government resource",
"provenance": {
"source": "nvd",
"kind": "reference",
"value": "https://cisa.example.gov/alerts/0002",
"decisionReason": null,
"recordedAt": "2024-01-02T11:00:00+00:00",
"fieldMask": ["references[]"]
},
"sourceTag": "CISA",
"summary": null,
"url": "https://cisa.example.gov/alerts/0002"
}
],
"severity": "medium",
"summary": "Example vulnerability two.",
"title": "CVE-2024-0002"
}

View File

@@ -0,0 +1,140 @@
// -----------------------------------------------------------------------------
// NvdParserSnapshotTests.cs
// Sprint: SPRINT_5100_0007_0005
// Task: CONN-FIX-005
// Description: NVD parser snapshot tests using TestKit ConnectorParserTestBase
// -----------------------------------------------------------------------------
using System.Text.Json;
using StellaOps.Canonical.Json;
using StellaOps.Concelier.Connector.Nvd.Internal;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Storage;
using StellaOps.TestKit.Connectors;
using Xunit;
namespace StellaOps.Concelier.Connector.Nvd.Tests.Nvd;
/// <summary>
/// Parser snapshot tests for the NVD connector.
/// Verifies that raw NVD JSON fixtures parse to expected canonical output.
/// </summary>
public sealed class NvdParserSnapshotTests : ConnectorParserTestBase<JsonDocument, IReadOnlyList<Advisory>>
{
private static readonly string BaseDirectory = AppContext.BaseDirectory;
protected override string FixturesDirectory =>
Path.Combine(BaseDirectory, "Nvd", "Fixtures");
protected override string ExpectedDirectory =>
Path.Combine(BaseDirectory, "Expected");
protected override JsonDocument DeserializeRaw(string json) =>
JsonDocument.Parse(json);
protected override IReadOnlyList<Advisory> Parse(JsonDocument raw)
{
var sourceDocument = CreateTestDocumentRecord();
var recordedAt = new DateTimeOffset(2024, 1, 2, 10, 0, 0, TimeSpan.Zero);
return NvdMapper.Map(raw, sourceDocument, recordedAt);
}
protected override IReadOnlyList<Advisory> DeserializeNormalized(string json) =>
CanonJson.Deserialize<List<Advisory>>(json) ?? new List<Advisory>();
protected override string SerializeToCanonical(IReadOnlyList<Advisory> model)
{
// For single advisory tests, serialize just the first advisory
if (model.Count == 1)
{
return CanonJson.Serialize(model[0]);
}
return CanonJson.Serialize(model);
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Snapshot")]
public void ParseNvdWindow1_CVE20240001_ProducesExpectedOutput()
{
VerifyParseSnapshotSingle("nvd-window-1.json", "nvd-window-1-CVE-2024-0001.canonical.json", "CVE-2024-0001");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Snapshot")]
public void ParseNvdWindow1_CVE20240002_ProducesExpectedOutput()
{
VerifyParseSnapshotSingle("nvd-window-1.json", "nvd-window-1-CVE-2024-0002.canonical.json", "CVE-2024-0002");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Determinism")]
public void ParseNvdWindow1_IsDeterministic()
{
VerifyDeterministicParse("nvd-window-1.json");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Determinism")]
public void ParseNvdMultipage_IsDeterministic()
{
VerifyDeterministicParse("nvd-multipage-1.json");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Snapshot")]
public void ParseConflictNvd_ProducesExpectedOutput()
{
// The conflict fixture is inline in NvdConflictFixtureTests
// This test verifies the canonical output matches
VerifyParseSnapshotSingle("conflict-nvd.canonical.json", "conflict-nvd.canonical.json", "CVE-2025-4242");
}
/// <summary>
/// Verifies that a fixture parses to the expected canonical output for a single advisory.
/// </summary>
private void VerifyParseSnapshotSingle(string fixtureFile, string expectedFile, string advisoryKey)
{
// Arrange
var rawJson = ReadFixture(fixtureFile);
var expectedJson = ReadExpected(expectedFile).Replace("\r\n", "\n").TrimEnd();
using var raw = DeserializeRaw(rawJson);
// Act
var advisories = Parse(raw);
var advisory = advisories.FirstOrDefault(a => a.AdvisoryKey == advisoryKey);
// Assert
Assert.NotNull(advisory);
var actualJson = CanonJson.Serialize(advisory).Replace("\r\n", "\n").TrimEnd();
if (actualJson != expectedJson)
{
// Write actual output for debugging
var actualPath = Path.Combine(ExpectedDirectory, expectedFile.Replace(".json", ".actual.json"));
Directory.CreateDirectory(Path.GetDirectoryName(actualPath)!);
File.WriteAllText(actualPath, actualJson);
}
Assert.Equal(expectedJson, actualJson);
}
private static DocumentRecord CreateTestDocumentRecord() =>
new(
Id: Guid.NewGuid(),
SourceName: NvdConnectorPlugin.SourceName,
Uri: "https://services.nvd.nist.gov/rest/json/cves/2.0",
FetchedAt: new DateTimeOffset(2024, 1, 2, 10, 0, 0, TimeSpan.Zero),
Sha256: "sha256-test",
Status: "completed",
ContentType: "application/json",
Headers: null,
Metadata: null,
Etag: null,
LastModified: null,
PayloadId: null);
}

View File

@@ -0,0 +1,500 @@
// -----------------------------------------------------------------------------
// NvdResilienceTests.cs
// Sprint: SPRINT_5100_0007_0005
// Task: CONN-FIX-011
// Description: Resilience tests for NVD connector - missing fields, invalid data
// -----------------------------------------------------------------------------
using System.Text.Json;
using FluentAssertions;
using StellaOps.Canonical.Json;
using StellaOps.Concelier.Connector.Nvd.Internal;
using StellaOps.Concelier.Storage;
using Xunit;
namespace StellaOps.Concelier.Connector.Nvd.Tests.Nvd;
/// <summary>
/// Resilience tests for the NVD connector.
/// Verifies graceful handling of partial, malformed, and edge-case inputs.
/// </summary>
public sealed class NvdResilienceTests
{
private static readonly DateTimeOffset FixedRecordedAt = new(2024, 10, 1, 0, 0, 0, TimeSpan.Zero);
#region Missing Fields Tests
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Map_MissingVulnerabilitiesArray_ReturnsEmptyList()
{
// Arrange
var json = """{"format": "NVD_CVE", "version": "2.0"}""";
using var document = JsonDocument.Parse(json);
var sourceDoc = CreateTestDocumentRecord();
// Act
var advisories = NvdMapper.Map(document, sourceDoc, FixedRecordedAt);
// Assert
advisories.Should().BeEmpty("missing vulnerabilities array should return empty list");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Map_EmptyVulnerabilitiesArray_ReturnsEmptyList()
{
// Arrange
var json = """{"vulnerabilities": []}""";
using var document = JsonDocument.Parse(json);
var sourceDoc = CreateTestDocumentRecord();
// Act
var advisories = NvdMapper.Map(document, sourceDoc, FixedRecordedAt);
// Assert
advisories.Should().BeEmpty("empty vulnerabilities array should return empty list");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Map_VulnerabilityMissingCveObject_SkipsEntry()
{
// Arrange
var json = """
{
"vulnerabilities": [
{"notCve": {}},
{"cve": {"id": "CVE-2024-0001"}}
]
}
""";
using var document = JsonDocument.Parse(json);
var sourceDoc = CreateTestDocumentRecord();
// Act
var advisories = NvdMapper.Map(document, sourceDoc, FixedRecordedAt);
// Assert
advisories.Should().HaveCount(1, "should skip entry without cve object");
advisories[0].AdvisoryKey.Should().Be("CVE-2024-0001");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Map_VulnerabilityMissingId_SkipsEntry()
{
// Arrange
var json = """
{
"vulnerabilities": [
{"cve": {"descriptions": []}},
{"cve": {"id": "CVE-2024-0002"}}
]
}
""";
using var document = JsonDocument.Parse(json);
var sourceDoc = CreateTestDocumentRecord();
// Act
var advisories = NvdMapper.Map(document, sourceDoc, FixedRecordedAt);
// Assert
advisories.Should().HaveCount(1, "should skip entry without id");
advisories[0].AdvisoryKey.Should().Be("CVE-2024-0002");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Map_VulnerabilityWithNullId_SkipsEntry()
{
// Arrange
var json = """
{
"vulnerabilities": [
{"cve": {"id": null}},
{"cve": {"id": "CVE-2024-0003"}}
]
}
""";
using var document = JsonDocument.Parse(json);
var sourceDoc = CreateTestDocumentRecord();
// Act
var advisories = NvdMapper.Map(document, sourceDoc, FixedRecordedAt);
// Assert
advisories.Should().HaveCount(1, "should skip entry with null id");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Map_VulnerabilityWithEmptyId_GeneratesSyntheticKey()
{
// Arrange
var json = """
{
"vulnerabilities": [
{"cve": {"id": ""}}
]
}
""";
using var document = JsonDocument.Parse(json);
var sourceDoc = CreateTestDocumentRecord();
// Act
var advisories = NvdMapper.Map(document, sourceDoc, FixedRecordedAt);
// Assert
advisories.Should().HaveCount(1);
advisories[0].AdvisoryKey.Should().StartWith("nvd:", "should generate synthetic key for empty id");
}
#endregion
#region Invalid Date Format Tests
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Map_InvalidPublishedDate_HandlesGracefully()
{
// Arrange
var json = """
{
"vulnerabilities": [
{
"cve": {
"id": "CVE-2024-0001",
"published": "not-a-date"
}
}
]
}
""";
using var document = JsonDocument.Parse(json);
var sourceDoc = CreateTestDocumentRecord();
// Act
var advisories = NvdMapper.Map(document, sourceDoc, FixedRecordedAt);
// Assert
advisories.Should().HaveCount(1, "should still parse advisory with invalid date");
advisories[0].Published.Should().BeNull("invalid date should result in null");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Map_MissingPublishedDate_HandlesGracefully()
{
// Arrange
var json = """
{
"vulnerabilities": [
{
"cve": {
"id": "CVE-2024-0001"
}
}
]
}
""";
using var document = JsonDocument.Parse(json);
var sourceDoc = CreateTestDocumentRecord();
// Act
var advisories = NvdMapper.Map(document, sourceDoc, FixedRecordedAt);
// Assert
advisories.Should().HaveCount(1);
advisories[0].Published.Should().BeNull("missing date should result in null");
}
#endregion
#region Unknown Enum Value Tests
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Map_UnknownCvssSeverity_HandlesGracefully()
{
// Arrange
var json = """
{
"vulnerabilities": [
{
"cve": {
"id": "CVE-2024-0001",
"metrics": {
"cvssMetricV31": [
{
"cvssData": {
"version": "3.1",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"baseScore": 9.8,
"baseSeverity": "UNKNOWN_SEVERITY"
}
}
]
}
}
}
]
}
""";
using var document = JsonDocument.Parse(json);
var sourceDoc = CreateTestDocumentRecord();
// Act
var advisories = NvdMapper.Map(document, sourceDoc, FixedRecordedAt);
// Assert
advisories.Should().HaveCount(1, "should still parse advisory with unknown severity");
// Unknown severity might be preserved or mapped to a default
}
#endregion
#region Determinism Tests
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Determinism")]
public void Map_SameInput_ProducesDeterministicOutput()
{
// Arrange
var json = """
{
"vulnerabilities": [
{
"cve": {
"id": "CVE-2024-0001",
"descriptions": [{"lang": "en", "value": "Test vulnerability"}]
}
}
]
}
""";
var sourceDoc = CreateTestDocumentRecord();
// Act
var results = new List<string>();
for (int i = 0; i < 3; i++)
{
using var document = JsonDocument.Parse(json);
var advisories = NvdMapper.Map(document, sourceDoc, FixedRecordedAt);
results.Add(CanonJson.Serialize(advisories));
}
// Assert
results.Distinct().Should().HaveCount(1,
"same input should produce identical output on multiple runs");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Determinism")]
public void Map_ErrorHandling_IsDeterministic()
{
// Arrange
var json = """
{
"vulnerabilities": [
{"cve": {}},
{"cve": {"id": "CVE-2024-0001"}},
{"notCve": {}},
{"cve": {"id": "CVE-2024-0002"}}
]
}
""";
var sourceDoc = CreateTestDocumentRecord();
// Act
var results = new List<int>();
for (int i = 0; i < 3; i++)
{
using var document = JsonDocument.Parse(json);
var advisories = NvdMapper.Map(document, sourceDoc, FixedRecordedAt);
results.Add(advisories.Count);
}
// Assert
results.Distinct().Should().HaveCount(1,
"error handling should be deterministic");
results[0].Should().Be(2, "should consistently skip invalid entries");
}
#endregion
#region Null/Empty Input Tests
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Map_NullDocument_ThrowsArgumentNullException()
{
// Arrange
var sourceDoc = CreateTestDocumentRecord();
// Act & Assert
var act = () => NvdMapper.Map(null!, sourceDoc, FixedRecordedAt);
act.Should().Throw<ArgumentNullException>()
.WithParameterName("document");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Map_NullSourceDocument_ThrowsArgumentNullException()
{
// Arrange
var json = """{"vulnerabilities": []}""";
using var document = JsonDocument.Parse(json);
// Act & Assert
var act = () => NvdMapper.Map(document, null!, FixedRecordedAt);
act.Should().Throw<ArgumentNullException>()
.WithParameterName("sourceDocument");
}
#endregion
#region Malformed JSON Tests
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Parse_MalformedJson_ThrowsJsonException()
{
// Arrange
var malformedJson = "{ invalid json }";
// Act & Assert
var act = () => JsonDocument.Parse(malformedJson);
act.Should().Throw<JsonException>();
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Parse_TruncatedJson_ThrowsJsonException()
{
// Arrange
var truncatedJson = """{"vulnerabilities": [{"cve": {"id": "CVE-2024""";
// Act & Assert
var act = () => JsonDocument.Parse(truncatedJson);
act.Should().Throw<JsonException>();
}
#endregion
#region Edge Case Tests
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Map_VeryLargeVulnerabilitiesArray_HandlesGracefully()
{
// Arrange - Create array with 1000 minimal vulnerabilities
var vulnerabilities = string.Join(",",
Enumerable.Range(1, 1000).Select(i => $"{{\"cve\": {{\"id\": \"CVE-2024-{i:D4}\"}}}}"));
var json = $"{{\"vulnerabilities\": [{vulnerabilities}]}}";
using var document = JsonDocument.Parse(json);
var sourceDoc = CreateTestDocumentRecord();
// Act
var advisories = NvdMapper.Map(document, sourceDoc, FixedRecordedAt);
// Assert
advisories.Should().HaveCount(1000, "should handle large arrays");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Map_DescriptionWithSpecialCharacters_HandlesGracefully()
{
// Arrange
var json = """
{
"vulnerabilities": [
{
"cve": {
"id": "CVE-2024-0001",
"descriptions": [
{
"lang": "en",
"value": "Test <script>alert('xss')</script> & \"quotes\" \n\t special chars 日本語"
}
]
}
}
]
}
""";
using var document = JsonDocument.Parse(json);
var sourceDoc = CreateTestDocumentRecord();
// Act
var advisories = NvdMapper.Map(document, sourceDoc, FixedRecordedAt);
// Assert
advisories.Should().HaveCount(1);
advisories[0].Summary.Should().Contain("<script>", "special characters should be preserved");
}
[Fact]
[Trait("Lane", "Unit")]
[Trait("Category", "Resilience")]
public void Map_VeryLongDescription_HandlesGracefully()
{
// Arrange
var longDescription = new string('x', 100_000); // 100KB description
var json = $$"""
{
"vulnerabilities": [
{
"cve": {
"id": "CVE-2024-0001",
"descriptions": [{"lang": "en", "value": "{{longDescription}}"}]
}
}
]
}
""";
using var document = JsonDocument.Parse(json);
var sourceDoc = CreateTestDocumentRecord();
// Act
var advisories = NvdMapper.Map(document, sourceDoc, FixedRecordedAt);
// Assert
advisories.Should().HaveCount(1, "should handle very long descriptions");
}
#endregion
private static DocumentRecord CreateTestDocumentRecord() =>
new(
Id: Guid.Parse("a1b2c3d4-e5f6-7890-abcd-ef1234567890"),
SourceName: NvdConnectorPlugin.SourceName,
Uri: "https://services.nvd.nist.gov/rest/json/cves/2.0",
FetchedAt: FixedRecordedAt,
Sha256: "sha256-test",
Status: "completed",
ContentType: "application/json",
Headers: null,
Metadata: null,
Etag: null,
LastModified: FixedRecordedAt,
PayloadId: null);
}

View File

@@ -11,8 +11,16 @@
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Nvd/StellaOps.Concelier.Connector.Nvd.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Canonical.Json/StellaOps.Canonical.Json.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
</ItemGroup>
<ItemGroup>
<None Include="Nvd/Fixtures/*.json" CopyToOutputDirectory="Always" />
<None Include="Expected/*.json" CopyToOutputDirectory="Always" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>