using System.Collections; using System.Collections.Concurrent; using System.Data; using Docker.DotNet; using Docker.DotNet.Models; using LCU.API.Internal.DocumentIngestorLib.Persistence; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; namespace LCU.API.Internal.DocumentIngestorLibTests; public class SqlServerDbContextFactory : IAsyncDisposable { private const int port = 1434; private const string password = "yourStrong(!)Password"; private readonly IList _contexts = new List(); private static readonly SemaphoreSlim ContainerUse = new(1); private static CreateContainerResponse? Container; private static readonly IDictionary _activeContexts = new ConcurrentDictionary(); public async Task CreateContext() { await ContainerUse.WaitAsync(); Container ??= await DatabaseContainerFactory(); ContainerUse.Release(); var connectionString = $"Data Source=localhost,{port};User Id=sa;Password={password};Database={Guid.NewGuid()};MultipleActiveResultSets=True;App=EntityFramework;Trust Server Certificate=true"; var options = new DbContextOptionsBuilder() .UseSqlServer(connectionString) .EnableSensitiveDataLogging() .Options; var context = new AppDbContext(options); await DatabaseConnectionReady(connectionString); await context.Database.EnsureCreatedAsync(); _contexts.Add(context); _activeContexts[context] = true; return context; } private static async Task DatabaseContainerFactory() { const string ImageName = "mcr.microsoft.com/mssql/server:2019-CU18-ubuntu-20.04"; const int containerPort = 1433; using var docker = new DockerClientConfiguration().CreateClient(); await docker.Images.CreateImageAsync ( new ImagesCreateParameters { FromImage = ImageName }, null, new Progress() ); var container = await docker.Containers.CreateContainerAsync ( new CreateContainerParameters { Image = ImageName, Name = "LCU.API.Internal.DocumentIngestor.IntegrationTestDatabase", Env = new List { "ACCEPT_EULA=Y", $"MSSQL_SA_PASSWORD={password}" }, ExposedPorts = new Dictionary { {$"{containerPort}", default} }, HostConfig = new HostConfig { PortBindings = new Dictionary> { { $"{containerPort}", new List { new() { HostPort = $"{port}" } } } } } } ); await docker.Containers.StartContainerAsync(container.ID, new ContainerStartParameters()); return container; } private static async Task DatabaseConnectionReady(string connectionString) { await using var connection = new SqlConnection(connectionString); var ready = false; var triesRemaining = 20; while (!ready && triesRemaining > 0) { triesRemaining--; try { await connection.OpenAsync(); } catch { await Task.Delay(TimeSpan.FromMilliseconds(200)); } ready = connection.State == ConnectionState.Open; } } public async ValueTask DisposeAsync() { foreach (var context in _contexts) { await context.DisposeAsync(); _activeContexts[context] = false; } await ContainerUse.WaitAsync(); if (_activeContexts.Values.All(a => a == false)) { await DisposeContainer(Container); Container = null; } ContainerUse.Release(); } private static async Task DisposeContainer(CreateContainerResponse container) { using var docker = new DockerClientConfiguration().CreateClient(); await docker.Containers.StopContainerAsync(container.ID, new ContainerStopParameters()); await docker.Containers.RemoveContainerAsync(container.ID, new ContainerRemoveParameters()); } }