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