diff --git a/src/Netclaw.Actors.Tests/Channels/Contracts/GatewayLifecycleContractTests.cs b/src/Netclaw.Actors.Tests/Channels/Contracts/GatewayLifecycleContractTests.cs index e81120cd2..40bec0eb1 100644 --- a/src/Netclaw.Actors.Tests/Channels/Contracts/GatewayLifecycleContractTests.cs +++ b/src/Netclaw.Actors.Tests/Channels/Contracts/GatewayLifecycleContractTests.cs @@ -44,6 +44,13 @@ protected GatewayLifecycleContractTests(ITestOutputHelper output) : base(output: akka.scheduler.implementation = "Akka.TestKit.TestScheduler, Akka.TestKit" """); + /// + /// Budget for AwaitAssertAsync loops that embed a GetSnapshotAsync Ask. + /// Must exceed the 3s Ask timeout across retries: 15s / (3s + 100ms interval) + /// guarantees >= 4 full attempts on a loaded runner. + /// + private static readonly TimeSpan SnapshotAssertTimeout = TimeSpan.FromSeconds(15); + protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) { } @@ -189,7 +196,7 @@ await AwaitAssertAsync(async () => Assert.False(snapshot.IsReady); Assert.False(snapshot.IsConnected); Assert.Equal(1, CleanReconnectCount); - }, cancellationToken: TestContext.Current.CancellationToken); + }, duration: SnapshotAssertTimeout, cancellationToken: TestContext.Current.CancellationToken); } [Fact] @@ -231,7 +238,7 @@ await AwaitAssertAsync(async () => Assert.True(snapshot.IsConnected); Assert.False(snapshot.IsReady); Assert.Equal(1, CleanReconnectCount); - }, cancellationToken: TestContext.Current.CancellationToken); + }, duration: SnapshotAssertTimeout, cancellationToken: TestContext.Current.CancellationToken); ReleaseTransportStop(); @@ -240,7 +247,7 @@ await AwaitAssertAsync(async () => var snapshot = await GetSnapshotAsync(actor); Assert.False(snapshot.IsConnected); Assert.False(snapshot.IsReady); - }, cancellationToken: TestContext.Current.CancellationToken); + }, duration: SnapshotAssertTimeout, cancellationToken: TestContext.Current.CancellationToken); } [Fact] @@ -270,7 +277,7 @@ await AwaitAssertAsync(async () => // publishing closes the Healthy-but-deaf gap where a client-side // ask timeout suppressed the only setup signal. Assert.Equal(2, ConnectionRestoredCount); - }, cancellationToken: TestContext.Current.CancellationToken); + }, duration: SnapshotAssertTimeout, cancellationToken: TestContext.Current.CancellationToken); } [Fact] @@ -310,7 +317,7 @@ await AwaitAssertAsync(async () => { Assert.Equal(2, TransportStartCount); Assert.True((await GetSnapshotAsync(actor)).IsReady); - }, cancellationToken: TestContext.Current.CancellationToken); + }, duration: SnapshotAssertTimeout, cancellationToken: TestContext.Current.CancellationToken); // Flap #2, again inside the stability window: the backoff must NOT // reset to zero — the next retry honors the grown 5s delay instead of @@ -342,7 +349,7 @@ await AwaitAssertAsync(async () => { Assert.Equal(2, TransportStartCount); Assert.True((await GetSnapshotAsync(actor)).IsReady); - }, cancellationToken: TestContext.Current.CancellationToken); + }, duration: SnapshotAssertTimeout, cancellationToken: TestContext.Current.CancellationToken); // Hold Ready past the stability window, then drop: the backoff must // reset, so the next retry fires immediately instead of after 5s. @@ -399,5 +406,5 @@ await AwaitAssertAsync(async () => Assert.False(snapshot.IsConnected); Assert.Equal(DisconnectedHealthDetail, snapshot.HealthDetail); Assert.Equal(expectedCleanReconnects, CleanReconnectCount); - }, cancellationToken: TestContext.Current.CancellationToken); + }, duration: SnapshotAssertTimeout, cancellationToken: TestContext.Current.CancellationToken); }