audit work, fixed StellaOps.sln warnings/errors, fixed tests, sprints work, new advisories
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// ExportEndpointsTests.cs
|
||||
// Sprint: SPRINT_20260106_003_003_EVIDENCE_export_bundle
|
||||
// Task: T024
|
||||
// Description: Integration tests for evidence bundle export API endpoints.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using StellaOps.EvidenceLocker.Api;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.EvidenceLocker.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for export API endpoints.
|
||||
/// </summary>
|
||||
public sealed class ExportEndpointsTests : IClassFixture<WebApplicationFactory<Program>>
|
||||
{
|
||||
private readonly WebApplicationFactory<Program> _factory;
|
||||
|
||||
public ExportEndpointsTests(WebApplicationFactory<Program> factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TriggerExport_ValidBundle_Returns202Accepted()
|
||||
{
|
||||
// Arrange
|
||||
var mockService = new Mock<IExportJobService>();
|
||||
mockService
|
||||
.Setup(s => s.CreateExportJobAsync(
|
||||
"bundle-123",
|
||||
It.IsAny<ExportTriggerRequest>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new ExportJobResult
|
||||
{
|
||||
ExportId = "exp-123",
|
||||
Status = "pending",
|
||||
EstimatedSize = 1024
|
||||
});
|
||||
|
||||
var client = CreateClientWithMock(mockService.Object);
|
||||
var request = new ExportTriggerRequest();
|
||||
|
||||
// Act
|
||||
var response = await client.PostAsJsonAsync("/api/v1/bundles/bundle-123/export", request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ExportTriggerResponse>();
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("exp-123", result.ExportId);
|
||||
Assert.Equal("pending", result.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TriggerExport_BundleNotFound_Returns404()
|
||||
{
|
||||
// Arrange
|
||||
var mockService = new Mock<IExportJobService>();
|
||||
mockService
|
||||
.Setup(s => s.CreateExportJobAsync(
|
||||
"nonexistent",
|
||||
It.IsAny<ExportTriggerRequest>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new ExportJobResult { IsNotFound = true });
|
||||
|
||||
var client = CreateClientWithMock(mockService.Object);
|
||||
var request = new ExportTriggerRequest();
|
||||
|
||||
// Act
|
||||
var response = await client.PostAsJsonAsync("/api/v1/bundles/nonexistent/export", request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetExportStatus_ExportReady_Returns200WithDownloadUrl()
|
||||
{
|
||||
// Arrange
|
||||
var mockService = new Mock<IExportJobService>();
|
||||
mockService
|
||||
.Setup(s => s.GetExportStatusAsync("bundle-123", "exp-123", It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new ExportJobStatus
|
||||
{
|
||||
ExportId = "exp-123",
|
||||
Status = ExportJobStatusEnum.Ready,
|
||||
FileSize = 2048,
|
||||
CompletedAt = DateTimeOffset.Parse("2026-01-07T12:00:00Z")
|
||||
});
|
||||
|
||||
var client = CreateClientWithMock(mockService.Object);
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/v1/bundles/bundle-123/export/exp-123");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ExportStatusResponse>();
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("ready", result.Status);
|
||||
Assert.Contains("download", result.DownloadUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetExportStatus_ExportProcessing_Returns202()
|
||||
{
|
||||
// Arrange
|
||||
var mockService = new Mock<IExportJobService>();
|
||||
mockService
|
||||
.Setup(s => s.GetExportStatusAsync("bundle-123", "exp-123", It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new ExportJobStatus
|
||||
{
|
||||
ExportId = "exp-123",
|
||||
Status = ExportJobStatusEnum.Processing,
|
||||
Progress = 50,
|
||||
EstimatedTimeRemaining = "30s"
|
||||
});
|
||||
|
||||
var client = CreateClientWithMock(mockService.Object);
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/v1/bundles/bundle-123/export/exp-123");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ExportStatusResponse>();
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("processing", result.Status);
|
||||
Assert.Equal(50, result.Progress);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetExportStatus_ExportNotFound_Returns404()
|
||||
{
|
||||
// Arrange
|
||||
var mockService = new Mock<IExportJobService>();
|
||||
mockService
|
||||
.Setup(s => s.GetExportStatusAsync("bundle-123", "nonexistent", It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((ExportJobStatus?)null);
|
||||
|
||||
var client = CreateClientWithMock(mockService.Object);
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/v1/bundles/bundle-123/export/nonexistent");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DownloadExport_ExportReady_ReturnsFileStream()
|
||||
{
|
||||
// Arrange
|
||||
var mockService = new Mock<IExportJobService>();
|
||||
var testStream = new MemoryStream(new byte[] { 0x1f, 0x8b, 0x08 }); // gzip magic bytes
|
||||
|
||||
mockService
|
||||
.Setup(s => s.GetExportFileAsync("bundle-123", "exp-123", It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new ExportFileResult
|
||||
{
|
||||
Status = ExportJobStatusEnum.Ready,
|
||||
FileStream = testStream,
|
||||
FileName = "evidence-bundle-123.tar.gz"
|
||||
});
|
||||
|
||||
var client = CreateClientWithMock(mockService.Object);
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/v1/bundles/bundle-123/export/exp-123/download");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("application/gzip", response.Content.Headers.ContentType?.MediaType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DownloadExport_ExportNotReady_Returns409Conflict()
|
||||
{
|
||||
// Arrange
|
||||
var mockService = new Mock<IExportJobService>();
|
||||
mockService
|
||||
.Setup(s => s.GetExportFileAsync("bundle-123", "exp-123", It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new ExportFileResult
|
||||
{
|
||||
Status = ExportJobStatusEnum.Processing
|
||||
});
|
||||
|
||||
var client = CreateClientWithMock(mockService.Object);
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/v1/bundles/bundle-123/export/exp-123/download");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.Conflict, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DownloadExport_ExportNotFound_Returns404()
|
||||
{
|
||||
// Arrange
|
||||
var mockService = new Mock<IExportJobService>();
|
||||
mockService
|
||||
.Setup(s => s.GetExportFileAsync("bundle-123", "nonexistent", It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((ExportFileResult?)null);
|
||||
|
||||
var client = CreateClientWithMock(mockService.Object);
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/v1/bundles/bundle-123/export/nonexistent/download");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TriggerExport_WithOptions_PassesOptionsToService()
|
||||
{
|
||||
// Arrange
|
||||
ExportTriggerRequest? capturedRequest = null;
|
||||
var mockService = new Mock<IExportJobService>();
|
||||
mockService
|
||||
.Setup(s => s.CreateExportJobAsync(
|
||||
"bundle-123",
|
||||
It.IsAny<ExportTriggerRequest>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Callback<string, ExportTriggerRequest, CancellationToken>((_, req, _) => capturedRequest = req)
|
||||
.ReturnsAsync(new ExportJobResult
|
||||
{
|
||||
ExportId = "exp-123",
|
||||
Status = "pending"
|
||||
});
|
||||
|
||||
var client = CreateClientWithMock(mockService.Object);
|
||||
var request = new ExportTriggerRequest
|
||||
{
|
||||
CompressionLevel = 9,
|
||||
IncludeLayerSboms = false,
|
||||
IncludeRekorProofs = false
|
||||
};
|
||||
|
||||
// Act
|
||||
await client.PostAsJsonAsync("/api/v1/bundles/bundle-123/export", request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(capturedRequest);
|
||||
Assert.Equal(9, capturedRequest.CompressionLevel);
|
||||
Assert.False(capturedRequest.IncludeLayerSboms);
|
||||
Assert.False(capturedRequest.IncludeRekorProofs);
|
||||
}
|
||||
|
||||
private HttpClient CreateClientWithMock(IExportJobService mockService)
|
||||
{
|
||||
return _factory.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
// Remove existing registration
|
||||
var descriptor = services.SingleOrDefault(
|
||||
d => d.ServiceType == typeof(IExportJobService));
|
||||
if (descriptor != null)
|
||||
{
|
||||
services.Remove(descriptor);
|
||||
}
|
||||
|
||||
// Add mock
|
||||
services.AddSingleton(mockService);
|
||||
});
|
||||
}).CreateClient();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user