using Microsoft.EntityFrameworkCore;
using Prefab.Handler;
using Prefab.Web.Data;
namespace Prefab.Tests.Infrastructure.Aspire;
///
/// Provides a reusable Aspire application host for integration tests.
///
public class AppHostFixture : IAsyncLifetime
{
private static readonly TimeSpan DefaultStartupTimeout = TimeSpan.FromMinutes(2);
private AppHost? _host;
///
/// Gets a value indicating whether the Aspire host has been started.
///
public bool IsRunning => _host is not null;
///
/// Returns the active .
///
/// Thrown if the host has not been started.
private AppHost Host => _host ?? throw new InvalidOperationException("The Aspire host has not been started.");
///
public async ValueTask InitializeAsync()
{
await StartHostAsync(null, CancellationToken.None);
}
///
public async ValueTask DisposeAsync()
{
if (_host is null)
{
return;
}
await _host.DisposeAsync();
_host = null;
}
///
/// Restarts the Aspire host with the supplied environment overrides.
///
/// Environment variables to apply when starting the host.
/// Cancellation token for the restart operation.
public async Task ReinitializeAsync(
IReadOnlyDictionary? environmentOverrides,
CancellationToken cancellationToken)
{
await DisposeAsync();
await StartHostAsync(environmentOverrides, cancellationToken);
}
///
/// Creates an for the specified resource.
///
public HttpClient CreateHttpClient(string resourceName) => Host.CreateHttpClient(resourceName);
///
/// Retrieves a connection string for the specified resource.
///
public Task GetConnectionStringAsync(string resourceName, CancellationToken cancellationToken) =>
Host.GetSqlConnectionStringAsync(resourceName, cancellationToken);
///
/// Provides an configured for write operations so tests can seed data.
///
/// Delegate that performs the seed work.
/// Token used to cancel the operation.
public async Task SeedTestWith(Func seedWork, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(seedWork);
var connectionString = await GetConnectionStringAsync("prefab-db", cancellationToken);
var options = new DbContextOptionsBuilder()
.UseSqlServer(connectionString)
.Options;
await using var db = new AppDb(options, new HandlerContextAccessor());
await db.Database.MigrateAsync(cancellationToken);
await seedWork(db, cancellationToken);
}
///
/// Starts the Aspire host with the specified environment overrides.
///
private async Task StartHostAsync(IReadOnlyDictionary? environmentOverrides, CancellationToken cancellationToken)
{
if (_host is not null)
{
throw new InvalidOperationException("The Aspire host has already been started.");
}
using var cts = cancellationToken.CanBeCanceled
? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)
: new CancellationTokenSource(DefaultStartupTimeout);
cts.CancelAfter(DefaultStartupTimeout);
var overrides = environmentOverrides ?? BuildEnvironmentOverrides();
_host = await AppHost.StartAsync(
overrides is { Count: > 0 } ? overrides : null,
cts.Token);
}
///
/// Provides the default environment overrides used when starting the host.
///
protected virtual IReadOnlyDictionary BuildEnvironmentOverrides() =>
new Dictionary(StringComparer.OrdinalIgnoreCase);
}