using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Containers; using JSMR.Infrastructure.Data; using JSMR.Tests.Fixtures; using Microsoft.EntityFrameworkCore; using MySqlConnector; using Testcontainers.MariaDb; using Testcontainers.Xunit; using Xunit.Sdk; [assembly: AssemblyFixture(typeof(MariaDbContainerFixture))] namespace JSMR.Tests.Fixtures; public sealed class MariaDbContainerFixture : IAsyncLifetime { const int MajorVersion = 10; const int MinorVersion = 11; const int Build = 6; private MariaDbContainer _container = default!; public string RootConnectionString { get; private set; } = default!; public async ValueTask InitializeAsync() { //_container = new ContainerBuilder() // .WithImage("mariadb:11") // .WithEnvironment("MARIADB_ROOT_PASSWORD", "rootpw") // .WithPortBinding(3307, 3306) // .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(3306)) // .Build(); //_container = new ContainerBuilder() // .WithImage($"mariadb:{MajorVersion}.{MinorVersion}.{Build}") // .WithEnvironment("MARIADB_ROOT_PASSWORD", "rootpw") // //.WithPortBinding(3307, 3306) // .WithPortBinding(3306, assignRandomHostPort: true) // .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(3306)) // .Build(); _container = new MariaDbBuilder() .WithImage($"mariadb:{MajorVersion}.{MinorVersion}.{Build}") .WithEnvironment("MARIADB_ROOT_PASSWORD", "rootpw") .WithUsername("root") .WithPassword("rootpw") // no explicit port binding .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(3306)) .Build(); await _container.StartAsync(); // No database specified: we’ll create per-test DBs //RootConnectionString = "Server=127.0.0.1;Port=3307;User=root;Password=rootpw;SslMode=none;"; RootConnectionString = _container.GetConnectionString(); var port = _container.GetMappedPublicPort(3306); //RootConnectionString = $"Server=127.0.0.1;Port={port};User=root;Password=rootpw;SslMode=none;"; } public async ValueTask DisposeAsync() => await _container.DisposeAsync(); } public class MariaDbFixture : IAsyncLifetime { const int MajorVersion = 10; const int MinorVersion = 11; const int Build = 6; public MariaDbContainer? MariaDbContainer { get; private set; } public string ConnectionString { get; private set; } = default!; public async ValueTask InitializeAsync() { MariaDbContainer = new MariaDbBuilder() .WithImage($"mariadb:{MajorVersion}.{MinorVersion}.{Build}") .Build(); await MariaDbContainer.StartAsync(); ConnectionString = MariaDbContainer.GetConnectionString(); await using AppDbContext context = CreateDbContext(); await context.Database.EnsureCreatedAsync(); //await context.Database.MigrateAsync(); // Testing await OnInitializedAsync(context); } protected virtual Task OnInitializedAsync(AppDbContext context) { return Task.FromResult(Task.CompletedTask); } public async ValueTask DisposeAsync() { if (MariaDbContainer is not null) { await MariaDbContainer.StopAsync(); await MariaDbContainer.DisposeAsync(); } GC.SuppressFinalize(this); } public AppDbContext CreateDbContext() { MySqlServerVersion serverVersion = new(new Version(MajorVersion, MinorVersion, Build)); DbContextOptions options = new DbContextOptionsBuilder() .UseMySql(ConnectionString, serverVersion, o => o.EnableRetryOnFailure()) .EnableSensitiveDataLogging() .Options; return new AppDbContext(options); } public async Task ResetAsync() { await using AppDbContext context = CreateDbContext(); await context.Database.EnsureDeletedAsync(); await context.Database.EnsureCreatedAsync(); } } [CollectionDefinition("db")] public sealed class MariaDbCollection : ICollectionFixture { } //public class MariaDbAssemblyFixtureDefinition : IAssemblyFixture { } //[UsedImplicitly] public sealed class MariaDbContainerFixture2(IMessageSink messageSink) : ContainerFixture(messageSink) { const int MajorVersion = 10; const int MinorVersion = 11; const int Build = 6; public string RootConnectionString => $"Server={Container.IpAddress};Port=3306;User=root;Password=rootpw;SslMode=none;"; protected override MariaDbBuilder Configure(MariaDbBuilder builder) { return builder.WithImage($"mariadb:{MajorVersion}.{MinorVersion}.{Build}") .WithEnvironment("MARIADB_ROOT_PASSWORD", "rootpw") .WithPortBinding(3307, 3306) //.WithPortBinding(3306, assignRandomHostPort: true) .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(3306)); } } public static class MariaTestDb { public static async Task CreateIsolatedAsync(string rootConnectionString, Func? seed = null) { string databaseName = $"t_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}"; await CreateDatabaseAsync(rootConnectionString, databaseName); MySqlConnectionStringBuilder connectionStringBuilder = new(rootConnectionString) { Database = databaseName }; AppDbContext dbContext = CreateDbContext(connectionStringBuilder.ConnectionString); await dbContext.Database.EnsureCreatedAsync(); if (seed != null) await seed(dbContext); return dbContext; } private static async Task CreateDatabaseAsync(string rootConnectionString, string databaseName) { await using MySqlConnection connection = new(rootConnectionString); await connection.OpenAsync(); await using MySqlCommand command = connection.CreateCommand(); command.CommandText = $"CREATE DATABASE `{databaseName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"; await command.ExecuteNonQueryAsync(); } private static AppDbContext CreateDbContext(string connectionString) { DbContextOptions options = new DbContextOptionsBuilder() .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), o => o.EnableRetryOnFailure()) .EnableSensitiveDataLogging() .Options; return new AppDbContext(options); } }