using Microsoft.Data.SqlClient; namespace ZB.MOM.WW.Auth.ApiKeys.SqlServer.Tests.Live; /// /// Shared connection details for the env-gated live SQL Server suite. /// /// /// Gated on AUTH_SQLSERVER_CONNSTR and skips cleanly when it is unset, matching the family /// live-test idiom (see ZB.MOM.WW.Secrets.Replicator.SqlServer) — the offline suite must stay /// runnable on any machine. Set it to a database the test is allowed to create and drop a schema in, /// for example Server=localhost,1433;Database=ZbAuthTest;User Id=sa;Password=...;TrustServerCertificate=true. /// Each test class works in a throwaway schema rather than a throwaway database: it needs no /// CREATE DATABASE right, and a virgin schema still reproduces the CREATE SCHEMA race the migrator's /// retry loop exists for. /// public static class LiveSqlServer { /// Environment variable holding the live connection string. public const string ConnectionStringVariable = "AUTH_SQLSERVER_CONNSTR"; /// The configured connection string, or when the suite is not enabled. public static string? ConnectionString => Environment.GetEnvironmentVariable(ConnectionStringVariable); /// Whether the live suite should run. public static bool IsEnabled => !string.IsNullOrWhiteSpace(ConnectionString); /// Skip reason shown when the suite is not enabled. public const string SkipReason = "Live SQL Server suite disabled; set AUTH_SQLSERVER_CONNSTR to enable."; /// /// Builds options against a throwaway schema so parallel runs and repeat runs cannot collide, /// and so a failed run leaves no trace in a shared database. /// /// The unique schema name for this test. /// Options targeting that schema. public static SqlServerApiKeyOptions OptionsFor(string schemaName) => new() { ConnectionString = ConnectionString!, Schema = schemaName, }; /// Generates a unique, allow-list-legal schema name for one test. /// A fresh schema name. public static string NewSchemaName() => $"zbauth_test_{Guid.NewGuid():N}"[..32]; /// Drops the throwaway schema and its three tables. /// The schema to drop. /// A task that completes when the schema is gone. public static async Task DropSchemaAsync(string schemaName) { if (!IsEnabled) { return; } await using var connection = new SqlConnection(ConnectionString); await connection.OpenAsync(); await using SqlCommand command = connection.CreateCommand(); command.CommandText = $""" IF OBJECT_ID(N'[{schemaName}].[api_key_audit]', N'U') IS NOT NULL DROP TABLE [{schemaName}].[api_key_audit]; IF OBJECT_ID(N'[{schemaName}].[api_keys]', N'U') IS NOT NULL DROP TABLE [{schemaName}].[api_keys]; IF OBJECT_ID(N'[{schemaName}].[schema_version]', N'U') IS NOT NULL DROP TABLE [{schemaName}].[schema_version]; IF EXISTS (SELECT 1 FROM sys.schemas WHERE name = N'{schemaName}') EXEC(N'DROP SCHEMA [{schemaName}]'); """; await command.ExecuteNonQueryAsync(); } /// Runs a scalar query against the live database. /// The query text. /// The first column of the first row, or . public static async Task ScalarAsync(string commandText) { await using var connection = new SqlConnection(ConnectionString); await connection.OpenAsync(); await using SqlCommand command = connection.CreateCommand(); command.CommandText = commandText; object? value = await command.ExecuteScalarAsync(); return value == DBNull.Value ? null : value; } /// Runs a non-query statement against the live database. /// The statement text. /// A task that completes when the statement has run. public static async Task ExecuteAsync(string commandText) { await using var connection = new SqlConnection(ConnectionString); await connection.OpenAsync(); await using SqlCommand command = connection.CreateCommand(); command.CommandText = commandText; await command.ExecuteNonQueryAsync(); } }