using System.Text; using Microsoft.Extensions.Logging.Abstractions; using StellaOps.Concelier.SbomIntegration.Models; using StellaOps.Concelier.SbomIntegration.Parsing; using StellaOps.Concelier.SbomIntegration.Vex; using StellaOps.TestKit; using Xunit; namespace StellaOps.Concelier.SbomIntegration.Tests; public sealed class VexIntegrationTests { [Trait("Category", TestCategories.Integration)] [Fact] public async Task ConsumeFromSbomAsync_ParsesEmbeddedCycloneDxVex() { var json = """ { "bomFormat": "CycloneDX", "specVersion": "1.7", "serialNumber": "urn:uuid:00000000-0000-0000-0000-000000000001", "version": 1, "metadata": { "timestamp": "2026-01-20T00:00:00Z", "component": { "bom-ref": "comp-1", "type": "library", "name": "example", "version": "1.0.0", "purl": "pkg:npm/example@1.0.0" }, "tools": [ { "vendor": "test", "name": "test", "version": "1.0" } ] }, "components": [ { "bom-ref": "comp-1", "type": "library", "name": "example", "version": "1.0.0", "purl": "pkg:npm/example@1.0.0" } ], "vulnerabilities": [ { "id": "CVE-2026-9999", "analysis": { "state": "not_affected", "justification": "component_not_present", "response": ["will_not_fix"], "detail": "component absent", "lastUpdated": "2026-01-20T00:00:00Z" }, "affects": [ { "ref": "comp-1" } ], "ratings": [ { "method": "CVSSv3", "score": 7.5, "severity": "high" } ] } ] } """; await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); var parser = new ParsedSbomParser(NullLogger.Instance); var sbom = await parser.ParseAsync(stream, SbomFormat.CycloneDX, CancellationToken.None); var consumer = CreateConsumer(); var result = await consumer.ConsumeFromSbomAsync(sbom, VexConsumptionPolicyDefaults.Default, CancellationToken.None); Assert.Single(result.Statements); Assert.Equal("CVE-2026-9999", result.Statements[0].VulnerabilityId); Assert.Equal(VexStatus.NotAffected, result.Statements[0].Status); Assert.Contains("pkg:npm/example@1.0.0", result.Statements[0].AffectedComponents); } private static VexConsumer CreateConsumer() { var evaluator = new VexTrustEvaluator(new StubTimeProvider()); var resolver = new VexConflictResolver(); var merger = new VexMerger(resolver); var extractors = new IVexStatementExtractor[] { new CycloneDxVexExtractor(), new SpdxVexExtractor() }; return new VexConsumer(evaluator, merger, extractors); } private sealed class StubTimeProvider : TimeProvider { public override DateTimeOffset GetUtcNow() => DateTimeOffset.Parse("2026-01-20T01:00:00Z"); } }