fix(audit-log): explicit long CommandTimeout on partition-switch and per-channel purge maintenance paths

This commit is contained in:
Joseph Doherty
2026-07-09 06:43:23 -04:00
parent 1b6b0aab2a
commit b59aa2d717
11 changed files with 231 additions and 17 deletions
@@ -474,4 +474,54 @@ WHERE name = 'UX_AuditLog_EventId'
Assert.Contains(rows, r => r.EventId == dbOldId);
Assert.Contains(rows, r => r.EventId == dbRecentId);
}
// ---------------------------------------------------------------------
// 5. SwitchOut_WithExplicitCommandTimeout_Succeeds (Task 4)
// ---------------------------------------------------------------------
/// <summary>
/// Task 4 (arch-review 04, S2): proves the explicit-<see cref="TimeSpan"/> maintenance-timeout
/// overload of <see cref="IAuditLogRepository.SwitchOutPartitionAsync"/> runs the real
/// drop-and-rebuild dance to completion against SQL Server — the old row is purged, the kept row
/// survives, and the returned sampled row-count reflects the switched partition. Exercising the
/// timeout path end-to-end guards against a regression that only sets the timeout on the sample
/// command and forgets the DDL batch (or vice versa).
/// </summary>
[SkippableFact]
public async Task SwitchOut_WithExplicitCommandTimeout_Succeeds()
{
Skip.IfNot(_fixture.Available, _fixture.SkipReason);
var siteId = "switch-timeout-" + Guid.NewGuid().ToString("N").Substring(0, 8);
var oldEventId = Guid.NewGuid();
var keptEventId = Guid.NewGuid();
var (oldOccurred, keptOccurred, _, _) = SeedOccurredAt();
await using (var seedConn = _fixture.OpenConnection())
{
await DirectInsertAsync(seedConn, oldEventId, oldOccurred, siteId);
await DirectInsertAsync(seedConn, keptEventId, keptOccurred, siteId);
}
var janBoundary = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc);
await using (var ctx = CreateContext())
{
var repo = new AuditLogRepository(ctx);
var rowsDeleted = await repo.SwitchOutPartitionAsync(janBoundary, TimeSpan.FromMinutes(30));
Assert.True(rowsDeleted >= 1,
$"Expected the Jan-2026 partition switch-out to report >= 1 sampled row; got {rowsDeleted}.");
}
await using var verify = CreateContext();
var rows = await verify.Set<AuditLogRow>()
.Where(e => e.SourceSiteId == siteId)
.ToListAsync();
Assert.DoesNotContain(rows, r => r.EventId == oldEventId);
Assert.Contains(rows, r => r.EventId == keptEventId);
// The dance must leave the idempotency-supporting unique index rebuilt.
await using var check = _fixture.OpenConnection();
await AssertUxIndexExistsAsync(check);
}
}