stabilize tests

This commit is contained in:
master
2026-02-01 21:37:40 +02:00
parent 55744f6a39
commit 5d5e80b2e4
6435 changed files with 33984 additions and 13802 deletions

View File

@@ -5,25 +5,32 @@
// Description: Integration tests for evidence bundle export API endpoints.
// -----------------------------------------------------------------------------
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using EvidenceLockerProgram = StellaOps.EvidenceLocker.WebService.Program;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using StellaOps.EvidenceLocker.Api;
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Xunit;
namespace StellaOps.EvidenceLocker.Tests;
/// <summary>
/// Integration tests for export API endpoints.
/// Uses the shared EvidenceLockerWebApplicationFactory (via Collection fixture)
/// instead of raw WebApplicationFactory&lt;Program&gt; to avoid loading real
/// infrastructure services (database, auth, background services) which causes
/// the test process to hang and consume excessive memory.
/// </summary>
public sealed class ExportEndpointsTests : IClassFixture<WebApplicationFactory<Program>>
[Collection(EvidenceLockerTestCollection.Name)]
public sealed class ExportEndpointsTests
{
private readonly WebApplicationFactory<Program> _factory;
private readonly EvidenceLockerWebApplicationFactory _factory;
public ExportEndpointsTests(WebApplicationFactory<Program> factory)
public ExportEndpointsTests(EvidenceLockerWebApplicationFactory factory)
{
_factory = factory;
}
@@ -45,11 +52,11 @@ public sealed class ExportEndpointsTests : IClassFixture<WebApplicationFactory<P
EstimatedSize = 1024
});
var client = CreateClientWithMock(mockService.Object);
using var scope = CreateClientWithMock(mockService.Object);
var request = new ExportTriggerRequest();
// Act
var response = await client.PostAsJsonAsync("/api/v1/bundles/bundle-123/export", request);
var response = await scope.Client.PostAsJsonAsync("/api/v1/bundles/bundle-123/export", request);
// Assert
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
@@ -72,11 +79,11 @@ public sealed class ExportEndpointsTests : IClassFixture<WebApplicationFactory<P
It.IsAny<CancellationToken>()))
.ReturnsAsync(new ExportJobResult { IsNotFound = true });
var client = CreateClientWithMock(mockService.Object);
using var scope = CreateClientWithMock(mockService.Object);
var request = new ExportTriggerRequest();
// Act
var response = await client.PostAsJsonAsync("/api/v1/bundles/nonexistent/export", request);
var response = await scope.Client.PostAsJsonAsync("/api/v1/bundles/nonexistent/export", request);
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
@@ -97,10 +104,10 @@ public sealed class ExportEndpointsTests : IClassFixture<WebApplicationFactory<P
CompletedAt = DateTimeOffset.Parse("2026-01-07T12:00:00Z")
});
var client = CreateClientWithMock(mockService.Object);
using var scope = CreateClientWithMock(mockService.Object);
// Act
var response = await client.GetAsync("/api/v1/bundles/bundle-123/export/exp-123");
var response = await scope.Client.GetAsync("/api/v1/bundles/bundle-123/export/exp-123");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -126,10 +133,10 @@ public sealed class ExportEndpointsTests : IClassFixture<WebApplicationFactory<P
EstimatedTimeRemaining = "30s"
});
var client = CreateClientWithMock(mockService.Object);
using var scope = CreateClientWithMock(mockService.Object);
// Act
var response = await client.GetAsync("/api/v1/bundles/bundle-123/export/exp-123");
var response = await scope.Client.GetAsync("/api/v1/bundles/bundle-123/export/exp-123");
// Assert
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
@@ -149,10 +156,10 @@ public sealed class ExportEndpointsTests : IClassFixture<WebApplicationFactory<P
.Setup(s => s.GetExportStatusAsync("bundle-123", "nonexistent", It.IsAny<CancellationToken>()))
.ReturnsAsync((ExportJobStatus?)null);
var client = CreateClientWithMock(mockService.Object);
using var scope = CreateClientWithMock(mockService.Object);
// Act
var response = await client.GetAsync("/api/v1/bundles/bundle-123/export/nonexistent");
var response = await scope.Client.GetAsync("/api/v1/bundles/bundle-123/export/nonexistent");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
@@ -174,10 +181,10 @@ public sealed class ExportEndpointsTests : IClassFixture<WebApplicationFactory<P
FileName = "evidence-bundle-123.tar.gz"
});
var client = CreateClientWithMock(mockService.Object);
using var scope = CreateClientWithMock(mockService.Object);
// Act
var response = await client.GetAsync("/api/v1/bundles/bundle-123/export/exp-123/download");
var response = await scope.Client.GetAsync("/api/v1/bundles/bundle-123/export/exp-123/download");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -196,10 +203,10 @@ public sealed class ExportEndpointsTests : IClassFixture<WebApplicationFactory<P
Status = ExportJobStatusEnum.Processing
});
var client = CreateClientWithMock(mockService.Object);
using var scope = CreateClientWithMock(mockService.Object);
// Act
var response = await client.GetAsync("/api/v1/bundles/bundle-123/export/exp-123/download");
var response = await scope.Client.GetAsync("/api/v1/bundles/bundle-123/export/exp-123/download");
// Assert
Assert.Equal(HttpStatusCode.Conflict, response.StatusCode);
@@ -214,10 +221,10 @@ public sealed class ExportEndpointsTests : IClassFixture<WebApplicationFactory<P
.Setup(s => s.GetExportFileAsync("bundle-123", "nonexistent", It.IsAny<CancellationToken>()))
.ReturnsAsync((ExportFileResult?)null);
var client = CreateClientWithMock(mockService.Object);
using var scope = CreateClientWithMock(mockService.Object);
// Act
var response = await client.GetAsync("/api/v1/bundles/bundle-123/export/nonexistent/download");
var response = await scope.Client.GetAsync("/api/v1/bundles/bundle-123/export/nonexistent/download");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
@@ -241,7 +248,7 @@ public sealed class ExportEndpointsTests : IClassFixture<WebApplicationFactory<P
Status = "pending"
});
var client = CreateClientWithMock(mockService.Object);
using var scope = CreateClientWithMock(mockService.Object);
var request = new ExportTriggerRequest
{
CompressionLevel = 9,
@@ -250,7 +257,7 @@ public sealed class ExportEndpointsTests : IClassFixture<WebApplicationFactory<P
};
// Act
await client.PostAsJsonAsync("/api/v1/bundles/bundle-123/export", request);
await scope.Client.PostAsJsonAsync("/api/v1/bundles/bundle-123/export", request);
// Assert
Assert.NotNull(capturedRequest);
@@ -259,9 +266,37 @@ public sealed class ExportEndpointsTests : IClassFixture<WebApplicationFactory<P
Assert.False(capturedRequest.IncludeRekorProofs);
}
private HttpClient CreateClientWithMock(IExportJobService mockService)
/// <summary>
/// Wraps a derived WebApplicationFactory and HttpClient so both are disposed together.
/// Previously, WithWebHostBuilder() created a new factory per test that was never disposed,
/// leaking TestServer instances and consuming gigabytes of memory.
/// </summary>
/// <summary>
/// Wraps a derived WebApplicationFactory and HttpClient so both are disposed together.
/// Previously, WithWebHostBuilder() created a new factory per test that was never disposed,
/// leaking TestServer instances and consuming gigabytes of memory.
/// </summary>
private sealed class MockScope : IDisposable
{
return _factory.WithWebHostBuilder(builder =>
private readonly WebApplicationFactory<EvidenceLockerProgram> _derivedFactory;
public HttpClient Client { get; }
public MockScope(WebApplicationFactory<EvidenceLockerProgram> derivedFactory)
{
_derivedFactory = derivedFactory;
Client = derivedFactory.CreateClient();
}
public void Dispose()
{
Client.Dispose();
_derivedFactory.Dispose();
}
}
private MockScope CreateClientWithMock(IExportJobService mockService)
{
var derivedFactory = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
@@ -276,6 +311,7 @@ public sealed class ExportEndpointsTests : IClassFixture<WebApplicationFactory<P
// Add mock
services.AddSingleton(mockService);
});
}).CreateClient();
});
return new MockScope(derivedFactory);
}
}