using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using ZB.MOM.WW.OtOpcUa.Configuration; namespace ZB.MOM.WW.OtOpcUa.ControlPlane.Tests.Harness; /// /// A migrated, throw-away config database on the central SQL Server, for the few ControlPlane behaviours /// the InMemory provider cannot model (a SQL Server DbUpdateException names the WHOLE statement /// batch; collation-ordered reads). Same server / credentials / env overrides as /// Configuration.Tests.SchemaComplianceFixture (OTOPCUA_CONFIG_TEST_SERVER, /// OTOPCUA_CONFIG_TEST_SA_PASSWORD; default the shared docker host's dev SA), and the same database /// name shape, so that suite's 24 h orphan reaper also reclaims a database a killed run leaks here. /// public sealed class SqlServerTestDatabase : IDisposable { private const string DefaultServer = "10.100.0.35,14330"; private const string DefaultSaPassword = "OtOpcUaDev_2026!"; /// Gets the connection string of the migrated database. public string ConnectionString { get; } private readonly string _databaseName; /// Creates and migrates the database. public SqlServerTestDatabase() { var server = Environment.GetEnvironmentVariable("OTOPCUA_CONFIG_TEST_SERVER") ?? DefaultServer; var saPassword = Environment.GetEnvironmentVariable("OTOPCUA_CONFIG_TEST_SA_PASSWORD") ?? DefaultSaPassword; _databaseName = $"OtOpcUaConfig_Test_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}"; ConnectionString = $"Server={server};Database={_databaseName};User Id=sa;Password={saPassword};TrustServerCertificate=True;Encrypt=False;"; using var ctx = NewContext(); ctx.Database.Migrate(); } /// A new context on the database. /// The context. public OtOpcUaConfigDbContext NewContext() => new(new DbContextOptionsBuilder().UseSqlServer(ConnectionString).Options); /// A factory over . /// The factory. public IDbContextFactory Factory() => new ContextFactory(this); /// Drops the database (best effort; a leak is reaped by the Configuration suite). public void Dispose() { try { SqlConnection.ClearAllPools(); var master = new SqlConnectionStringBuilder(ConnectionString) { InitialCatalog = "master" }.ConnectionString; using var conn = new SqlConnection(master); conn.Open(); using var cmd = conn.CreateCommand(); cmd.CommandText = @" DECLARE @sql nvarchar(max); IF DB_ID(@name) IS NOT NULL BEGIN SET @sql = N'ALTER DATABASE ' + QUOTENAME(@name) + N' SET SINGLE_USER WITH ROLLBACK IMMEDIATE; ' + N'DROP DATABASE ' + QUOTENAME(@name) + N';'; EXEC sp_executesql @sql; END"; cmd.Parameters.Add(new SqlParameter("@name", _databaseName)); cmd.ExecuteNonQuery(); } catch (Exception) { // Best effort — see the class remarks. } } private sealed class ContextFactory(SqlServerTestDatabase owner) : IDbContextFactory { public OtOpcUaConfigDbContext CreateDbContext() => owner.NewContext(); } }