- 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.
153 lines
4.8 KiB
C#
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<HttpFixtureServer<Program>>
|
|
/// {
|
|
/// private readonly HttpClient _client;
|
|
///
|
|
/// public ApiTests(HttpFixtureServer<Program> 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}")
|
|
};
|
|
}
|
|
}
|