test(db): FlipAuditLogClusteredKey tests restore to head, expect migration 64's grant

AuditMaintenanceGrants (migration 64) landed after the flip and adds
GRANT UPDATE (SourceNode) to the purger, but the tests still migrated "back
up" only to the flip and asserted the pre-64 grant list, so the class failed
in either test order (the grant missing after DownUp, or unexpected before).
Restore to head and compare the down state against the pre-64 grant set.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-11-12 06:55:04 -05:00
parent c7dfc21b4c
commit 7bc350818c
@@ -87,12 +87,14 @@ public sealed class FlipAuditLogClusteredKeyMigrationTests : IClassFixture<MsSql
Assert.Equal("EventId, OccurredAtUtc", down["PK_AuditLog"].Keys); Assert.Equal("EventId, OccurredAtUtc", down["PK_AuditLog"].Keys);
Assert.False(down.ContainsKey("UX_AuditLog_EventId_Occurred")); Assert.False(down.ContainsKey("UX_AuditLog_EventId_Occurred"));
AssertTwelveNonClusteredIndexes(down); AssertTwelveNonClusteredIndexes(down);
Assert.Equal(ExpectedGrants, await ReadGrantsAsync()); Assert.Equal(GrantsBeforeMaintenanceGrants, await ReadGrantsAsync());
// Old shape: the purge's staging table must take the old key order. // Old shape: the purge's staging table must take the old key order.
await AssertSwitchOutPurgesAsync(new DateTime(2027, 3, 1, 0, 0, 0, DateTimeKind.Utc)); await AssertSwitchOutPurgesAsync(new DateTime(2027, 3, 1, 0, 0, 0, DateTimeKind.Utc));
await MigrateToAsync(FlipMigration); // Back to head (not just to the flip): migrations after it — AuditMaintenanceGrants —
// were reverted by the Down too, and the class leaves the database fully migrated.
await MigrateToHeadAsync();
var up = await ReadIndexesAsync(); var up = await ReadIndexesAsync();
Assert.Equal("OccurredAtUtc, EventId", up["PK_AuditLog"].Keys); Assert.Equal("OccurredAtUtc, EventId", up["PK_AuditLog"].Keys);
@@ -140,7 +142,7 @@ public sealed class FlipAuditLogClusteredKeyMigrationTests : IClassFixture<MsSql
} }
finally finally
{ {
await MigrateToAsync(FlipMigration); await MigrateToHeadAsync();
} }
} }
@@ -151,12 +153,18 @@ public sealed class FlipAuditLogClusteredKeyMigrationTests : IClassFixture<MsSql
"scadabridge_audit_chainer|UPDATE|GRANT|RowHash", "scadabridge_audit_chainer|UPDATE|GRANT|RowHash",
"scadabridge_audit_purger|DELETE|GRANT|", "scadabridge_audit_purger|DELETE|GRANT|",
"scadabridge_audit_purger|SELECT|GRANT|", "scadabridge_audit_purger|SELECT|GRANT|",
// AuditMaintenanceGrants (migration 64, after the flip) — the archive path's column grant.
"scadabridge_audit_purger|UPDATE|GRANT|SourceNode",
"scadabridge_audit_writer|DELETE|DENY|", "scadabridge_audit_writer|DELETE|DENY|",
"scadabridge_audit_writer|INSERT|GRANT|", "scadabridge_audit_writer|INSERT|GRANT|",
"scadabridge_audit_writer|SELECT|GRANT|", "scadabridge_audit_writer|SELECT|GRANT|",
"scadabridge_audit_writer|UPDATE|DENY|", "scadabridge_audit_writer|UPDATE|DENY|",
]; ];
// The grants one migration before the flip: AuditMaintenanceGrants is not applied yet.
private static readonly string[] GrantsBeforeMaintenanceGrants =
ExpectedGrants.Where(g => !g.EndsWith("|SourceNode", StringComparison.Ordinal)).ToArray();
private static void AssertTwelveNonClusteredIndexes(IReadOnlyDictionary<string, IndexShape> indexes) private static void AssertTwelveNonClusteredIndexes(IReadOnlyDictionary<string, IndexShape> indexes)
{ {
Assert.Equal("OccurredAtUtc DESC", indexes["IX_AuditLog_OccurredAtUtc"].Keys); Assert.Equal("OccurredAtUtc DESC", indexes["IX_AuditLog_OccurredAtUtc"].Keys);
@@ -252,6 +260,13 @@ FROM n;", conn) { CommandTimeout = 300 };
await ctx.GetService<IMigrator>().MigrateAsync(migration); await ctx.GetService<IMigrator>().MigrateAsync(migration);
} }
private async Task MigrateToHeadAsync()
{
await using var ctx = new ScadaBridgeDbContext(new DbContextOptionsBuilder<ScadaBridgeDbContext>()
.UseSqlServer(_fixture.ConnectionString, sql => sql.CommandTimeout(600)).Options);
await ctx.GetService<IMigrator>().MigrateAsync();
}
private static string MigrationBefore(string migration) private static string MigrationBefore(string migration)
{ {
using var ctx = new ScadaBridgeDbContext(new DbContextOptionsBuilder<ScadaBridgeDbContext>() using var ctx = new ScadaBridgeDbContext(new DbContextOptionsBuilder<ScadaBridgeDbContext>()