Files
prefab-page-detail/Prefab.Tests/Infrastructure/KestrelInProcApp.cs
2025-10-27 17:39:18 -04:00

111 lines
3.6 KiB
C#

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;
/// <summary>
/// Hosts a project entry point in-process with Kestrel so integration tests hit real HTTP.
/// </summary>
public sealed class KestrelInProcApp<TEntryPoint> : WebApplicationFactory<TEntryPoint>, IAsyncLifetime
where TEntryPoint : class
{
private readonly AspireInfraHost _infra;
private readonly ProjectResource _project;
private readonly int? _port;
private readonly IDictionary<string, string?> _overrides;
private readonly Action<IServiceCollection>? _configureServices;
private Dictionary<string, string?> _config = new(StringComparer.OrdinalIgnoreCase);
private Uri? _httpAddress;
public KestrelInProcApp(
AspireInfraHost infra,
ProjectResource project,
int? port,
IDictionary<string, string?>? overrides = null,
Action<IServiceCollection>? configureServices = null)
{
_infra = infra;
_project = project;
_port = port;
_overrides = overrides ?? new Dictionary<string, string?>(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<IServer>();
var feature = server.Features.Get<IServerAddressesFeature>();
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);
}
}
}