207 lines
7.2 KiB
C#
207 lines
7.2 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.AspNetCore.Components.Authorization;
|
|
using Prefab.Handler;
|
|
using Prefab.Web.Data;
|
|
|
|
namespace Prefab.Tests.Infrastructure;
|
|
|
|
/// <summary>
|
|
/// Spins up Aspire infrastructure plus the Prefab web and catalog apps in-process.
|
|
/// </summary>
|
|
public class PrefabCompositeFixture : IAsyncLifetime
|
|
{
|
|
public const int DebugPrefabPort = 5173;
|
|
public const int DebugCatalogPort = 5174;
|
|
|
|
private readonly PrefabHarnessOptions _options;
|
|
private readonly TestAuthenticationStateProvider _authenticationProvider = new();
|
|
|
|
protected PrefabCompositeFixture(PrefabHarnessOptions? options = null)
|
|
{
|
|
_options = options ?? ResolveDefaultOptions();
|
|
Infra = new AspireInfraHost(_options, "prefab-db");
|
|
}
|
|
|
|
public AspireInfraHost Infra { get; }
|
|
public KestrelInProcApp<Prefab.Web.Module> PrefabWeb { get; private set; } = default!;
|
|
public KestrelInProcApp<Prefab.Catalog.Api.Module>? CatalogApi { get; private set; }
|
|
public TestAuthenticationStateProvider AuthenticationStateProvider => _authenticationProvider;
|
|
|
|
public Uri PrefabBaseAddress => PrefabWeb.HttpAddress;
|
|
public Uri CatalogBaseAddress => CatalogApi?.HttpAddress ?? throw new InvalidOperationException("Catalog API has not been started.");
|
|
|
|
protected virtual bool ShouldStartCatalog => true;
|
|
|
|
public async ValueTask InitializeAsync()
|
|
{
|
|
await Infra.InitializeAsync();
|
|
await EnsureDatabaseInitialized(CancellationToken.None);
|
|
|
|
var prefabProject = Infra.GetProject("prefab-web");
|
|
var useFixedPorts = _options.Mode == RunMode.DebugPersistent && _options.DisablePortRandomization;
|
|
|
|
Uri? catalogAddress = null;
|
|
if (ShouldStartCatalog)
|
|
{
|
|
var catalogProject = Infra.GetProject("prefab-catalog");
|
|
var catalogPort = useFixedPorts ? DebugCatalogPort : (int?)null;
|
|
CatalogApi = new KestrelInProcApp<Prefab.Catalog.Api.Module>(
|
|
Infra,
|
|
catalogProject,
|
|
catalogPort,
|
|
overrides: null,
|
|
configureServices: ConfigureTestAuthentication);
|
|
|
|
await CatalogApi.InitializeAsync();
|
|
catalogAddress = CatalogApi.HttpAddress;
|
|
}
|
|
else
|
|
{
|
|
CatalogApi = null;
|
|
}
|
|
|
|
var overrides = GetWebOverrides(catalogAddress);
|
|
|
|
var prefabPort = useFixedPorts ? DebugPrefabPort : (int?)null;
|
|
PrefabWeb = new KestrelInProcApp<Prefab.Web.Module>(
|
|
Infra,
|
|
prefabProject,
|
|
prefabPort,
|
|
overrides,
|
|
ConfigureTestAuthentication);
|
|
|
|
await PrefabWeb.InitializeAsync();
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
await Infra.DisposeAsync();
|
|
}
|
|
|
|
public HttpClient CreateHttpClientForWeb() => PrefabWeb.CreateClient();
|
|
|
|
public IServiceScope CreateWebScope() => PrefabWeb.AppServices.CreateScope();
|
|
|
|
public async Task<string> GetConnectionStringAsync(string resourceName, CancellationToken cancellationToken = default) =>
|
|
await Infra.GetConnectionStringAsync(resourceName, cancellationToken);
|
|
|
|
public async Task SeedAsync(Func<AppDb, CancellationToken, Task> seed, CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(seed);
|
|
|
|
var connectionString = await GetConnectionStringAsync("prefab-db", cancellationToken);
|
|
var optionsBuilder = new DbContextOptionsBuilder<AppDb>().UseSqlServer(connectionString);
|
|
|
|
await using var db = new AppDb(optionsBuilder.Options, new HandlerContextAccessor());
|
|
await db.Database.MigrateAsync(cancellationToken);
|
|
await seed(db, cancellationToken);
|
|
}
|
|
|
|
protected virtual async ValueTask EnsureDatabaseInitialized(CancellationToken cancellationToken)
|
|
{
|
|
var connectionString = await GetConnectionStringAsync("prefab-db", cancellationToken);
|
|
var optionsBuilder = new DbContextOptionsBuilder<AppDb>().UseSqlServer(connectionString);
|
|
|
|
await using var db = new AppDb(optionsBuilder.Options, new HandlerContextAccessor());
|
|
await db.Database.MigrateAsync(cancellationToken);
|
|
}
|
|
|
|
protected virtual IDictionary<string, string?> GetWebOverrides(Uri? catalogAddress) =>
|
|
new Dictionary<string, string?>
|
|
{
|
|
["Prefab__Catalog__Client__Transport"] = "Http",
|
|
["Prefab__Catalog__Client__BaseAddress"] = catalogAddress?.ToString().TrimEnd('/') ?? string.Empty
|
|
};
|
|
|
|
private void ConfigureTestAuthentication(IServiceCollection services)
|
|
{
|
|
var descriptors = services.Where(d => d.ServiceType == typeof(AuthenticationStateProvider)).ToList();
|
|
foreach (var descriptor in descriptors)
|
|
{
|
|
services.Remove(descriptor);
|
|
}
|
|
|
|
services.AddSingleton(_authenticationProvider);
|
|
services.AddSingleton<AuthenticationStateProvider>(_ => _authenticationProvider);
|
|
}
|
|
|
|
private static PrefabHarnessOptions ResolveDefaultOptions()
|
|
{
|
|
var modeEnv = Environment.GetEnvironmentVariable("PREFAB_TEST_MODE")?.Trim().ToLowerInvariant();
|
|
var mode = modeEnv switch
|
|
{
|
|
"debug" => RunMode.DebugPersistent,
|
|
"ephemeral" => RunMode.EphemeralIsolated,
|
|
_ => RunMode.DebugPersistent
|
|
};
|
|
|
|
return mode switch
|
|
{
|
|
RunMode.EphemeralIsolated => new PrefabHarnessOptions(
|
|
RunMode.EphemeralIsolated,
|
|
EnableAspireDashboard: false,
|
|
DisablePortRandomization: false),
|
|
_ => new PrefabHarnessOptions(
|
|
RunMode.DebugPersistent,
|
|
EnableAspireDashboard: true,
|
|
DisablePortRandomization: true)
|
|
};
|
|
}
|
|
}
|
|
|
|
public sealed class PrefabCompositeFixture_Debug : PrefabCompositeFixture
|
|
{
|
|
public PrefabCompositeFixture_Debug()
|
|
: base(new PrefabHarnessOptions(
|
|
RunMode.DebugPersistent,
|
|
EnableAspireDashboard: true,
|
|
DisablePortRandomization: true))
|
|
{
|
|
}
|
|
}
|
|
|
|
public sealed class PrefabCompositeFixture_Ephemeral : PrefabCompositeFixture
|
|
{
|
|
public PrefabCompositeFixture_Ephemeral()
|
|
: base(new PrefabHarnessOptions(
|
|
RunMode.EphemeralIsolated,
|
|
EnableAspireDashboard: false,
|
|
DisablePortRandomization: false))
|
|
{
|
|
}
|
|
}
|
|
|
|
public sealed class PrefabCompositeFixture_InProcess : PrefabCompositeFixture
|
|
{
|
|
public PrefabCompositeFixture_InProcess()
|
|
: base(new PrefabHarnessOptions(
|
|
RunMode.DebugPersistent,
|
|
EnableAspireDashboard: true,
|
|
DisablePortRandomization: true))
|
|
{
|
|
}
|
|
|
|
protected override bool ShouldStartCatalog => false;
|
|
|
|
protected override IDictionary<string, string?> GetWebOverrides(Uri? catalogAddress) =>
|
|
new Dictionary<string, string?>
|
|
{
|
|
["Prefab__Catalog__Client__Transport"] = "InProcess"
|
|
};
|
|
}
|
|
|
|
[CollectionDefinition("Prefab.Debug")]
|
|
public sealed class PrefabDebugCollection : ICollectionFixture<PrefabCompositeFixture_Debug>
|
|
{
|
|
}
|
|
|
|
[CollectionDefinition("Prefab.Ephemeral")]
|
|
public sealed class PrefabEphemeralCollection : ICollectionFixture<PrefabCompositeFixture_Ephemeral>
|
|
{
|
|
}
|
|
|
|
[CollectionDefinition("Prefab.InProcess")]
|
|
public sealed class PrefabInProcessCollection : ICollectionFixture<PrefabCompositeFixture_InProcess>
|
|
{
|
|
}
|