fix(sitecallaudit): push StuckOnly filter into SQL; doc accuracy fixes

This commit is contained in:
Joseph Doherty
2026-05-21 04:24:16 -04:00
parent e3519fdb39
commit ac1f73cf8a
8 changed files with 168 additions and 20 deletions

View File

@@ -271,6 +271,67 @@ public class SiteCallAuditRepositoryTests : IClassFixture<MsSqlMigrationFixture>
Assert.Equal(5, allIds.Count);
}
[SkippableFact]
public async Task QueryAsync_StuckCutoff_ComposesWithKeysetPaging_NoEmptyPages()
{
Skip.IfNot(_fixture.Available, _fixture.SkipReason);
var site = NewSiteId();
await using var context = CreateContext();
var repo = new SiteCallAuditRepository(context);
// Three stuck rows (non-terminal, created before the cutoff) interleaved
// by CreatedAtUtc with non-stuck rows: recent non-terminal rows and an
// old-but-terminal row. The stuck predicate is pushed into the SQL WHERE
// alongside the keyset cursor, so each page must come back full of stuck
// rows — never under-filled by a post-filter.
var t0 = new DateTime(2026, 5, 20, 8, 0, 0, DateTimeKind.Utc);
var cutoff = t0.AddMinutes(10);
var stuckIds = new List<TrackedOperationId>();
for (var i = 0; i < 3; i++)
{
var stuckId = TrackedOperationId.New();
stuckIds.Add(stuckId);
// Stuck: non-terminal, created before the cutoff.
await repo.UpsertAsync(NewRow(
stuckId, sourceSite: site, status: "Attempted",
createdAtUtc: t0.AddMinutes(i)));
// Not stuck: non-terminal but created after the cutoff.
await repo.UpsertAsync(NewRow(
TrackedOperationId.New(), sourceSite: site, status: "Attempted",
createdAtUtc: cutoff.AddMinutes(i + 1)));
// Not stuck: created before the cutoff but terminal.
await repo.UpsertAsync(NewRow(
TrackedOperationId.New(), sourceSite: site, status: "Delivered",
createdAtUtc: t0.AddMinutes(i), terminal: true,
terminalAtUtc: t0.AddMinutes(i + 1)));
}
var filter = new SiteCallQueryFilter(SourceSite: site, StuckCutoffUtc: cutoff);
var page1 = await repo.QueryAsync(filter, new SiteCallPaging(PageSize: 2));
Assert.Equal(2, page1.Count);
Assert.All(page1, r => Assert.Null(r.TerminalAtUtc));
Assert.All(page1, r => Assert.True(r.CreatedAtUtc < cutoff));
var cursor1 = page1[^1];
var page2 = await repo.QueryAsync(
filter,
new SiteCallPaging(
PageSize: 2,
AfterCreatedAtUtc: cursor1.CreatedAtUtc,
AfterId: cursor1.TrackedOperationId));
// Only the third stuck row remains — no empty trailing page.
Assert.Single(page2);
Assert.Null(page2[0].TerminalAtUtc);
Assert.True(page2[0].CreatedAtUtc < cutoff);
// Exactly the three stuck rows, no overlap, no non-stuck leakage.
var returned = page1.Concat(page2).Select(r => r.TrackedOperationId).ToHashSet();
Assert.Equal(stuckIds.ToHashSet(), returned);
}
[SkippableFact]
public async Task PurgeTerminalAsync_RemovesTerminalAndOld()
{