fix(audit): race-safe channel cache + UTC-kind cursor handling in gRPC pull client (review)

This commit is contained in:
Joseph Doherty
2026-06-15 09:49:43 -04:00
parent 2adc5767da
commit d03c2af9a1
2 changed files with 91 additions and 10 deletions
@@ -163,4 +163,53 @@ public class GrpcPullAuditEventsClientTests
Assert.Empty(result.Events);
Assert.False(result.MoreAvailable);
}
[Fact]
public async Task PullAsync_swallows_unexpected_faults_to_empty_response()
{
// I3(a): the catch-all path. A non-transport fault (e.g. a mapping/
// protocol error surfacing as InvalidOperationException) must still be
// swallowed to empty — audit reconciliation is best-effort and a throw
// would only get re-caught by the actor's per-site guard.
var invoker = FakeInvoker.Throwing(new InvalidOperationException("boom"));
var sut = new GrpcPullAuditEventsClient(
new StaticEnumerator(new SiteEntry("site-a", "http://site-a:8083")),
invoker,
NullLogger<GrpcPullAuditEventsClient>.Instance);
var result = await sut.PullAsync("site-a", BaseTime, batchSize: 256, CancellationToken.None);
Assert.Empty(result.Events);
Assert.False(result.MoreAvailable);
}
[Fact]
public async Task PullAsync_with_minvalue_unspecified_cursor_does_not_throw_and_dials()
{
// I3(b) / guards I2: the reconciliation cursor starts at DateTime.MinValue
// with Kind=Unspecified. EnsureUtc must treat it AS UTC (per the system-wide
// "all timestamps are UTC" invariant) and NOT call ToUniversalTime() — on a
// host with a positive UTC offset that underflows and Timestamp.FromDateTime
// throws ArgumentOutOfRangeException, crashing the FIRST pull for every site.
var minUnspecified = default(DateTime); // DateTime.MinValue, Kind=Unspecified
Assert.Equal(DateTimeKind.Unspecified, minUnspecified.Kind);
var invoker = FakeInvoker.Returning(new ProtoPullResponse());
var sut = new GrpcPullAuditEventsClient(
new StaticEnumerator(new SiteEntry("site-a", "http://site-a:8083")),
invoker,
NullLogger<GrpcPullAuditEventsClient>.Instance);
// MUST NOT throw — must dial successfully.
var result = await sut.PullAsync("site-a", minUnspecified, batchSize: 256, CancellationToken.None);
Assert.Equal(1, invoker.CallCount);
Assert.Equal("http://site-a:8083", invoker.Endpoint);
Assert.NotNull(invoker.Request);
// The unspecified-MinValue cursor is carried through verbatim as UTC
// MinValue (no local-TZ conversion).
Assert.Equal(DateTime.MinValue, invoker.Request!.SinceUtc.ToDateTime());
Assert.Empty(result.Events);
Assert.False(result.MoreAvailable);
}
}