using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Hosting;
namespace Prefab.Tests.Infrastructure;
///
/// Hosts a project entry point in-process with Kestrel so integration tests hit real HTTP.
///
public sealed class KestrelInProcApp : WebApplicationFactory, IAsyncLifetime
where TEntryPoint : class
{
private readonly AspireInfraHost _infra;
private readonly ProjectResource _project;
private readonly int? _port;
private readonly IDictionary _overrides;
private readonly Action? _configureServices;
private Dictionary _config = new(StringComparer.OrdinalIgnoreCase);
private Uri? _httpAddress;
public KestrelInProcApp(
AspireInfraHost infra,
ProjectResource project,
int? port,
IDictionary? overrides = null,
Action? configureServices = null)
{
_infra = infra;
_project = project;
_port = port;
_overrides = overrides ?? new Dictionary(StringComparer.OrdinalIgnoreCase);
_configureServices = configureServices;
this.UseKestrel(_port ?? 0);
}
public Uri HttpAddress => _httpAddress ?? throw new InvalidOperationException("Application not started.");
public IServiceProvider AppServices => Services;
public async ValueTask InitializeAsync()
{
_config = await AspireInfraHost.ResolveConfigForProjectAsync(_project, _infra.Services);
foreach (var (key, value) in _overrides)
{
_config[key.Replace("__", ":")] = value;
}
using var client = CreateClient();
_httpAddress = client.BaseAddress ?? ResolveServerAddress();
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
foreach (var (key, value) in _config)
{
if (string.Equals(key, "ASPNETCORE_URLS", StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (value is null)
{
continue;
}
builder.UseSetting(key, value);
}
builder.UseSetting("ASPNETCORE_ENVIRONMENT", "Testing");
builder.ConfigureServices(services =>
{
RemoveHostedService(services, "Prefab.Web.Workers.DataSeeder");
RemoveHostedService(services, "Prefab.Catalog.Api.Workers.DataSeeder");
_configureServices?.Invoke(services);
});
}
public override ValueTask DisposeAsync() => base.DisposeAsync();
private Uri ResolveServerAddress()
{
var server = Services.GetRequiredService();
var feature = server.Features.Get();
var address = feature?.Addresses.FirstOrDefault();
if (address is not null && Uri.TryCreate(address, UriKind.Absolute, out var uri))
{
return uri;
}
// Fallback to localhost with known (or dynamic) port.
var port = _port.GetValueOrDefault(0);
return new Uri($"http://127.0.0.1:{port}");
}
private static void RemoveHostedService(IServiceCollection services, string typeFullName)
{
var descriptors = services
.Where(s => s.ServiceType == typeof(IHostedService) && s.ImplementationType?.FullName == typeFullName)
.ToList();
foreach (var descriptor in descriptors)
{
services.Remove(descriptor);
}
}
}