Files
git.stella-ops.org/src/__Libraries/StellaOps.TestKit/Fixtures/HttpFixtureServer.cs
master 491e883653 Add tests for SBOM generation determinism across multiple formats
- Created `StellaOps.TestKit.Tests` project for unit tests related to determinism.
- Implemented `DeterminismManifestTests` to validate deterministic output for canonical bytes and strings, file read/write operations, and error handling for invalid schema versions.
- Added `SbomDeterminismTests` to ensure identical inputs produce consistent SBOMs across SPDX 3.0.1 and CycloneDX 1.6/1.7 formats, including parallel execution tests.
- Updated project references in `StellaOps.Integration.Determinism` to include the new determinism testing library.
2025-12-24 00:36:14 +02:00

153 lines
4.8 KiB
C#

using System.Net;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
namespace StellaOps.TestKit.Fixtures;
/// <summary>
/// Provides an in-memory HTTP test server using WebApplicationFactory for contract testing.
/// </summary>
/// <typeparam name="TProgram">The entry point type of the web application (usually Program).</typeparam>
/// <remarks>
/// Usage:
/// <code>
/// public class ApiTests : IClassFixture&lt;HttpFixtureServer&lt;Program&gt;&gt;
/// {
/// private readonly HttpClient _client;
///
/// public ApiTests(HttpFixtureServer&lt;Program&gt; fixture)
/// {
/// _client = fixture.CreateClient();
/// }
///
/// [Fact]
/// public async Task GetHealth_ReturnsOk()
/// {
/// var response = await _client.GetAsync("/health");
/// response.EnsureSuccessStatusCode();
/// }
/// }
/// </code>
/// </remarks>
public sealed class HttpFixtureServer<TProgram> : WebApplicationFactory<TProgram>
where TProgram : class
{
private readonly Action<IServiceCollection>? _configureServices;
/// <summary>
/// Creates a new HTTP fixture server with optional service configuration.
/// </summary>
/// <param name="configureServices">Optional action to configure test services (e.g., replace dependencies with mocks).</param>
public HttpFixtureServer(Action<IServiceCollection>? configureServices = null)
{
_configureServices = configureServices;
}
/// <summary>
/// Configures the web host for testing (disables HTTPS redirection, applies custom services).
/// </summary>
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Apply user-provided service configuration (e.g., mock dependencies)
_configureServices?.Invoke(services);
});
builder.UseEnvironment("Test");
}
/// <summary>
/// Creates an HttpClient configured to communicate with the test server.
/// </summary>
public new HttpClient CreateClient()
{
return base.CreateClient();
}
/// <summary>
/// Creates an HttpClient with custom configuration.
/// </summary>
public HttpClient CreateClient(Action<HttpClient> configure)
{
var client = CreateClient();
configure(client);
return client;
}
}
/// <summary>
/// Provides a stub HTTP message handler for hermetic HTTP tests without external dependencies.
/// </summary>
/// <remarks>
/// Usage:
/// <code>
/// var handler = new HttpMessageHandlerStub()
/// .WhenRequest("https://api.example.com/data")
/// .Responds(HttpStatusCode.OK, "{\"status\":\"ok\"}");
///
/// var httpClient = new HttpClient(handler);
/// var response = await httpClient.GetAsync("https://api.example.com/data");
/// // response.StatusCode == HttpStatusCode.OK
/// </code>
/// </remarks>
public sealed class HttpMessageHandlerStub : HttpMessageHandler
{
private readonly Dictionary<string, Func<HttpRequestMessage, Task<HttpResponseMessage>>> _handlers = new();
private Func<HttpRequestMessage, Task<HttpResponseMessage>>? _defaultHandler;
/// <summary>
/// Configures a response for a specific URL.
/// </summary>
public HttpMessageHandlerStub WhenRequest(string url, Func<HttpRequestMessage, Task<HttpResponseMessage>> handler)
{
_handlers[url] = handler;
return this;
}
/// <summary>
/// Configures a simple response for a specific URL.
/// </summary>
public HttpMessageHandlerStub WhenRequest(string url, HttpStatusCode statusCode, string? content = null)
{
return WhenRequest(url, _ => Task.FromResult(new HttpResponseMessage(statusCode)
{
Content = content != null ? new StringContent(content) : null
}));
}
/// <summary>
/// Configures a default handler for unmatched requests.
/// </summary>
public HttpMessageHandlerStub WhenAnyRequest(Func<HttpRequestMessage, Task<HttpResponseMessage>> handler)
{
_defaultHandler = handler;
return this;
}
/// <summary>
/// Sends the HTTP request through the stub handler.
/// </summary>
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var url = request.RequestUri?.ToString() ?? string.Empty;
if (_handlers.TryGetValue(url, out var handler))
{
return await handler(request);
}
if (_defaultHandler != null)
{
return await _defaultHandler(request);
}
// Default: 404 Not Found for unmatched requests
return new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent($"No stub configured for {url}")
};
}
}