b9096f7c57
ExecuteSafeUnaryAsync capped the whole retry pipeline at one DefaultCallTimeout while each attempt carried the same deadline, so a first attempt ending in DeadlineExceeded spent the entire budget and the retry never ran. The outer cap is now MaxAttempts x DefaultCallTimeout plus MaxDelay per backoff (MxGatewayClientRetryPolicy.SafeUnaryBudget), in both MxGatewayClient and GalaxyRepositoryClient; a budget too large for a cancellation timer leaves only the per-attempt deadlines. Tests: MxGatewayClientWireTests.SafeUnaryRetry_AfterDeadlineExceededFirstAttempt_RunsTheRetry (real gRPC loopback server stalls the first Invoke; fails with Cancelled on the old code), plus two SafeUnaryBudget unit tests.
78 lines
2.6 KiB
C#
78 lines
2.6 KiB
C#
namespace ZB.MOM.WW.MxGateway.Client.Tests;
|
|
|
|
public sealed class MxGatewayClientOptionsTests
|
|
{
|
|
/// <summary>
|
|
/// The safe-unary budget covers every attempt's deadline plus the backoff between them, so a
|
|
/// first attempt that hits its deadline does not consume the retry's time (#197).
|
|
/// </summary>
|
|
[Fact]
|
|
public void SafeUnaryBudget_CoversEveryAttemptAndBackoff()
|
|
{
|
|
var options = new MxGatewayClientOptions
|
|
{
|
|
Endpoint = new Uri("http://localhost:5000"),
|
|
ApiKey = "test-api-key",
|
|
DefaultCallTimeout = TimeSpan.FromSeconds(30),
|
|
Retry = new MxGatewayClientRetryOptions { MaxAttempts = 3, MaxDelay = TimeSpan.FromSeconds(2) },
|
|
};
|
|
|
|
Assert.Equal(TimeSpan.FromSeconds(94), MxGatewayClientRetryPolicy.SafeUnaryBudget(options));
|
|
}
|
|
|
|
/// <summary>A budget beyond what a cancellation timer accepts means no outer cap rather than a throw.</summary>
|
|
[Fact]
|
|
public void SafeUnaryBudget_WhenTooLargeForATimer_ReturnsNull()
|
|
{
|
|
var options = new MxGatewayClientOptions
|
|
{
|
|
Endpoint = new Uri("http://localhost:5000"),
|
|
ApiKey = "test-api-key",
|
|
DefaultCallTimeout = TimeSpan.FromDays(40),
|
|
Retry = new MxGatewayClientRetryOptions { MaxAttempts = 2 },
|
|
};
|
|
|
|
Assert.Null(MxGatewayClientRetryPolicy.SafeUnaryBudget(options));
|
|
}
|
|
|
|
/// <summary>Verifies that options with valid endpoint and API key pass validation.</summary>
|
|
[Fact]
|
|
public void Validate_WithAbsoluteEndpointAndApiKey_Succeeds()
|
|
{
|
|
var options = new MxGatewayClientOptions
|
|
{
|
|
Endpoint = new Uri("http://localhost:5000"),
|
|
ApiKey = "test-api-key",
|
|
};
|
|
|
|
options.Validate();
|
|
}
|
|
|
|
/// <summary>Verifies that empty API key causes validation to fail.</summary>
|
|
[Fact]
|
|
public void Validate_WithEmptyApiKey_Throws()
|
|
{
|
|
var options = new MxGatewayClientOptions
|
|
{
|
|
Endpoint = new Uri("http://localhost:5000"),
|
|
ApiKey = "",
|
|
};
|
|
|
|
Assert.Throws<ArgumentException>(options.Validate);
|
|
}
|
|
|
|
/// <summary>Verifies that invalid retry options cause validation to fail.</summary>
|
|
[Fact]
|
|
public void Validate_WithInvalidRetryOptions_Throws()
|
|
{
|
|
var options = new MxGatewayClientOptions
|
|
{
|
|
Endpoint = new Uri("http://localhost:5000"),
|
|
ApiKey = "test-api-key",
|
|
Retry = new MxGatewayClientRetryOptions { MaxAttempts = 0 },
|
|
};
|
|
|
|
Assert.Throws<ArgumentOutOfRangeException>(options.Validate);
|
|
}
|
|
}
|