consolidate the tests locations
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Testcontainers.PostgreSql;
|
||||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace StellaOps.Infrastructure.Postgres.Testing;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for PostgreSQL integration test fixtures.
|
||||
/// Uses Testcontainers to spin up a real PostgreSQL instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Inherit from this class and override <see cref="GetMigrationAssembly"/> and <see cref="GetModuleName"/>
|
||||
/// to provide module-specific migrations.
|
||||
/// </remarks>
|
||||
public abstract class PostgresIntegrationFixture : IAsyncLifetime
|
||||
{
|
||||
private PostgreSqlContainer? _container;
|
||||
private PostgresFixture? _fixture;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the PostgreSQL connection string for tests.
|
||||
/// </summary>
|
||||
public string ConnectionString => _container?.GetConnectionString()
|
||||
?? throw new InvalidOperationException("Container not initialized");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the schema name for test isolation.
|
||||
/// </summary>
|
||||
public string SchemaName => _fixture?.SchemaName
|
||||
?? throw new InvalidOperationException("Fixture not initialized");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the PostgreSQL test fixture.
|
||||
/// </summary>
|
||||
public PostgresFixture Fixture => _fixture
|
||||
?? throw new InvalidOperationException("Fixture not initialized");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger for this fixture.
|
||||
/// </summary>
|
||||
protected virtual ILogger Logger => NullLogger.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the PostgreSQL Docker image to use.
|
||||
/// </summary>
|
||||
protected virtual string PostgresImage => "postgres:16-alpine";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly containing embedded SQL migrations.
|
||||
/// </summary>
|
||||
/// <returns>Assembly with embedded migration resources, or null if no migrations.</returns>
|
||||
protected abstract Assembly? GetMigrationAssembly();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the module name for logging and schema naming.
|
||||
/// </summary>
|
||||
protected abstract string GetModuleName();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the resource prefix for filtering embedded resources.
|
||||
/// </summary>
|
||||
protected virtual string? GetResourcePrefix() => null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the PostgreSQL container and runs migrations.
|
||||
/// </summary>
|
||||
public virtual async Task InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_container = new PostgreSqlBuilder()
|
||||
.WithImage(PostgresImage)
|
||||
.Build();
|
||||
|
||||
await _container.StartAsync();
|
||||
}
|
||||
catch (ArgumentException ex) when (ShouldSkipForMissingDocker(ex))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_container is not null)
|
||||
{
|
||||
await _container.DisposeAsync();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore cleanup failures during skip.
|
||||
}
|
||||
|
||||
_container = null;
|
||||
|
||||
throw SkipException.ForSkip(
|
||||
$"Postgres integration tests require Docker/Testcontainers. Skipping because the container failed to start: {ex.Message}");
|
||||
}
|
||||
|
||||
var moduleName = GetModuleName();
|
||||
_fixture = PostgresFixtureFactory.Create(ConnectionString, moduleName, Logger);
|
||||
await _fixture.InitializeAsync();
|
||||
|
||||
var migrationAssembly = GetMigrationAssembly();
|
||||
if (migrationAssembly != null)
|
||||
{
|
||||
await _fixture.RunMigrationsFromAssemblyAsync(
|
||||
migrationAssembly,
|
||||
moduleName,
|
||||
GetResourcePrefix());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up the PostgreSQL container and fixture.
|
||||
/// </summary>
|
||||
public virtual async Task DisposeAsync()
|
||||
{
|
||||
if (_fixture != null)
|
||||
{
|
||||
await _fixture.DisposeAsync();
|
||||
}
|
||||
|
||||
if (_container != null)
|
||||
{
|
||||
await _container.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Truncates all tables in the test schema for test isolation between test methods.
|
||||
/// </summary>
|
||||
public Task TruncateAllTablesAsync(CancellationToken cancellationToken = default)
|
||||
=> Fixture.TruncateAllTablesAsync(cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Executes raw SQL for test setup.
|
||||
/// </summary>
|
||||
public Task ExecuteSqlAsync(string sql, CancellationToken cancellationToken = default)
|
||||
=> Fixture.ExecuteSqlAsync(sql, cancellationToken);
|
||||
|
||||
private static bool ShouldSkipForMissingDocker(ArgumentException exception)
|
||||
{
|
||||
return string.Equals(exception.ParamName, "DockerEndpointAuthConfig", StringComparison.Ordinal)
|
||||
|| exception.Message.Contains("Docker is either not running", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL integration fixture without migrations.
|
||||
/// Useful for testing the infrastructure itself or creating schemas dynamically.
|
||||
/// </summary>
|
||||
public sealed class PostgresIntegrationFixtureWithoutMigrations : PostgresIntegrationFixture
|
||||
{
|
||||
protected override Assembly? GetMigrationAssembly() => null;
|
||||
protected override string GetModuleName() => "Test";
|
||||
}
|
||||
Reference in New Issue
Block a user