fix(sitecallaudit): push StuckOnly filter into SQL; doc accuracy fixes
This commit is contained in:
@@ -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()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user