Files
git.stella-ops.org/tests/AirGap/StellaOps.AirGap.Importer.Tests/Reconciliation/CycloneDxParserTests.cs
master 2170a58734
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
Add comprehensive security tests for OWASP A02, A05, A07, and A08 categories
- Implemented tests for Cryptographic Failures (A02) to ensure proper handling of sensitive data, secure algorithms, and key management.
- Added tests for Security Misconfiguration (A05) to validate production configurations, security headers, CORS settings, and feature management.
- Developed tests for Authentication Failures (A07) to enforce strong password policies, rate limiting, session management, and MFA support.
- Created tests for Software and Data Integrity Failures (A08) to verify artifact signatures, SBOM integrity, attestation chains, and feed updates.
2025-12-16 16:40:44 +02:00

137 lines
4.3 KiB
C#

// =============================================================================
// CycloneDxParserTests.cs
// Golden-file tests for CycloneDX SBOM parsing
// Part of Task T24: Golden-file tests for determinism
// =============================================================================
using FluentAssertions;
using StellaOps.AirGap.Importer.Reconciliation;
using StellaOps.AirGap.Importer.Reconciliation.Parsers;
namespace StellaOps.AirGap.Importer.Tests.Reconciliation;
public sealed class CycloneDxParserTests
{
private static readonly string FixturesPath = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
"Reconciliation", "Fixtures");
[Fact]
public async Task ParseAsync_ValidCycloneDx_ExtractsAllSubjects()
{
// Arrange
var parser = new CycloneDxParser();
var filePath = Path.Combine(FixturesPath, "sample.cdx.json");
// Skip if fixtures not available
if (!File.Exists(filePath))
{
return;
}
// Act
var result = await parser.ParseAsync(filePath);
// Assert
result.IsSuccess.Should().BeTrue();
result.Format.Should().Be(SbomFormat.CycloneDx);
result.SpecVersion.Should().Be("1.6");
result.SerialNumber.Should().Be("urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79");
result.GeneratorTool.Should().Contain("syft");
// Should have 3 subjects with SHA-256 hashes (primary + 2 components)
result.Subjects.Should().HaveCount(3);
// Verify subjects are sorted by digest
result.Subjects.Should().BeInAscendingOrder(s => s.Digest, StringComparer.Ordinal);
}
[Fact]
public async Task ParseAsync_ExtractsPrimarySubject()
{
// Arrange
var parser = new CycloneDxParser();
var filePath = Path.Combine(FixturesPath, "sample.cdx.json");
if (!File.Exists(filePath))
{
return;
}
// Act
var result = await parser.ParseAsync(filePath);
// Assert
result.PrimarySubject.Should().NotBeNull();
result.PrimarySubject!.Name.Should().Be("test-app");
result.PrimarySubject.Version.Should().Be("1.0.0");
result.PrimarySubject.Digest.Should().StartWith("sha256:");
}
[Fact]
public async Task ParseAsync_SubjectDigestsAreNormalized()
{
// Arrange
var parser = new CycloneDxParser();
var filePath = Path.Combine(FixturesPath, "sample.cdx.json");
if (!File.Exists(filePath))
{
return;
}
// Act
var result = await parser.ParseAsync(filePath);
// Assert - all digests should be normalized sha256:lowercase format
foreach (var subject in result.Subjects)
{
subject.Digest.Should().StartWith("sha256:");
subject.Digest[7..].Should().MatchRegex("^[a-f0-9]{64}$");
}
}
[Fact]
public void DetectFormat_CycloneDxFile_ReturnsCycloneDx()
{
var parser = new CycloneDxParser();
parser.DetectFormat("test.cdx.json").Should().Be(SbomFormat.CycloneDx);
parser.DetectFormat("test.bom.json").Should().Be(SbomFormat.CycloneDx);
}
[Fact]
public void DetectFormat_NonCycloneDxFile_ReturnsUnknown()
{
var parser = new CycloneDxParser();
parser.DetectFormat("test.spdx.json").Should().Be(SbomFormat.Unknown);
parser.DetectFormat("test.json").Should().Be(SbomFormat.Unknown);
}
[Fact]
public async Task ParseAsync_Deterministic_SameOutputForSameInput()
{
// Arrange
var parser = new CycloneDxParser();
var filePath = Path.Combine(FixturesPath, "sample.cdx.json");
if (!File.Exists(filePath))
{
return;
}
// Act - parse twice
var result1 = await parser.ParseAsync(filePath);
var result2 = await parser.ParseAsync(filePath);
// Assert - results should be identical
result1.Subjects.Select(s => s.Digest)
.Should().BeEquivalentTo(result2.Subjects.Select(s => s.Digest));
result1.Subjects.Select(s => s.Name)
.Should().BeEquivalentTo(result2.Subjects.Select(s => s.Name));
// Order should be the same
result1.Subjects.Select(s => s.Digest).Should().Equal(result2.Subjects.Select(s => s.Digest));
}
}