fix(sql-reaper): validate the GUID suffix and QUOTENAME the drop DDL; short reaper connect timeout; mint names from the prefix constant
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests;
|
||||
|
||||
@@ -14,6 +15,14 @@ namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests;
|
||||
/// 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>
|
||||
/// <remarks>
|
||||
/// Joined to <see cref="AdminUiSqlServerCollection"/> deliberately, though it never touches the
|
||||
/// fixture: xunit builds a collection fixture before running the collection's tests and serialises them
|
||||
/// within it, which is what keeps <see cref="AdminUiSqlServerFixture"/>'s own startup reaper from
|
||||
/// racing this one. Left in its own (parallel) collection, that reaper can drop the aged database this
|
||||
/// test plants, between the plant and the assertion — observed as an intermittent failure.
|
||||
/// </remarks>
|
||||
[Collection(nameof(AdminUiSqlServerCollection))]
|
||||
public sealed class SqlTestDatabaseReaperTests
|
||||
{
|
||||
[SqlServerFact]
|
||||
@@ -85,6 +94,36 @@ public sealed class SqlTestDatabaseReaperTests
|
||||
}
|
||||
}
|
||||
|
||||
[SqlServerFact]
|
||||
public void Reap_leaves_an_aged_name_whose_suffix_is_not_a_guid_alone()
|
||||
{
|
||||
var master = AdminUiSqlServer.ConnectionStringFor("master");
|
||||
|
||||
// Aged enough to be reaped on the timestamp alone — only the suffix check saves it. That check is
|
||||
// what bounds the character set of every candidate: without it a database named
|
||||
// "<prefix><aged timestamp>_" + arbitrary text would be fed to the drop statement, and before the
|
||||
// statement was parameterised that was a second-order injection running as sa.
|
||||
var notAGuid = $"{AdminUiSqlServer.TestDatabasePrefix}20250101000000_notaguid";
|
||||
|
||||
CreateDatabase(master, notAGuid);
|
||||
|
||||
try
|
||||
{
|
||||
var reaped = SqlTestDatabaseReaper.ReapOrphans(
|
||||
master,
|
||||
AdminUiSqlServer.TestDatabasePrefix,
|
||||
DateTime.UtcNow - TimeSpan.FromHours(24));
|
||||
|
||||
reaped.ShouldNotContain(notAGuid);
|
||||
DatabaseExists(master, notAGuid)
|
||||
.ShouldBeTrue("only names shaped exactly as this code mints them are ever dropped");
|
||||
}
|
||||
finally
|
||||
{
|
||||
SqlTestDatabaseReaper.TryDropDatabase(master, notAGuid);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateDatabase(string masterConnectionString, string databaseName)
|
||||
{
|
||||
using var conn = new SqlConnection(masterConnectionString);
|
||||
|
||||
Reference in New Issue
Block a user