c1e34c307e
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
102 lines
4.6 KiB
C#
102 lines
4.6 KiB
C#
using Microsoft.Data.SqlClient;
|
|
|
|
namespace ZB.MOM.WW.Auth.ApiKeys.SqlServer.Tests.Live;
|
|
|
|
/// <summary>
|
|
/// Shared connection details for the env-gated live SQL Server suite.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gated on <c>AUTH_SQLSERVER_CONNSTR</c> and skips cleanly when it is unset, matching the family
|
|
/// live-test idiom (see <c>ZB.MOM.WW.Secrets.Replicator.SqlServer</c>) — 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 <c>Server=localhost,1433;Database=ZbAuthTest;User Id=sa;Password=...;TrustServerCertificate=true</c>.
|
|
/// Each test class works in a throwaway <em>schema</em> 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.
|
|
/// </remarks>
|
|
public static class LiveSqlServer
|
|
{
|
|
/// <summary>Environment variable holding the live connection string.</summary>
|
|
public const string ConnectionStringVariable = "AUTH_SQLSERVER_CONNSTR";
|
|
|
|
/// <summary>The configured connection string, or <see langword="null"/> when the suite is not enabled.</summary>
|
|
public static string? ConnectionString =>
|
|
Environment.GetEnvironmentVariable(ConnectionStringVariable);
|
|
|
|
/// <summary>Whether the live suite should run.</summary>
|
|
public static bool IsEnabled => !string.IsNullOrWhiteSpace(ConnectionString);
|
|
|
|
/// <summary>Skip reason shown when the suite is not enabled.</summary>
|
|
public const string SkipReason =
|
|
"Live SQL Server suite disabled; set AUTH_SQLSERVER_CONNSTR to enable.";
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="schemaName">The unique schema name for this test.</param>
|
|
/// <returns>Options targeting that schema.</returns>
|
|
public static SqlServerApiKeyOptions OptionsFor(string schemaName) => new()
|
|
{
|
|
ConnectionString = ConnectionString!,
|
|
Schema = schemaName,
|
|
};
|
|
|
|
/// <summary>Generates a unique, allow-list-legal schema name for one test.</summary>
|
|
/// <returns>A fresh schema name.</returns>
|
|
public static string NewSchemaName() => $"zbauth_test_{Guid.NewGuid():N}"[..32];
|
|
|
|
/// <summary>Drops the throwaway schema and its three tables.</summary>
|
|
/// <param name="schemaName">The schema to drop.</param>
|
|
/// <returns>A task that completes when the schema is gone.</returns>
|
|
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();
|
|
}
|
|
|
|
/// <summary>Runs a scalar query against the live database.</summary>
|
|
/// <param name="commandText">The query text.</param>
|
|
/// <returns>The first column of the first row, or <see langword="null"/>.</returns>
|
|
public static async Task<object?> 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;
|
|
}
|
|
|
|
/// <summary>Runs a non-query statement against the live database.</summary>
|
|
/// <param name="commandText">The statement text.</param>
|
|
/// <returns>A task that completes when the statement has run.</returns>
|
|
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();
|
|
}
|
|
}
|