231 lines
7.0 KiB
C#
231 lines
7.0 KiB
C#
// -----------------------------------------------------------------------------
|
|
// 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<OperationCanceledException>(
|
|
() => _service.ExportAsync(request, cts.Token));
|
|
}
|
|
|
|
// Mock implementation for testing
|
|
private class MockAuditBundleWriter : IAuditBundleWriter
|
|
{
|
|
public Task<AuditBundleWriteResult> WriteAsync(
|
|
AuditBundleWriteRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return Task.FromResult(new AuditBundleWriteResult { Success = true });
|
|
}
|
|
}
|
|
}
|