Files
2026-03-25 14:50:13 -05:00

138 lines
3.7 KiB
C#

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<AppDbContext> _contexts = new List<AppDbContext>();
private static readonly SemaphoreSlim ContainerUse = new(1);
private static CreateContainerResponse? Container;
private static readonly IDictionary<AppDbContext, bool> _activeContexts = new ConcurrentDictionary<AppDbContext, bool>();
public async Task<AppDbContext> 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<AppDbContext>()
.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<CreateContainerResponse> 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<JSONMessage>()
);
var container = await docker.Containers.CreateContainerAsync
(
new CreateContainerParameters
{
Image = ImageName,
Name = "LCU.API.Internal.DocumentIngestor.IntegrationTestDatabase",
Env = new List<string>
{
"ACCEPT_EULA=Y",
$"MSSQL_SA_PASSWORD={password}"
},
ExposedPorts = new Dictionary<string, EmptyStruct>
{
{$"{containerPort}", default}
},
HostConfig = new HostConfig
{
PortBindings = new Dictionary<string, IList<PortBinding>>
{
{ $"{containerPort}", new List<PortBinding> { 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());
}
}