// ----------------------------------------------------------------------------- // AuditPackExportServiceTests.cs // Sprint: SPRINT_1227_0005_0003_FE_copy_audit_export // Task: T10 — Unit tests for AuditPackExportService // ----------------------------------------------------------------------------- namespace StellaOps.AuditPack.Tests; using StellaOps.AuditPack.Models; using StellaOps.AuditPack.Services; using System.IO.Compression; using System.Text.Json; [Trait("Category", "Unit")] public class AuditPackExportServiceTests { private readonly AuditPackExportService _service; public AuditPackExportServiceTests() { _service = new AuditPackExportService( new MockAuditBundleWriter(), null); } [Fact] public async Task ExportAsync_Zip_CreatesValidZipArchive() { // Arrange var request = new ExportRequest { ScanId = "scan-123", Format = ExportFormat.Zip, Segments = [ExportSegment.Sbom, ExportSegment.Match], IncludeAttestations = true, IncludeProofChain = false, Filename = "test-export" }; // Act var result = await _service.ExportAsync(request); // Assert result.Success.Should().BeTrue(); result.ContentType.Should().Be("application/zip"); result.Filename.Should().Be("test-export.zip"); result.Data.Should().NotBeNull(); result.SizeBytes.Should().BeGreaterThan(0); // Verify ZIP structure using var memoryStream = new MemoryStream(result.Data!); using var archive = new ZipArchive(memoryStream, ZipArchiveMode.Read); archive.Entries.Should().Contain(e => e.FullName == "manifest.json"); } [Fact] public async Task ExportAsync_Json_CreatesSingleJsonFile() { // Arrange var request = new ExportRequest { ScanId = "scan-456", Format = ExportFormat.Json, Segments = [ExportSegment.Sbom], IncludeAttestations = false, IncludeProofChain = false, Filename = "test-json" }; // Act var result = await _service.ExportAsync(request); // Assert result.Success.Should().BeTrue(); result.ContentType.Should().Be("application/json"); result.Filename.Should().Be("test-json.json"); // Verify JSON structure using var jsonDoc = JsonDocument.Parse(result.Data!); var root = jsonDoc.RootElement; root.TryGetProperty("scanId", out var scanIdProp).Should().BeTrue(); scanIdProp.GetString().Should().Be("scan-456"); root.TryGetProperty("segments", out _).Should().BeTrue(); } [Fact] public async Task ExportAsync_Dsse_CreatesDsseEnvelope() { // Arrange var request = new ExportRequest { ScanId = "scan-789", Format = ExportFormat.Dsse, Segments = [ExportSegment.Policy], IncludeAttestations = true, IncludeProofChain = true, Filename = "test-dsse" }; // Act var result = await _service.ExportAsync(request); // Assert result.Success.Should().BeTrue(); result.ContentType.Should().Be("application/vnd.dsse+json"); result.Filename.Should().Be("test-dsse.dsse.json"); // Verify DSSE structure using var jsonDoc = JsonDocument.Parse(result.Data!); var root = jsonDoc.RootElement; root.TryGetProperty("payloadType", out var payloadType).Should().BeTrue(); payloadType.GetString().Should().Be("application/vnd.stellaops.audit-pack+json"); root.TryGetProperty("payload", out _).Should().BeTrue(); root.TryGetProperty("signatures", out _).Should().BeTrue(); } [Fact] public async Task ExportAsync_AllSegments_IncludesAllInZip() { // Arrange var allSegments = new[] { ExportSegment.Sbom, ExportSegment.Match, ExportSegment.Reachability, ExportSegment.Guards, ExportSegment.Runtime, ExportSegment.Policy }; var request = new ExportRequest { ScanId = "scan-full", Format = ExportFormat.Zip, Segments = allSegments, IncludeAttestations = true, IncludeProofChain = true, Filename = "full-export" }; // Act var result = await _service.ExportAsync(request); // Assert result.Success.Should().BeTrue(); using var memoryStream = new MemoryStream(result.Data!); using var archive = new ZipArchive(memoryStream, ZipArchiveMode.Read); // Should have manifest + 6 segments + attestations + proof chain archive.Entries.Count.Should().BeGreaterThanOrEqualTo(3); } [Fact] public async Task ExportAsync_EmptySegments_StillCreatesValidExport() { // Arrange var request = new ExportRequest { ScanId = "scan-empty", Format = ExportFormat.Json, Segments = [], IncludeAttestations = false, IncludeProofChain = false, Filename = "empty-export" }; // Act var result = await _service.ExportAsync(request); // Assert result.Success.Should().BeTrue(); result.Data.Should().NotBeNull(); } [Fact] public async Task ExportAsync_UnsupportedFormat_ReturnsFailed() { // Arrange var request = new ExportRequest { ScanId = "scan-fail", Format = (ExportFormat)999, // Invalid format Segments = [ExportSegment.Sbom], IncludeAttestations = false, IncludeProofChain = false, Filename = "fail-export" }; // Act var result = await _service.ExportAsync(request); // Assert result.Success.Should().BeFalse(); result.Error.Should().Contain("Unsupported"); } [Fact] public async Task ExportAsync_CancellationRequested_ThrowsOperationCanceled() { // Arrange var request = new ExportRequest { ScanId = "scan-cancel", Format = ExportFormat.Zip, Segments = [ExportSegment.Sbom], IncludeAttestations = false, IncludeProofChain = false, Filename = "cancel-export" }; var cts = new CancellationTokenSource(); cts.Cancel(); // Act & Assert await Assert.ThrowsAsync( () => _service.ExportAsync(request, cts.Token)); } // Mock implementation for testing private class MockAuditBundleWriter : IAuditBundleWriter { public Task WriteAsync( AuditBundleWriteRequest request, CancellationToken cancellationToken = default) { return Task.FromResult(new AuditBundleWriteResult { Success = true }); } } }