diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionManagerTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionManagerTests.cs index 2a0a1af..1ac81c7 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionManagerTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionManagerTests.cs @@ -420,10 +420,16 @@ public sealed class SessionManagerTests [Fact] public async Task InvokeAsync_WhenWorkerFaulted_FailsFastWithBothStates() { + // Use a deliberately large ready-wait timeout so fail-fast is unambiguous: a terminal + // worker must surface immediately instead of burning it. The timing assertion below is + // anchored to a small fraction of this timeout (not an absolute ~100ms wall-clock bound, + // which flaked under CI load) so a regression that waited out the timeout is caught + // while ordinary scheduling jitter never trips it. + const int readyWaitTimeoutMs = 5000; FakeWorkerClient workerClient = new() { State = WorkerClientState.Faulted }; SessionManager manager = CreateManager( new FakeSessionWorkerClientFactory(workerClient), - options: CreateOptions(workerReadyWaitTimeoutMs: 500)); + options: CreateOptions(workerReadyWaitTimeoutMs: readyWaitTimeoutMs)); GatewaySession session = await manager.OpenSessionAsync(CreateOpenRequest(), "client-1", ownerKeyId: null, CancellationToken.None); Assert.Equal(SessionState.Ready, session.State); @@ -435,7 +441,9 @@ public sealed class SessionManagerTests CancellationToken.None)); stopwatch.Stop(); - Assert.True(stopwatch.ElapsedMilliseconds < 100, $"Expected immediate fail-fast but took {stopwatch.ElapsedMilliseconds}ms."); + Assert.True( + stopwatch.ElapsedMilliseconds < readyWaitTimeoutMs / 3, + $"Expected fail-fast well under the {readyWaitTimeoutMs}ms ready-wait timeout but took {stopwatch.ElapsedMilliseconds}ms."); Assert.Equal(SessionManagerErrorCode.SessionNotReady, exception.ErrorCode); Assert.Contains("Session state is Ready", exception.Message); Assert.Contains("worker state is Faulted", exception.Message); @@ -487,15 +495,17 @@ public sealed class SessionManagerTests GatewaySession session = await manager.OpenSessionAsync(CreateOpenRequest(), "client-1", ownerKeyId: null, CancellationToken.None); Assert.Equal(SessionState.Ready, session.State); - Stopwatch stopwatch = Stopwatch.StartNew(); + // A zero timeout has no wait window to burn, so there is no wall-clock timing to assert + // (the previous absolute ~100ms bound only measured host load and flaked under CI). The + // error code, the byte-for-byte both-states message, and InvokeCount == 0 fully pin the + // immediate fail-fast — a regression that started polling would still surface the same + // outcome, not a timing difference worth a flaky assertion. SessionManagerException exception = await Assert.ThrowsAsync( async () => await manager.InvokeAsync( session.SessionId, CreateCommand(MxCommandKind.Ping), CancellationToken.None)); - stopwatch.Stop(); - Assert.True(stopwatch.ElapsedMilliseconds < 100, $"Expected immediate fail-fast but took {stopwatch.ElapsedMilliseconds}ms."); Assert.Equal(SessionManagerErrorCode.SessionNotReady, exception.ErrorCode); Assert.Contains("Session state is Ready", exception.Message); Assert.Contains("worker state is Handshaking", exception.Message);