using Microsoft.Data.SqlClient;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer.IntegrationTests;
///
/// Env-gated throwaway database for the SQL historian backend. HISTORIAN_SQL_TEST_CONNECTION absent ⇒
/// is set, nothing connects, and every test calls Assert.Skip. Present ⇒
/// creates OtOpcUaHistorian_T_<guid> (never touches another database) and drops it on dispose.
/// Tests isolate from each other with .
///
public sealed class SqlHistorianFixture : IAsyncLifetime
{
/// Env var holding a connection string with CREATE/DROP DATABASE rights.
public const string ConnectionEnvVar = "HISTORIAN_SQL_TEST_CONNECTION";
private string? _master;
private string? _dbName;
/// Why tests must skip, or null when the database is available.
public string? SkipReason { get; private set; }
/// Connection string targeting the throwaway database. Valid only when is null.
public string ConnectionString { get; private set; } = "";
/// Skips the calling test when the gate is closed.
public void SkipIfUnavailable()
{
if (SkipReason is not null) Assert.Skip(SkipReason);
}
/// A fresh, valid schema name (nothing exists under it yet).
/// A schema identifier unique to this call.
public static string UniqueSchema() => "h" + Guid.NewGuid().ToString("N")[..16];
///
public async ValueTask InitializeAsync()
{
var cs = Environment.GetEnvironmentVariable(ConnectionEnvVar);
if (string.IsNullOrWhiteSpace(cs))
{
SkipReason = $"{ConnectionEnvVar} is not set; SQL Server historian integration tests skipped.";
return;
}
var b = new SqlConnectionStringBuilder(cs) { InitialCatalog = "master" };
_master = b.ConnectionString;
_dbName = "OtOpcUaHistorian_T_" + Guid.NewGuid().ToString("N");
await ExecAsync(_master, $"CREATE DATABASE [{_dbName}]").ConfigureAwait(false);
b.InitialCatalog = _dbName;
ConnectionString = b.ConnectionString;
}
///
public async ValueTask DisposeAsync()
{
if (_master is null || _dbName is null) return;
SqlConnection.ClearAllPools();
await ExecAsync(_master,
$"ALTER DATABASE [{_dbName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [{_dbName}]").ConfigureAwait(false);
}
private static async Task ExecAsync(string cs, string sql)
{
await using var c = new SqlConnection(cs);
await c.OpenAsync().ConfigureAwait(false);
await using var cmd = c.CreateCommand();
cmd.CommandText = sql;
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
}
}
/// Shares one throwaway database across the integration tests.
[CollectionDefinition(Name)]
public sealed class SqlHistorianCollection : ICollectionFixture
{
/// Collection name.
public const string Name = "SqlHistorian";
}