test(sql): reap >24h-old orphaned test databases at fixture startup (AdminUI + Configuration prefixes)
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Shouldly;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Pins <see cref="SqlTestDatabaseReaper"/> against the real shared SQL Server: an aged orphan is
|
||||
/// dropped, a fresh sibling is not.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A <b>pin, not a red-first test</b>. The reaper is time-gated and the behaviour it protects — "a
|
||||
/// database created by a run that is long over gets dropped, one created by a run that may still be
|
||||
/// going does not" — has no observable failing state to start from; what it needs is a guard against
|
||||
/// someone later widening the age window, the <c>LIKE</c>, or the timestamp parse. It therefore drives
|
||||
/// the internal core with an explicit cutoff rather than waiting 24 hours.
|
||||
/// </remarks>
|
||||
public sealed class SqlTestDatabaseReaperTests
|
||||
{
|
||||
[SqlServerFact]
|
||||
public void Reap_drops_databases_older_than_the_cutoff_and_leaves_fresh_ones_alone()
|
||||
{
|
||||
var master = AdminUiSqlServer.ConnectionStringFor("master");
|
||||
|
||||
// A name that carries a timestamp from 2025 — unambiguously older than any cutoff this suite uses.
|
||||
var aged = $"{AdminUiSqlServer.TestDatabasePrefix}20250101000000_{Guid.NewGuid():N}";
|
||||
var fresh = $"{AdminUiSqlServer.TestDatabasePrefix}{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}";
|
||||
|
||||
CreateDatabase(master, aged);
|
||||
|
||||
try
|
||||
{
|
||||
CreateDatabase(master, fresh);
|
||||
|
||||
try
|
||||
{
|
||||
var reaped = SqlTestDatabaseReaper.ReapOrphans(
|
||||
master,
|
||||
AdminUiSqlServer.TestDatabasePrefix,
|
||||
DateTime.UtcNow - TimeSpan.FromHours(24));
|
||||
|
||||
reaped.ShouldContain(aged);
|
||||
DatabaseExists(master, aged).ShouldBeFalse("the aged orphan should have been reaped");
|
||||
|
||||
reaped.ShouldNotContain(fresh);
|
||||
DatabaseExists(master, fresh).ShouldBeTrue("a database from a possibly-live run must survive");
|
||||
}
|
||||
finally
|
||||
{
|
||||
SqlTestDatabaseReaper.TryDropDatabase(master, fresh);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// No-op when the reaper already took it; belt-and-braces if an assertion failed first.
|
||||
SqlTestDatabaseReaper.TryDropDatabase(master, aged);
|
||||
}
|
||||
}
|
||||
|
||||
[SqlServerFact]
|
||||
public void Reap_leaves_a_matching_name_whose_timestamp_does_not_parse_alone()
|
||||
{
|
||||
var master = AdminUiSqlServer.ConnectionStringFor("master");
|
||||
var unparseable = $"{AdminUiSqlServer.TestDatabasePrefix}notatimestamp_{Guid.NewGuid():N}";
|
||||
|
||||
CreateDatabase(master, unparseable);
|
||||
|
||||
try
|
||||
{
|
||||
// The cutoff stays at the production 24 hours — deliberately NOT a far-future one, which would
|
||||
// sweep away the live database of an AdminUI fixture running in parallel on this shared server.
|
||||
// It still bites: an unparseable name that fell through to default(DateTime) would read as year
|
||||
// 0001 and be dropped.
|
||||
var reaped = SqlTestDatabaseReaper.ReapOrphans(
|
||||
master,
|
||||
AdminUiSqlServer.TestDatabasePrefix,
|
||||
DateTime.UtcNow - TimeSpan.FromHours(24));
|
||||
|
||||
reaped.ShouldNotContain(unparseable);
|
||||
DatabaseExists(master, unparseable)
|
||||
.ShouldBeTrue("a name this code could not have minted is never dropped");
|
||||
}
|
||||
finally
|
||||
{
|
||||
SqlTestDatabaseReaper.TryDropDatabase(master, unparseable);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateDatabase(string masterConnectionString, string databaseName)
|
||||
{
|
||||
using var conn = new SqlConnection(masterConnectionString);
|
||||
conn.Open();
|
||||
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = $"CREATE DATABASE [{databaseName}];";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
private static bool DatabaseExists(string masterConnectionString, string databaseName)
|
||||
{
|
||||
using var conn = new SqlConnection(masterConnectionString);
|
||||
conn.Open();
|
||||
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = "SELECT DB_ID(@name);";
|
||||
cmd.Parameters.Add(new SqlParameter("@name", databaseName));
|
||||
|
||||
return cmd.ExecuteScalar() is not (null or DBNull);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user