using ZB.MOM.WW.CBDDC.Core.Exceptions;
using ZB.MOM.WW.CBDDC.Core.Network;
using ZB.MOM.WW.CBDDC.Core.Resilience;
namespace ZB.MOM.WW.CBDDC.Core.Tests;
public class RetryPolicyTests
{
///
/// Verifies transient failures are retried until a successful result is returned.
///
[Fact]
public async Task ExecuteAsync_WhenTransientFailureEventuallySucceeds_RetriesAndReturnsResult()
{
var policy = new RetryPolicy(CreateConfigProvider(retryAttempts: 3, retryDelayMs: 1));
var attempts = 0;
int result = await policy.ExecuteAsync(async () =>
{
attempts++;
if (attempts < 3) throw new NetworkException("transient");
await Task.CompletedTask;
return 42;
}, "test-op");
result.ShouldBe(42);
attempts.ShouldBe(3);
}
///
/// Verifies transient failures throw retry exhausted when all retries are consumed.
///
[Fact]
public async Task ExecuteAsync_WhenTransientFailureExhausted_ThrowsRetryExhaustedException()
{
var policy = new RetryPolicy(CreateConfigProvider(retryAttempts: 2, retryDelayMs: 1));
var attempts = 0;
var ex = await Should.ThrowAsync(() => policy.ExecuteAsync(() =>
{
attempts++;
throw new NetworkException("still transient");
}, "test-op"));
ex.ErrorCode.ShouldBe("RETRY_EXHAUSTED");
ex.InnerException.ShouldBeOfType();
attempts.ShouldBe(2);
}
///
/// Verifies non-transient failures are not retried.
///
[Fact]
public async Task ExecuteAsync_WhenFailureIsNonTransient_DoesNotRetry()
{
var policy = new RetryPolicy(CreateConfigProvider(retryAttempts: 3, retryDelayMs: 1));
var attempts = 0;
await Should.ThrowAsync(() => policy.ExecuteAsync(() =>
{
attempts++;
throw new InvalidOperationException("non-transient");
}, "test-op"));
attempts.ShouldBe(1);
}
private static IPeerNodeConfigurationProvider CreateConfigProvider(int retryAttempts, int retryDelayMs)
{
var configProvider = Substitute.For();
configProvider.GetConfiguration().Returns(new PeerNodeConfiguration
{
RetryAttempts = retryAttempts,
RetryDelayMs = retryDelayMs
});
return configProvider;
}
}