audit work, fixed StellaOps.sln warnings/errors, fixed tests, sprints work, new advisories

This commit is contained in:
master
2026-01-07 18:49:59 +02:00
parent 04ec098046
commit 608a7f85c0
866 changed files with 56323 additions and 6231 deletions

View File

@@ -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();
}
}