Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user