fix(sitecallaudit): UpdatedAtUtc index + per-row pull resilience + UTC-convention + first-cycle test (review)

This commit is contained in:
Joseph Doherty
2026-06-15 10:47:25 -04:00
parent 963e3427da
commit 6b0140dd62
5 changed files with 118 additions and 21 deletions
@@ -186,6 +186,42 @@ public class GrpcPullSiteCallsClientTests
Assert.False(result.MoreAvailable);
}
[Fact]
public async Task PullAsync_skips_poison_row_and_returns_the_good_rows()
{
// Poison-row resilience: one malformed operational (an unparseable
// TrackedOperationId fails SiteCallDtoMapper.FromDto → Guid.Parse) must be
// skipped+logged PER ROW rather than sinking the whole batch through the
// outer catch-all. The two good rows survive, re-stamped + oldest-first.
var older = Guid.NewGuid();
var newer = Guid.NewGuid();
var proto = new ProtoPullResponse { MoreAvailable = false };
proto.Operationals.Add(Dto(newer, BaseTime.AddMinutes(5)));
// Malformed row in the middle of the batch.
var bad = Dto(Guid.NewGuid(), BaseTime.AddMinutes(2));
bad.TrackedOperationId = "not-a-guid";
proto.Operationals.Add(bad);
proto.Operationals.Add(Dto(older, BaseTime));
var invoker = FakeInvoker.Returning(proto);
var sut = new GrpcPullSiteCallsClient(
new StaticEnumerator(new SiteEntry("site-a", "http://site-a:8083")),
invoker,
NullLogger<GrpcPullSiteCallsClient>.Instance);
// Must NOT throw — the bad row is dropped, the good rows are returned.
var result = await sut.PullAsync("site-a", BaseTime, batchSize: 256, CancellationToken.None);
Assert.Equal(2, result.SiteCalls.Count);
// Survivors are oldest-first and SourceSite re-stamped from the dialed siteId.
Assert.Equal(older, result.SiteCalls[0].TrackedOperationId.Value);
Assert.Equal(newer, result.SiteCalls[1].TrackedOperationId.Value);
Assert.Equal("site-a", result.SiteCalls[0].SourceSite);
Assert.Equal("site-a", result.SiteCalls[1].SourceSite);
Assert.False(result.MoreAvailable);
}
[Fact]
public async Task PullAsync_with_minvalue_unspecified_cursor_does_not_throw_and_dials()
{