diff --git a/tests/ZB.MOM.WW.ScadaBridge.AuditLog.Tests/Site/Telemetry/SiteAuditTelemetryActorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.AuditLog.Tests/Site/Telemetry/SiteAuditTelemetryActorTests.cs index 38416762..08d8af3a 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.AuditLog.Tests/Site/Telemetry/SiteAuditTelemetryActorTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.AuditLog.Tests/Site/Telemetry/SiteAuditTelemetryActorTests.cs @@ -416,27 +416,42 @@ public class SiteAuditTelemetryActorTests : TestKit // Assert — exactly one push containing ONLY the valid row: the orphan // has no operational half to send. + // + // Both MarkForwardedAsync expectations live INSIDE the awaited block + // rather than after it. The valid row is marked Forwarded only after + // the push returns and its ack is parsed (OnCachedDrainAsync: abandon, + // then push, then mark), so observing the push establishes no + // happens-before edge with that call — asserting it bare assumed one. + // On a loaded parallel run the drain's post-push continuation can be + // scheduled after the poll that saw the push, and the bare assertion + // then failed with "Actually received no matching calls" while the + // orphan's own earlier call appeared as the single non-matching one. + // The bounded wait removes the ordering assumption only: each call must + // still occur exactly once, with exactly the same arguments, or the + // test fails. await AwaitAssertAsync(async () => { await _client.Received(1).IngestCachedTelemetryAsync( Arg.Any(), Arg.Any()); - }, TimeSpan.FromSeconds(5)); + // The valid row is marked Forwarded because central ack'd it... + await _queue.Received(1).MarkForwardedAsync( + Arg.Is>(g => g.Count == 1 && g[0] == valid.EventId), + Arg.Any()); + + // ...and the orphan is marked Forwarded too, in its own call, so it + // LEAVES the cached queue. Its audit half is still delivered by the + // reconciliation pull, which covers Forwarded rows as well as Pending. + await _queue.Received(1).MarkForwardedAsync( + Arg.Is>(g => g.Count == 1 && g[0] == orphan.EventId), + Arg.Any()); + }, TimeSpan.FromSeconds(10)); + + // Safe as bare assertions: capturedBatch is captured synchronously by + // the push stub, so the awaited gate above already ordered them. Assert.NotNull(capturedBatch); Assert.Single(capturedBatch!.Packets); Assert.Equal(valid.EventId.ToString(), capturedBatch.Packets[0].AuditEvent.EventId); - - // The valid row is marked Forwarded because central ack'd it... - await _queue.Received(1).MarkForwardedAsync( - Arg.Is>(g => g.Count == 1 && g[0] == valid.EventId), - Arg.Any()); - - // ...and the orphan is marked Forwarded too, in its own call, so it - // LEAVES the cached queue. Its audit half is still delivered by the - // reconciliation pull, which covers Forwarded rows as well as Pending. - await _queue.Received(1).MarkForwardedAsync( - Arg.Is>(g => g.Count == 1 && g[0] == orphan.EventId), - Arg.Any()); } [Fact]