From f07e8461f07e08b84e3a683e6ddfa28b11a2d45b Mon Sep 17 00:00:00 2001 From: Andy Zivkovic Date: Sat, 5 Nov 2022 07:57:24 +1030 Subject: [PATCH 1/3] Add test to validate HTTP 429 retries --- .../HttpSource/HttpRetryHandlerTests.cs | 105 ++++++++---------- 1 file changed, 49 insertions(+), 56 deletions(-) diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/HttpSource/HttpRetryHandlerTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/HttpSource/HttpRetryHandlerTests.cs index 76b98badae5..a2b3256c93a 100644 --- a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/HttpSource/HttpRetryHandlerTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/HttpSource/HttpRetryHandlerTests.cs @@ -21,7 +21,7 @@ namespace NuGet.Protocol.Tests public class HttpRetryHandlerTests { private const int MaxTries = 5; - private const string TestUrl = "https://test.local/test.json"; + private const string TestUrl = "https://local.test/test.json"; private static readonly TimeSpan SmallTimeout = TimeSpan.FromMilliseconds(50); private static readonly TimeSpan LargeTimeout = TimeSpan.FromSeconds(5); @@ -100,12 +100,7 @@ public async Task HttpRetryHandler_DifferentRequestInstanceEachTime() // Arrange TimeSpan retryDelay = TimeSpan.Zero; - TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( - new Dictionary() - { - [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = MaxTries.ToString(), - [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = retryDelay.TotalMilliseconds.ToString() - }); + TestEnvironmentVariableReader testEnvironmentVariableReader = GetEnhancedHttpRetryEnvironmentVariables(); var requests = new HashSet(); Func handler = requestMessage => @@ -140,12 +135,7 @@ public async Task HttpRetryHandler_CancelsRequestAfterTimeout() // Arrange TimeSpan retryDelay = TimeSpan.Zero; - TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( - new Dictionary() - { - [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = MaxTries.ToString(), - [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = retryDelay.TotalMilliseconds.ToString() - }); + TestEnvironmentVariableReader testEnvironmentVariableReader = GetEnhancedHttpRetryEnvironmentVariables(); var requestToken = CancellationToken.None; Func> handler = async (requestMessage, token) => { @@ -182,12 +172,7 @@ public async Task HttpRetryHandler_ThrowsTimeoutExceptionForTimeout() // Arrange TimeSpan retryDelay = TimeSpan.Zero; - TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( - new Dictionary() - { - [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = MaxTries.ToString(), - [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = retryDelay.TotalMilliseconds.ToString() - }); + TestEnvironmentVariableReader testEnvironmentVariableReader = GetEnhancedHttpRetryEnvironmentVariables(); Func> handler = async (requestMessage, token) => { await Task.Delay(LargeTimeout); @@ -220,12 +205,7 @@ public async Task HttpRetryHandler_MultipleTriesTimed() // Arrange TimeSpan retryDelay = SmallTimeout; - TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( - new Dictionary() - { - [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = MaxTries.ToString(), - [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = retryDelay.TotalMilliseconds.ToString() - }); + TestEnvironmentVariableReader testEnvironmentVariableReader = GetEnhancedHttpRetryEnvironmentVariables(); Func handler = requestMessage => { return new HttpResponseMessage(HttpStatusCode.ServiceUnavailable); @@ -266,12 +246,7 @@ public async Task HttpRetryHandler_MultipleTriesNoSuccess() // Arrange TimeSpan retryDelay = TimeSpan.Zero; - TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( - new Dictionary() - { - [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = MaxTries.ToString(), - [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = retryDelay.TotalMilliseconds.ToString() - }); + TestEnvironmentVariableReader testEnvironmentVariableReader = GetEnhancedHttpRetryEnvironmentVariables(); var hits = 0; Func handler = requestMessage => @@ -307,12 +282,7 @@ public async Task HttpRetryHandler_MultipleSuccessFirstVerifySingleHit() // Arrange TimeSpan retryDelay = TimeSpan.Zero; - TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( - new Dictionary() - { - [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = MaxTries.ToString(), - [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = retryDelay.TotalMilliseconds.ToString() - }); + TestEnvironmentVariableReader testEnvironmentVariableReader = GetEnhancedHttpRetryEnvironmentVariables(); var hits = 0; Func handler = requestMessage => @@ -347,12 +317,7 @@ public async Task HttpRetryHandler_404VerifySingleHit() // Arrange TimeSpan retryDelay = TimeSpan.Zero; - TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( - new Dictionary() - { - [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = MaxTries.ToString(), - [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = retryDelay.TotalMilliseconds.ToString() - }); + TestEnvironmentVariableReader testEnvironmentVariableReader = GetEnhancedHttpRetryEnvironmentVariables(); var hits = 0; Func handler = requestMessage => @@ -432,12 +397,7 @@ public async Task HttpRetryHandler_MultipleTriesUntilSuccess() // Arrange TimeSpan retryDelay = TimeSpan.Zero; - TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( - new Dictionary() - { - [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = MaxTries.ToString(), - [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = retryDelay.TotalMilliseconds.ToString() - }); + TestEnvironmentVariableReader testEnvironmentVariableReader = GetEnhancedHttpRetryEnvironmentVariables(); var tries = 0; var sent503 = false; @@ -501,13 +461,7 @@ public async Task HttpRetryHandler_EnhancedRetryAllowsSettingMoreRetries() } }; - TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( - new Dictionary() - { - [EnhancedHttpRetryHelper.IsEnabledEnvironmentVariableName] = bool.TrueString, - [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = "11", - [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = "3" - }); + TestEnvironmentVariableReader testEnvironmentVariableReader = GetEnhancedHttpRetryEnvironmentVariables(retryCount: 11, delayMilliseconds: 3); EnhancedHttpRetryHelper helper = new EnhancedHttpRetryHelper(testEnvironmentVariableReader); Assert.Equal(helper.IsEnabled, true); @@ -564,11 +518,50 @@ public void HttpRetryHandler_EnhancedRetryOnByDefault(string value, bool expecte Assert.Equal(helper.DelayInMilliseconds, EnhancedHttpRetryHelper.DefaultDelayMilliseconds); } + [Fact] + public async Task HttpRetryHandler_RetriesOn429() + { + // Arrange + int attempt = 0; + // .NET Framework don't have HttpStatusCode.TooManyRequests + HttpStatusCode tooManyRequestsStatusCode = (HttpStatusCode)429; + + var busyServer = new HttpRetryTestHandler((req, cancellationToken) => + attempt++ < 2 + ? Task.FromResult(new HttpResponseMessage(tooManyRequestsStatusCode)) + : Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))); + var client = new HttpClient(busyServer); + + var httpRetryHandlerRequest = new HttpRetryHandlerRequest(client, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)); + TestEnvironmentVariableReader environmentVariables = GetEnhancedHttpRetryEnvironmentVariables(); + var httpRetryHandler = new HttpRetryHandler(environmentVariables); + var logger = new TestLogger(); + + // Act + var response = await httpRetryHandler.SendAsync(httpRetryHandlerRequest, logger, CancellationToken.None); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + attempt.Should().Be(3); + + string expectedMessagePrefix = " " + tooManyRequestsStatusCode.ToString() + " " + TestUrl; + logger.Messages.Where(m => m.StartsWith(expectedMessagePrefix, StringComparison.Ordinal)).Count().Should().Be(2); + } + private static TimeSpan GetRetryMinTime(int tries, TimeSpan retryDelay) { return TimeSpan.FromTicks((tries - 1) * retryDelay.Ticks); } + private static TestEnvironmentVariableReader GetEnhancedHttpRetryEnvironmentVariables(bool? isEnabled = true, int? retryCount = MaxTries, int? delayMilliseconds = 0) + => new TestEnvironmentVariableReader( + new Dictionary() + { + [EnhancedHttpRetryHelper.IsEnabledEnvironmentVariableName] = isEnabled?.ToString(), + [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = retryCount?.ToString(), + [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = delayMilliseconds?.ToString() + }); + private class TestHandler : DelegatingHandler { public HttpRequestMessage LastRequest { get; private set; } From c9a94bb9588c2e7833c613aa8608699291d028d3 Mon Sep 17 00:00:00 2001 From: Andy Zivkovic Date: Sat, 5 Nov 2022 08:19:40 +1030 Subject: [PATCH 2/3] Retry on HTTP 429 and 408 --- .../NuGet.Protocol/HttpSource/HttpRetryHandler.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/NuGet.Core/NuGet.Protocol/HttpSource/HttpRetryHandler.cs b/src/NuGet.Core/NuGet.Protocol/HttpSource/HttpRetryHandler.cs index 88dd9c4505c..c5438de9e9f 100644 --- a/src/NuGet.Core/NuGet.Protocol/HttpSource/HttpRetryHandler.cs +++ b/src/NuGet.Core/NuGet.Protocol/HttpSource/HttpRetryHandler.cs @@ -204,7 +204,11 @@ public async Task SendAsync( requestUri, bodyStopwatch.ElapsedMilliseconds)); - if ((int)response.StatusCode >= 500) + int statusCode = (int)response.StatusCode; + // 5xx == server side failure + // 408 == request timeout + // 429 == too many requests + if (statusCode >= 500 || statusCode == 408 || statusCode == 429) { success = false; } From 88f0e58ca7369078f86fbab88d475e53b149fe72 Mon Sep 17 00:00:00 2001 From: Andy Zivkovic Date: Thu, 10 Nov 2022 08:06:43 +1030 Subject: [PATCH 3/3] Add EnvVar to disable 429 retries --- .../NuGet.Protocol/EnhancedHttpRetryHelper.cs | 21 +++- .../HttpSource/HttpRetryHandler.cs | 2 +- .../EnhancedHttpRetryHelperTests.cs | 111 ++++++++++++++++++ .../HttpSource/HttpRetryHandlerTests.cs | 65 ++++------ 4 files changed, 155 insertions(+), 44 deletions(-) create mode 100644 test/NuGet.Core.Tests/NuGet.Protocol.Tests/EnhancedHttpRetryHelperTests.cs diff --git a/src/NuGet.Core/NuGet.Protocol/EnhancedHttpRetryHelper.cs b/src/NuGet.Core/NuGet.Protocol/EnhancedHttpRetryHelper.cs index 635bba90232..1f623541488 100644 --- a/src/NuGet.Core/NuGet.Protocol/EnhancedHttpRetryHelper.cs +++ b/src/NuGet.Core/NuGet.Protocol/EnhancedHttpRetryHelper.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +#nullable enable + using System; using NuGet.Common; @@ -26,6 +28,11 @@ internal class EnhancedHttpRetryHelper /// public const int DefaultRetryCount = 6; + /// + /// The default value indicating whether or not to retry HTTP 429 responses. + /// + public const bool DefaultRetry429 = true; + /// /// The environment variable used to change the delay value. /// @@ -41,6 +48,11 @@ internal class EnhancedHttpRetryHelper /// public const string RetryCountEnvironmentVariableName = "NUGET_ENHANCED_MAX_NETWORK_TRY_COUNT"; + /// + /// The environment variabled to to disable retrying HTTP 429 responses. + /// + public const string Retry429EnvironmentVariableName = "NUGET_RETRY_HTTP_429"; + private readonly IEnvironmentVariableReader _environmentVariableReader; private bool? _isEnabled = null; @@ -49,6 +61,8 @@ internal class EnhancedHttpRetryHelper private int? _delayInMilliseconds = null; + private bool? _retry429 = null; + /// /// Initializes a new instance of the class. /// @@ -74,6 +88,11 @@ public EnhancedHttpRetryHelper(IEnvironmentVariableReader environmentVariableRea /// internal int DelayInMilliseconds => _delayInMilliseconds ??= GetIntFromEnvironmentVariable(DelayInMillisecondsEnvironmentVariableName, defaultValue: DefaultDelayMilliseconds, _environmentVariableReader); + /// + /// Gets a value indicating whether or not retryable HTTP 4xx responses should be retried. + /// + internal bool Retry429 => _retry429 ??= GetBoolFromEnvironmentVariable(Retry429EnvironmentVariableName, defaultValue: true, _environmentVariableReader); + /// /// Gets a value from the specified environment variable. /// @@ -106,7 +125,7 @@ private static int GetIntFromEnvironmentVariable(string variableName, int defaul { try { - if (int.TryParse(environmentVariableReader.GetEnvironmentVariable(variableName), out int parsedValue)) + if (int.TryParse(environmentVariableReader.GetEnvironmentVariable(variableName), out int parsedValue) && parsedValue >= 0) { return parsedValue; } diff --git a/src/NuGet.Core/NuGet.Protocol/HttpSource/HttpRetryHandler.cs b/src/NuGet.Core/NuGet.Protocol/HttpSource/HttpRetryHandler.cs index c5438de9e9f..8411e6c7fa7 100644 --- a/src/NuGet.Core/NuGet.Protocol/HttpSource/HttpRetryHandler.cs +++ b/src/NuGet.Core/NuGet.Protocol/HttpSource/HttpRetryHandler.cs @@ -208,7 +208,7 @@ public async Task SendAsync( // 5xx == server side failure // 408 == request timeout // 429 == too many requests - if (statusCode >= 500 || statusCode == 408 || statusCode == 429) + if (statusCode >= 500 || ((statusCode == 408 || statusCode == 429) && _enhancedHttpRetryHelper.Retry429)) { success = false; } diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/EnhancedHttpRetryHelperTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/EnhancedHttpRetryHelperTests.cs new file mode 100644 index 00000000000..7a91437f486 --- /dev/null +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/EnhancedHttpRetryHelperTests.cs @@ -0,0 +1,111 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#nullable enable + +using System.Collections.Generic; +using Test.Utility; +using Xunit; + +namespace NuGet.Protocol.Tests +{ + public class EnhancedHttpRetryHelperTests + { + [Fact] + public void NoEnvionrmentVaraiblesSet_UsesDefaultValues() + { + // Arrange + TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader(new Dictionary()); + + // Act + EnhancedHttpRetryHelper helper = new(testEnvironmentVariableReader); + + // Assert + Assert.Equal(helper.IsEnabled, EnhancedHttpRetryHelper.DefaultEnabled); + Assert.Equal(helper.RetryCount, EnhancedHttpRetryHelper.DefaultRetryCount); + Assert.Equal(helper.DelayInMilliseconds, EnhancedHttpRetryHelper.DefaultDelayMilliseconds); + Assert.Equal(helper.Retry429, EnhancedHttpRetryHelper.DefaultRetry429); + } + + [Theory] + [InlineData("")] + [InlineData("5")] + [InlineData("something")] + public void InvalidBoolValue_UsesDefault(string value) + { + // Arrange + var dict = new Dictionary() + { + [EnhancedHttpRetryHelper.IsEnabledEnvironmentVariableName] = value + }; + var environmentReader = new TestEnvironmentVariableReader(dict); + + // Act + EnhancedHttpRetryHelper helper = new(environmentReader); + + // Assert + Assert.Equal(helper.IsEnabled, EnhancedHttpRetryHelper.DefaultEnabled); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ValidBoolValue_UsesValue(bool value) + { + // Arrange + var dict = new Dictionary() + { + [EnhancedHttpRetryHelper.IsEnabledEnvironmentVariableName] = value.ToString().ToLowerInvariant() + }; + var environmentReader = new TestEnvironmentVariableReader(dict); + + // Act + EnhancedHttpRetryHelper helper = new(environmentReader); + + // Assert + Assert.Equal(helper.IsEnabled, value); + } + + [Theory] + [InlineData("")] + [InlineData("true")] + [InlineData("something")] + [InlineData("-5")] + public void InvalidIntValue_UsesDefault(string value) + { + // Arrange + var dict = new Dictionary() + { + [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = value + }; + var environmentReader = new TestEnvironmentVariableReader(dict); + + // Act + EnhancedHttpRetryHelper helper = new(environmentReader); + + // Assert + Assert.Equal(helper.RetryCount, EnhancedHttpRetryHelper.DefaultRetryCount); + } + + [Theory] + [InlineData(5)] + [InlineData(10)] + [InlineData(100)] + public void ValidIntValue_UsesValue(int value) + { + // Arrange + var dict = new Dictionary() + { + [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = value.ToString().ToLowerInvariant() + }; + var environmentReader = new TestEnvironmentVariableReader(dict); + + // Act + EnhancedHttpRetryHelper helper = new(environmentReader); + + // Assert + Assert.Equal(helper.RetryCount, value); + } + + } +} diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/HttpSource/HttpRetryHandlerTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/HttpSource/HttpRetryHandlerTests.cs index a2b3256c93a..95795dd77aa 100644 --- a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/HttpSource/HttpRetryHandlerTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/HttpSource/HttpRetryHandlerTests.cs @@ -461,21 +461,15 @@ public async Task HttpRetryHandler_EnhancedRetryAllowsSettingMoreRetries() } }; - TestEnvironmentVariableReader testEnvironmentVariableReader = GetEnhancedHttpRetryEnvironmentVariables(retryCount: 11, delayMilliseconds: 3); - - EnhancedHttpRetryHelper helper = new EnhancedHttpRetryHelper(testEnvironmentVariableReader); - Assert.Equal(helper.IsEnabled, true); - // Enhanced retry mode causes a random 0-199 ms jitter so we can't time it in this test - // but we can make sure the setting got through - Assert.Equal(helper.DelayInMilliseconds, 3); - Assert.Equal(helper.RetryCount, 11); + int retryCount = 11; + TestEnvironmentVariableReader testEnvironmentVariableReader = GetEnhancedHttpRetryEnvironmentVariables(retryCount: retryCount, delayMilliseconds: 3); var retryHandler = new HttpRetryHandler(testEnvironmentVariableReader); var testHandler = new HttpRetryTestHandler(handler); var httpClient = new HttpClient(testHandler); var request = new HttpRetryHandlerRequest(httpClient, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)) { - MaxTries = helper.RetryCount, + MaxTries = retryCount, RequestTimeout = Timeout.InfiniteTimeSpan, // HttpRetryHandler will override with values from NUGET_ENHANCED_NETWORK_RETRY_DELAY_MILLISECONDS // so set this to a value that will cause test timeout if the correct value is not honored. @@ -484,7 +478,8 @@ public async Task HttpRetryHandler_EnhancedRetryAllowsSettingMoreRetries() var log = new TestLogger(); // Act - using (var response = await retryHandler.SendAsync(request, log, CancellationToken.None)) + using (CancellationTokenSource cts = new CancellationTokenSource(millisecondsDelay: 60 * 1000)) + using (var response = await retryHandler.SendAsync(request, log, cts.Token)) { // Assert Assert.True(sent503); @@ -494,32 +489,9 @@ public async Task HttpRetryHandler_EnhancedRetryAllowsSettingMoreRetries() } [Theory] - [InlineData(null, EnhancedHttpRetryHelper.DefaultEnabled)] - [InlineData("true", true)] - [InlineData("false", false)] - [InlineData("0", EnhancedHttpRetryHelper.DefaultEnabled)] - [InlineData("something", EnhancedHttpRetryHelper.DefaultEnabled)] - public void HttpRetryHandler_EnhancedRetryOnByDefault(string value, bool expectedValue) - { - // Arrange - TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader( - new Dictionary() - { - [EnhancedHttpRetryHelper.IsEnabledEnvironmentVariableName] = value, - [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = null, - [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = null - }); - - // Act - EnhancedHttpRetryHelper helper = new EnhancedHttpRetryHelper(testEnvironmentVariableReader); - - Assert.Equal(helper.IsEnabled, expectedValue); - Assert.Equal(helper.RetryCount, EnhancedHttpRetryHelper.DefaultRetryCount); - Assert.Equal(helper.DelayInMilliseconds, EnhancedHttpRetryHelper.DefaultDelayMilliseconds); - } - - [Fact] - public async Task HttpRetryHandler_RetriesOn429() + [InlineData(true)] + [InlineData(false)] + public async Task HttpRetryHandler_429Response_RetriesWhenEnvVarSet(bool retry429) { // Arrange int attempt = 0; @@ -533,7 +505,7 @@ public async Task HttpRetryHandler_RetriesOn429() var client = new HttpClient(busyServer); var httpRetryHandlerRequest = new HttpRetryHandlerRequest(client, () => new HttpRequestMessage(HttpMethod.Get, TestUrl)); - TestEnvironmentVariableReader environmentVariables = GetEnhancedHttpRetryEnvironmentVariables(); + TestEnvironmentVariableReader environmentVariables = GetEnhancedHttpRetryEnvironmentVariables(retry429: retry429); var httpRetryHandler = new HttpRetryHandler(environmentVariables); var logger = new TestLogger(); @@ -541,11 +513,19 @@ public async Task HttpRetryHandler_RetriesOn429() var response = await httpRetryHandler.SendAsync(httpRetryHandlerRequest, logger, CancellationToken.None); // Assert - response.StatusCode.Should().Be(HttpStatusCode.OK); - attempt.Should().Be(3); + if (retry429) + { + response.StatusCode.Should().Be(HttpStatusCode.OK); + attempt.Should().Be(3); + } + else + { + response.StatusCode.Should().Be(tooManyRequestsStatusCode); + attempt.Should().Be(1); + } string expectedMessagePrefix = " " + tooManyRequestsStatusCode.ToString() + " " + TestUrl; - logger.Messages.Where(m => m.StartsWith(expectedMessagePrefix, StringComparison.Ordinal)).Count().Should().Be(2); + logger.Messages.Where(m => m.StartsWith(expectedMessagePrefix, StringComparison.Ordinal)).Count().Should().Be(retry429 ? 2 : 1); } private static TimeSpan GetRetryMinTime(int tries, TimeSpan retryDelay) @@ -553,13 +533,14 @@ private static TimeSpan GetRetryMinTime(int tries, TimeSpan retryDelay) return TimeSpan.FromTicks((tries - 1) * retryDelay.Ticks); } - private static TestEnvironmentVariableReader GetEnhancedHttpRetryEnvironmentVariables(bool? isEnabled = true, int? retryCount = MaxTries, int? delayMilliseconds = 0) + private static TestEnvironmentVariableReader GetEnhancedHttpRetryEnvironmentVariables(bool? isEnabled = true, int? retryCount = MaxTries, int? delayMilliseconds = 0, bool? retry429 = true) => new TestEnvironmentVariableReader( new Dictionary() { [EnhancedHttpRetryHelper.IsEnabledEnvironmentVariableName] = isEnabled?.ToString(), [EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = retryCount?.ToString(), - [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = delayMilliseconds?.ToString() + [EnhancedHttpRetryHelper.DelayInMillisecondsEnvironmentVariableName] = delayMilliseconds?.ToString(), + [EnhancedHttpRetryHelper.Retry429EnvironmentVariableName] = retry429?.ToString(), }); private class TestHandler : DelegatingHandler