license switch agpl -> busl1, sprints work, new product advisories
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// <copyright file="TemporalCacheTests.cs" company="StellaOps">
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
// Copyright (c) StellaOps. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
// Sprint: SPRINT_20260105_002_001_TEST_time_skew_idempotency
|
||||
// Task: TSKW-010
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// <copyright file="ConcelierConfigDiffTests.cs" company="StellaOps">
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
// Copyright (c) StellaOps. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
// Sprint: SPRINT_20260105_002_005_TEST_cross_cutting
|
||||
// Task: CCUT-020
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// <copyright file="AstraConnectorTests.cs" company="Stella Operations">
|
||||
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
|
||||
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
|
||||
@@ -0,0 +1,549 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// ParsedSbomParserTests.cs
|
||||
// Sprint: SPRINT_20260119_015_Concelier_sbom_full_extraction
|
||||
// Task: TASK-015-008, TASK-015-009 - Parsed SBOM parsing tests
|
||||
// Description: Unit tests for enriched SBOM parsing
|
||||
// -----------------------------------------------------------------------------
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using StellaOps.Concelier.SbomIntegration.Models;
|
||||
using StellaOps.Concelier.SbomIntegration.Parsing;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Concelier.SbomIntegration.Tests;
|
||||
|
||||
public sealed class ParsedSbomParserTests
|
||||
{
|
||||
private readonly ParsedSbomParser _parser;
|
||||
|
||||
public ParsedSbomParserTests()
|
||||
{
|
||||
var loggerMock = new Mock<ILogger<ParsedSbomParser>>();
|
||||
_parser = new ParsedSbomParser(loggerMock.Object);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ParseAsync_CycloneDx_ExtractsMetadataComponentsAndServices()
|
||||
{
|
||||
var content = """
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.7",
|
||||
"serialNumber": "urn:uuid:1234",
|
||||
"metadata": {
|
||||
"timestamp": "2026-01-20T00:00:00Z",
|
||||
"component": {
|
||||
"bom-ref": "app",
|
||||
"name": "myapp",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"tools": [
|
||||
{ "name": "stella-scanner" }
|
||||
],
|
||||
"authors": [
|
||||
{ "name": "dev@example.com" }
|
||||
],
|
||||
"supplier": { "name": "Acme" },
|
||||
"manufacturer": { "name": "AcmeManu" }
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "lib",
|
||||
"name": "lib",
|
||||
"version": "2.0.0",
|
||||
"purl": "pkg:npm/lib@2.0.0",
|
||||
"scope": "optional",
|
||||
"modified": true,
|
||||
"supplier": {
|
||||
"name": "LibSupplier",
|
||||
"url": "https://supplier.example.com",
|
||||
"contact": [
|
||||
{ "name": "Supplier Contact", "email": "contact@example.com" }
|
||||
]
|
||||
},
|
||||
"manufacturer": {
|
||||
"name": "LibManufacturer"
|
||||
},
|
||||
"evidence": {
|
||||
"identity": {
|
||||
"field": "purl",
|
||||
"confidence": 0.9,
|
||||
"value": "pkg:npm/lib@2.0.0"
|
||||
},
|
||||
"occurrences": [
|
||||
{
|
||||
"location": "src/lib.js",
|
||||
"line": 10,
|
||||
"offset": 4,
|
||||
"symbol": "libfn",
|
||||
"additionalContext": "ctx"
|
||||
}
|
||||
],
|
||||
"callstack": {
|
||||
"frames": [
|
||||
{
|
||||
"package": "pkg",
|
||||
"module": "mod",
|
||||
"function": "fn",
|
||||
"parameters": ["a", "b"],
|
||||
"line": 20,
|
||||
"column": 2,
|
||||
"fullFilename": "/src/lib.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"licenses": [
|
||||
{ "expression": "MIT" }
|
||||
],
|
||||
"copyright": [
|
||||
"Copyright 2026"
|
||||
]
|
||||
},
|
||||
"pedigree": {
|
||||
"ancestors": [
|
||||
{ "bom-ref": "ancestor", "version": "0.1", "description": "base" }
|
||||
],
|
||||
"variants": [
|
||||
{ "bom-ref": "variant", "version": "2.0" }
|
||||
],
|
||||
"commits": [
|
||||
{ "uid": "abc123", "message": "fix" }
|
||||
],
|
||||
"patches": [
|
||||
{ "type": "backport", "diff": { "text": "diff", "url": "https://example.com/diff" } }
|
||||
],
|
||||
"notes": ["note1", "note2"]
|
||||
},
|
||||
"cryptoProperties": {
|
||||
"assetType": "algorithm",
|
||||
"algorithmProperties": {
|
||||
"primitive": "hash",
|
||||
"parameterSetIdentifier": "ps1",
|
||||
"curve": "P-256",
|
||||
"executionEnvironment": "software",
|
||||
"certificationLevel": "fips140-2",
|
||||
"mode": "gcm",
|
||||
"padding": "pkcs7",
|
||||
"cryptoFunctions": ["digest"],
|
||||
"classicalSecurityLevel": 128,
|
||||
"nistQuantumSecurityLevel": 1,
|
||||
"keySize": 256
|
||||
},
|
||||
"certificateProperties": {
|
||||
"subjectName": "CN=Test",
|
||||
"issuerName": "CA",
|
||||
"notValidBefore": "2026-01-01T00:00:00Z",
|
||||
"notValidAfter": "2027-01-01T00:00:00Z",
|
||||
"signatureAlgorithmRef": "sha256",
|
||||
"subjectPublicKeyRef": "key",
|
||||
"certificateFormat": "x509",
|
||||
"certificateExtension": "ext"
|
||||
},
|
||||
"protocolProperties": {
|
||||
"type": "tls",
|
||||
"version": "1.3",
|
||||
"cipherSuites": ["TLS_AES_128_GCM_SHA256"],
|
||||
"ikev2TransformTypes": ["aes"],
|
||||
"cryptoRefArray": ["ref1"]
|
||||
},
|
||||
"relatedCryptoMaterialProperties": {
|
||||
"type": "key",
|
||||
"id": "key-1",
|
||||
"algorithmRef": "alg-1",
|
||||
"securedBy": ["sec1"],
|
||||
"relatedCryptographicAssets": [
|
||||
{ "type": "certificate", "ref": "cert-1" }
|
||||
]
|
||||
},
|
||||
"oid": "1.2.3.4"
|
||||
},
|
||||
"modelCard": {
|
||||
"bom-ref": "model-1",
|
||||
"modelParameters": {
|
||||
"task": "classification",
|
||||
"architectureFamily": "cnn",
|
||||
"modelArchitecture": "resnet",
|
||||
"approach": { "type": "supervised" },
|
||||
"datasets": [
|
||||
{
|
||||
"name": "dataset1",
|
||||
"version": "1.0",
|
||||
"url": "https://example.com/ds",
|
||||
"hashes": [
|
||||
{ "alg": "SHA-256", "content": "abcd" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{ "format": "image", "description": "jpg" }
|
||||
],
|
||||
"outputs": [
|
||||
{ "format": "label", "description": "class" }
|
||||
]
|
||||
},
|
||||
"quantitativeAnalysis": {
|
||||
"performanceMetrics": [
|
||||
{
|
||||
"type": "accuracy",
|
||||
"value": "0.9",
|
||||
"slice": "overall",
|
||||
"confidenceInterval": { "lowerBound": "0.88", "upperBound": "0.92" }
|
||||
}
|
||||
],
|
||||
"graphics": {
|
||||
"collection": [
|
||||
{ "name": "roc", "image": "data:image/png;base64,aa", "description": "ROC" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"considerations": {
|
||||
"users": ["devs"],
|
||||
"useCases": ["testing"],
|
||||
"technicalLimitations": ["small dataset"],
|
||||
"ethicalConsiderations": [
|
||||
{ "name": "bias", "mitigationStrategy": "review" }
|
||||
],
|
||||
"fairnessAssessments": [
|
||||
{ "groupAtRisk": "group1", "benefits": "benefit", "harms": "harm", "mitigationStrategy": "mit" }
|
||||
],
|
||||
"environmentalConsiderations": {
|
||||
"energyConsumptions": [
|
||||
{
|
||||
"activity": "training",
|
||||
"activityEnergyCost": "10kWh",
|
||||
"co2CostEquivalent": "5kg",
|
||||
"co2CostOffset": "1kg",
|
||||
"properties": [
|
||||
{ "name": "region", "value": "us" }
|
||||
],
|
||||
"energyProviders": [
|
||||
{
|
||||
"bom-ref": "prov",
|
||||
"description": "provider",
|
||||
"organization": { "name": "EnergyCo" },
|
||||
"energySource": "solar",
|
||||
"energyProvided": "5kWh",
|
||||
"externalReferences": [
|
||||
{ "type": "website", "url": "https://energy.example.com" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{ "name": "note", "value": "env" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
"license": {
|
||||
"id": "MIT",
|
||||
"name": "MIT License",
|
||||
"url": "https://example.com/license",
|
||||
"text": {
|
||||
"content": "TUlU",
|
||||
"encoding": "base64"
|
||||
},
|
||||
"licensing": {
|
||||
"licensor": { "name": "Acme" },
|
||||
"licensee": "Consumer",
|
||||
"purchaseOrder": "PO-123"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"expression": "MIT AND (Apache-2.0 OR BSD-3-Clause)"
|
||||
}
|
||||
],
|
||||
"externalReferences": [
|
||||
{ "type": "website", "url": "https://example.com/lib", "comment": "home" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"ref": "app",
|
||||
"dependsOn": ["lib"]
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"bom-ref": "svc",
|
||||
"name": "api",
|
||||
"version": "1.0.0",
|
||||
"authenticated": true,
|
||||
"x-trust-boundary": true,
|
||||
"endpoints": ["https://api.example.com"],
|
||||
"licenses": [
|
||||
{ "expression": "Apache-2.0" }
|
||||
],
|
||||
"externalReferences": [
|
||||
{ "type": "documentation", "url": "https://example.com/api-docs" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"formulation": [
|
||||
{
|
||||
"bom-ref": "form-1",
|
||||
"components": [
|
||||
"lib",
|
||||
{
|
||||
"ref": "app",
|
||||
"properties": [
|
||||
{ "name": "stage", "value": "build" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"workflows": [
|
||||
{
|
||||
"name": "build",
|
||||
"description": "build pipeline",
|
||||
"inputs": ["src"],
|
||||
"outputs": ["artifact"],
|
||||
"tasks": [
|
||||
{
|
||||
"name": "compile",
|
||||
"description": "compile sources",
|
||||
"inputs": ["src"],
|
||||
"outputs": ["bin"],
|
||||
"parameters": [
|
||||
{ "name": "opt", "value": "O2" }
|
||||
],
|
||||
"properties": [
|
||||
{ "name": "runner", "value": "msbuild" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{ "name": "workflow", "value": "ci" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"name": "package",
|
||||
"description": "package app",
|
||||
"inputs": ["bin"],
|
||||
"outputs": ["artifact"],
|
||||
"parameters": [
|
||||
{ "name": "format", "value": "zip" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{ "name": "formulation", "value": "v1" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(content));
|
||||
|
||||
var result = await _parser.ParseAsync(stream, SbomFormat.CycloneDX);
|
||||
|
||||
result.Format.Should().Be("cyclonedx");
|
||||
result.SpecVersion.Should().Be("1.7");
|
||||
result.SerialNumber.Should().Be("urn:uuid:1234");
|
||||
result.Metadata.Name.Should().Be("myapp");
|
||||
result.Metadata.Version.Should().Be("1.0.0");
|
||||
result.Metadata.Supplier.Should().Be("Acme");
|
||||
result.Metadata.Manufacturer.Should().Be("AcmeManu");
|
||||
result.Components.Should().Contain(c => c.BomRef == "app");
|
||||
result.Components.Should().Contain(c => c.Purl == "pkg:npm/lib@2.0.0");
|
||||
var lib = result.Components.Single(c => c.BomRef == "lib");
|
||||
lib.ExternalReferences.Should().ContainSingle(r =>
|
||||
r.Type == "website" && r.Url == "https://example.com/lib");
|
||||
lib.Scope.Should().Be(ComponentScope.Optional);
|
||||
lib.Modified.Should().BeTrue();
|
||||
lib.Supplier.Should().NotBeNull();
|
||||
lib.Supplier!.Name.Should().Be("LibSupplier");
|
||||
lib.Supplier!.Url.Should().Be("https://supplier.example.com");
|
||||
lib.Manufacturer.Should().NotBeNull();
|
||||
lib.Manufacturer!.Name.Should().Be("LibManufacturer");
|
||||
lib.Evidence.Should().NotBeNull();
|
||||
lib.Evidence!.Identity.Should().NotBeNull();
|
||||
lib.Evidence!.Identity!.Field.Should().Be("purl");
|
||||
lib.Evidence!.Identity!.Confidence.Should().Be(0.9);
|
||||
lib.Evidence!.Occurrences.Should().ContainSingle(o => o.Location == "src/lib.js");
|
||||
lib.Evidence!.Callstack.Should().NotBeNull();
|
||||
lib.Evidence!.Callstack!.Frames.Should().ContainSingle(f => f.Function == "fn");
|
||||
lib.Evidence!.Licenses.Should().ContainSingle(l => l.Expression != null);
|
||||
lib.Evidence!.Copyrights.Should().ContainSingle(c => c == "Copyright 2026");
|
||||
lib.Pedigree.Should().NotBeNull();
|
||||
lib.Pedigree!.Ancestors.Should().ContainSingle(a => a.BomRef == "ancestor");
|
||||
lib.Pedigree!.Variants.Should().ContainSingle(v => v.BomRef == "variant");
|
||||
lib.Pedigree!.Commits.Should().ContainSingle(c => c.BomRef == "abc123");
|
||||
lib.Pedigree!.Patches.Should().ContainSingle(p => p.Type == "backport");
|
||||
lib.Pedigree!.Notes.Should().Contain(n => n == "note1");
|
||||
lib.CryptoProperties.Should().NotBeNull();
|
||||
lib.CryptoProperties!.AssetType.Should().Be(CryptoAssetType.Algorithm);
|
||||
lib.CryptoProperties!.AlgorithmProperties.Should().NotBeNull();
|
||||
lib.CryptoProperties!.AlgorithmProperties!.Primitive.Should().Be(CryptoPrimitive.Hash);
|
||||
lib.CryptoProperties!.CertificateProperties.Should().NotBeNull();
|
||||
lib.CryptoProperties!.CertificateProperties!.SubjectName.Should().Be("CN=Test");
|
||||
lib.CryptoProperties!.ProtocolProperties.Should().NotBeNull();
|
||||
lib.CryptoProperties!.ProtocolProperties!.Type.Should().Be("tls");
|
||||
lib.CryptoProperties!.RelatedCryptoMaterial.Should().NotBeNull();
|
||||
lib.CryptoProperties!.RelatedCryptoMaterial!.Reference.Should().Be("key-1");
|
||||
lib.CryptoProperties!.RelatedCryptoMaterial!.MaterialRefs.Should().Contain("cert-1");
|
||||
lib.ModelCard.Should().NotBeNull();
|
||||
lib.ModelCard!.BomRef.Should().Be("model-1");
|
||||
lib.ModelCard!.ModelParameters.Should().NotBeNull();
|
||||
lib.ModelCard!.ModelParameters!.Task.Should().Be("classification");
|
||||
lib.ModelCard!.ModelParameters!.Datasets.Should().ContainSingle(d => d.Name == "dataset1");
|
||||
lib.ModelCard!.QuantitativeAnalysis.Should().NotBeNull();
|
||||
lib.ModelCard!.QuantitativeAnalysis!.PerformanceMetrics.Should().ContainSingle(m => m.Type == "accuracy");
|
||||
lib.ModelCard!.Considerations.Should().NotBeNull();
|
||||
lib.ModelCard!.Considerations!.Users.Should().ContainSingle(u => u == "devs");
|
||||
lib.Licenses.Should().HaveCount(2);
|
||||
lib.Licenses[0].SpdxId.Should().Be("MIT");
|
||||
lib.Licenses[0].Name.Should().Be("MIT License");
|
||||
lib.Licenses[0].Url.Should().Be("https://example.com/license");
|
||||
lib.Licenses[0].Text.Should().Be("MIT");
|
||||
lib.Licenses[0].Licensing.Should().NotBeNull();
|
||||
lib.Licenses[0].Licensing!.Licensor.Should().Be("Acme");
|
||||
lib.Licenses[0].Licensing!.Licensee.Should().Be("Consumer");
|
||||
lib.Licenses[0].Licensing!.PurchaseOrder.Should().Be("PO-123");
|
||||
lib.Licenses[1].Expression.Should().BeOfType<ConjunctiveSet>();
|
||||
var andExpr = (ConjunctiveSet)lib.Licenses[1].Expression!;
|
||||
andExpr.Members.Should().HaveCount(2);
|
||||
andExpr.Members[0].Should().BeOfType<SimpleLicense>()
|
||||
.Which.Id.Should().Be("MIT");
|
||||
andExpr.Members[1].Should().BeOfType<DisjunctiveSet>();
|
||||
var orExpr = (DisjunctiveSet)andExpr.Members[1];
|
||||
orExpr.Members.OfType<SimpleLicense>()
|
||||
.Should()
|
||||
.ContainSingle(license => license.Id == "Apache-2.0");
|
||||
result.Dependencies.Should().ContainSingle(d => d.SourceRef == "app");
|
||||
result.Services.Should().ContainSingle(s => s.Name == "api");
|
||||
result.Services[0].Authenticated.Should().BeTrue();
|
||||
result.Services[0].CrossesTrustBoundary.Should().BeTrue();
|
||||
result.Services[0].Licenses.Should().ContainSingle();
|
||||
result.Services[0].ExternalReferences.Should().ContainSingle(r =>
|
||||
r.Type == "documentation" && r.Url == "https://example.com/api-docs");
|
||||
result.Formulation.Should().NotBeNull();
|
||||
result.Formulation!.BomRef.Should().Be("form-1");
|
||||
result.Formulation.Components.Should().HaveCount(2);
|
||||
result.Formulation.Components.Should().ContainSingle(c => c.BomRef == "lib");
|
||||
result.Formulation.Components.Should().ContainSingle(c =>
|
||||
c.BomRef == "app" && c.Properties.ContainsKey("stage"));
|
||||
result.Formulation.Workflows.Should().ContainSingle(w => w.Name == "build");
|
||||
var workflow = result.Formulation.Workflows.Single(w => w.Name == "build");
|
||||
workflow.InputRefs.Should().Contain("src");
|
||||
workflow.OutputRefs.Should().Contain("artifact");
|
||||
workflow.Tasks.Should().ContainSingle(t => t.Name == "compile");
|
||||
workflow.Tasks[0].Parameters.Should().ContainKey("opt").WhoseValue.Should().Be("O2");
|
||||
workflow.Tasks[0].Properties.Should().ContainKey("runner").WhoseValue.Should().Be("msbuild");
|
||||
result.Formulation.Tasks.Should().ContainSingle(t => t.Name == "package");
|
||||
result.Formulation.Tasks[0].Parameters.Should().ContainKey("format").WhoseValue.Should().Be("zip");
|
||||
result.Formulation.Properties.Should().ContainKey("formulation").WhoseValue.Should().Be("v1");
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ParseAsync_Spdx3_ExtractsDocumentAndPackageMetadata()
|
||||
{
|
||||
var content = """
|
||||
{
|
||||
"@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
|
||||
"@graph": [
|
||||
{
|
||||
"@type": "SpdxDocument",
|
||||
"spdxId": "urn:doc",
|
||||
"name": "sbom-doc",
|
||||
"creationInfo": {
|
||||
"specVersion": "3.0.1",
|
||||
"created": "2026-01-20T00:00:00Z",
|
||||
"createdBy": ["org:Acme"],
|
||||
"createdUsing": ["tool:stella"],
|
||||
"profile": ["https://spdx.org/rdf/3.0.1/terms/Core/ProfileIdentifierType/core"]
|
||||
},
|
||||
"rootElement": ["spdx:pkg:root"],
|
||||
"namespaceMap": [
|
||||
{ "prefix": "ex", "namespace": "https://example.com" }
|
||||
],
|
||||
"import": [
|
||||
{ "externalSpdxId": "urn:ext" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"@type": "build_Build",
|
||||
"spdxId": "spdx:build:1",
|
||||
"buildId": "build-123",
|
||||
"buildType": "release",
|
||||
"buildStartTime": "2026-01-20T01:00:00Z",
|
||||
"buildEndTime": "2026-01-20T01:30:00Z",
|
||||
"configSourceEntrypoint": "build.yaml",
|
||||
"configSourceDigest": "sha256:abcd",
|
||||
"configSourceUri": "https://example.com/build.yaml",
|
||||
"environment": [
|
||||
{ "name": "OS", "value": "linux" }
|
||||
],
|
||||
"parameters": {
|
||||
"opt": "O2",
|
||||
"threads": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "software_Package",
|
||||
"spdxId": "spdx:pkg:root",
|
||||
"name": "root",
|
||||
"software_packageVersion": "1.0.0",
|
||||
"packageUrl": "pkg:npm/root@1.0.0",
|
||||
"simplelicensing_licenseExpression": "Apache-2.0 WITH LLVM-exception",
|
||||
"verifiedUsing": [
|
||||
{ "algorithm": "SHA256", "hashValue": "abc" }
|
||||
],
|
||||
"externalRef": [
|
||||
{ "externalRefType": "purl", "locator": "pkg:npm/root@1.0.0", "comment": "source" }
|
||||
],
|
||||
"externalIdentifier": [
|
||||
{ "externalIdentifierType": "cpe23", "identifier": "cpe:2.3:a:root" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(content));
|
||||
|
||||
var result = await _parser.ParseAsync(stream, SbomFormat.SPDX);
|
||||
|
||||
result.Format.Should().Be("spdx");
|
||||
result.SpecVersion.Should().Be("3.0.1");
|
||||
result.SerialNumber.Should().Be("urn:doc");
|
||||
result.Metadata.Name.Should().Be("sbom-doc");
|
||||
result.Metadata.RootComponentRef.Should().Be("spdx:pkg:root");
|
||||
result.Metadata.NamespaceMap.Should().ContainSingle(map => map.Prefix == "ex");
|
||||
result.BuildInfo.Should().NotBeNull();
|
||||
result.BuildInfo!.BuildId.Should().Be("build-123");
|
||||
result.BuildInfo.BuildType.Should().Be("release");
|
||||
result.BuildInfo.BuildStartTime.Should().Be(DateTimeOffset.Parse("2026-01-20T01:00:00Z"));
|
||||
result.BuildInfo.BuildEndTime.Should().Be(DateTimeOffset.Parse("2026-01-20T01:30:00Z"));
|
||||
result.BuildInfo.ConfigSourceEntrypoint.Should().Be("build.yaml");
|
||||
result.BuildInfo.ConfigSourceDigest.Should().Be("sha256:abcd");
|
||||
result.BuildInfo.ConfigSourceUri.Should().Be("https://example.com/build.yaml");
|
||||
result.BuildInfo.Environment.Should().ContainKey("OS").WhoseValue.Should().Be("linux");
|
||||
result.BuildInfo.Parameters.Should().ContainKey("opt").WhoseValue.Should().Be("O2");
|
||||
result.BuildInfo.Parameters.Should().ContainKey("threads").WhoseValue.Should().Be("8");
|
||||
var root = result.Components.Single(c => c.Purl == "pkg:npm/root@1.0.0");
|
||||
root.Cpe.Should().Be("cpe:2.3:a:root");
|
||||
root.Hashes.Should().ContainSingle(h => h.Algorithm == "SHA256" && h.Value == "abc");
|
||||
root.ExternalReferences.Should().ContainSingle(r =>
|
||||
r.Type == "purl" && r.Url == "pkg:npm/root@1.0.0");
|
||||
root.Licenses.Should().ContainSingle();
|
||||
root.Licenses[0].Expression.Should().BeOfType<WithException>();
|
||||
var withExpr = (WithException)root.Licenses[0].Expression!;
|
||||
withExpr.Exception.Should().Be("LLVM-exception");
|
||||
withExpr.License.Should().BeOfType<SimpleLicense>()
|
||||
.Which.Id.Should().Be("Apache-2.0");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// <copyright file="ConcelierSchemaEvolutionTests.cs" company="StellaOps">
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
// Copyright (c) StellaOps. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
// Sprint: SPRINT_20260105_002_005_TEST_cross_cutting
|
||||
// Task: CCUT-010
|
||||
|
||||
Reference in New Issue
Block a user