parent
93bc8c4218
commit
3b36c9091a
@ -57,7 +57,7 @@ namespace Azure { namespace Core { namespace Http { namespace Policies {
|
||||
/**
|
||||
* @brief Maximum number of attempts to retry.
|
||||
*/
|
||||
int MaxRetries = 3;
|
||||
int32_t MaxRetries = 3;
|
||||
|
||||
/**
|
||||
* @brief Mimimum amount of time between retry attempts.
|
||||
@ -265,7 +265,21 @@ namespace Azure { namespace Core { namespace Http { namespace Policies {
|
||||
* @param context The context used to call send request.
|
||||
* @return A positive number indicating the current intent to send the request.
|
||||
*/
|
||||
static int GetRetryNumber(Context const& context);
|
||||
static int32_t GetRetryNumber(Context const& context);
|
||||
|
||||
protected:
|
||||
virtual bool ShouldRetryOnTransportFailure(
|
||||
RetryOptions const& retryOptions,
|
||||
int32_t attempt,
|
||||
std::chrono::milliseconds& retryAfter,
|
||||
double jitterFactor = -1) const;
|
||||
|
||||
virtual bool ShouldRetryOnResponse(
|
||||
RawResponse const& response,
|
||||
RetryOptions const& retryOptions,
|
||||
int32_t attempt,
|
||||
std::chrono::milliseconds& retryAfter,
|
||||
double jitterFactor = -1) const;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -46,36 +46,48 @@ inline std::string GetRequestLogMessage(LogOptions const& options, Request const
|
||||
<< requestUrl.GetUrlWithoutQuery();
|
||||
{
|
||||
auto encodedRequestQueryParams = requestUrl.GetQueryParameters();
|
||||
auto const& unencodedAllowedQueryParams = options.AllowedHttpQueryParameters;
|
||||
if (!encodedRequestQueryParams.empty() && !unencodedAllowedQueryParams.empty())
|
||||
{
|
||||
std::remove_const<std::remove_reference<decltype(unencodedAllowedQueryParams)>::type>::type
|
||||
encodedAllowedQueryParams;
|
||||
std::transform(
|
||||
unencodedAllowedQueryParams.begin(),
|
||||
unencodedAllowedQueryParams.end(),
|
||||
std::inserter(encodedAllowedQueryParams, encodedAllowedQueryParams.begin()),
|
||||
[](std::string const& s) { return Url::Encode(s); });
|
||||
|
||||
std::remove_const<std::remove_reference<decltype(encodedRequestQueryParams)>::type>::type
|
||||
encodedAllowedRequestQueryParams;
|
||||
for (auto const& encodedRequestQueryParam : encodedRequestQueryParams)
|
||||
std::remove_const<std::remove_reference<decltype(encodedRequestQueryParams)>::type>::type
|
||||
loggedQueryParams;
|
||||
|
||||
if (!encodedRequestQueryParams.empty())
|
||||
{
|
||||
auto const& unencodedAllowedQueryParams = options.AllowedHttpQueryParameters;
|
||||
if (!unencodedAllowedQueryParams.empty())
|
||||
{
|
||||
if (encodedRequestQueryParam.second.empty()
|
||||
|| (encodedAllowedQueryParams.find(encodedRequestQueryParam.first)
|
||||
!= encodedAllowedQueryParams.end()))
|
||||
std::remove_const<std::remove_reference<decltype(unencodedAllowedQueryParams)>::type>::type
|
||||
encodedAllowedQueryParams;
|
||||
std::transform(
|
||||
unencodedAllowedQueryParams.begin(),
|
||||
unencodedAllowedQueryParams.end(),
|
||||
std::inserter(encodedAllowedQueryParams, encodedAllowedQueryParams.begin()),
|
||||
[](std::string const& s) { return Url::Encode(s); });
|
||||
|
||||
for (auto const& encodedRequestQueryParam : encodedRequestQueryParams)
|
||||
{
|
||||
encodedAllowedRequestQueryParams.insert(encodedRequestQueryParam);
|
||||
if (encodedRequestQueryParam.second.empty()
|
||||
|| (encodedAllowedQueryParams.find(encodedRequestQueryParam.first)
|
||||
!= encodedAllowedQueryParams.end()))
|
||||
{
|
||||
loggedQueryParams.insert(encodedRequestQueryParam);
|
||||
}
|
||||
else
|
||||
{
|
||||
loggedQueryParams.insert(
|
||||
std::make_pair(encodedRequestQueryParam.first, RedactedPlaceholder));
|
||||
}
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto const& encodedRequestQueryParam : encodedRequestQueryParams)
|
||||
{
|
||||
encodedAllowedRequestQueryParams.insert(
|
||||
loggedQueryParams.insert(
|
||||
std::make_pair(encodedRequestQueryParam.first, RedactedPlaceholder));
|
||||
}
|
||||
}
|
||||
|
||||
log << Azure::Core::_detail::FormatEncodedUrlQueryParameters(
|
||||
encodedAllowedRequestQueryParams);
|
||||
log << Azure::Core::_detail::FormatEncodedUrlQueryParameters(loggedQueryParams);
|
||||
}
|
||||
}
|
||||
AppendHeaders(log, request.GetHeaders(), options.AllowedHttpHeaders);
|
||||
|
||||
@ -16,10 +16,7 @@ using namespace Azure::Core::Http::Policies;
|
||||
using namespace Azure::Core::Http::Policies::_internal;
|
||||
|
||||
namespace {
|
||||
typedef decltype(RetryOptions::RetryDelay) Delay;
|
||||
typedef decltype(RetryOptions::MaxRetries) RetryNumber;
|
||||
|
||||
bool GetResponseHeaderBasedDelay(RawResponse const& response, Delay& retryAfter)
|
||||
bool GetResponseHeaderBasedDelay(RawResponse const& response, std::chrono::milliseconds& retryAfter)
|
||||
{
|
||||
// Try to find retry-after headers. There are several of them possible.
|
||||
auto const& responseHeaders = response.GetHeaders();
|
||||
@ -56,97 +53,42 @@ bool GetResponseHeaderBasedDelay(RawResponse const& response, Delay& retryAfter)
|
||||
return false;
|
||||
}
|
||||
|
||||
Delay CalculateExponentialDelay(RetryOptions const& retryOptions, RetryNumber attempt)
|
||||
std::chrono::milliseconds CalculateExponentialDelay(
|
||||
RetryOptions const& retryOptions,
|
||||
int32_t attempt,
|
||||
double jitterFactor)
|
||||
{
|
||||
constexpr auto beforeLastBit = std::numeric_limits<RetryNumber>::digits
|
||||
- (std::numeric_limits<RetryNumber>::is_signed ? 1 : 0);
|
||||
if (jitterFactor < 0.8 || jitterFactor > 1.3)
|
||||
{
|
||||
// jitterFactor is a random double number in the range [0.8 .. 1.3]
|
||||
jitterFactor
|
||||
= 0.8 + ((static_cast<double>(static_cast<int32_t>(std::rand())) / RAND_MAX) * 0.5);
|
||||
}
|
||||
|
||||
constexpr auto beforeLastBit
|
||||
= std::numeric_limits<int32_t>::digits - (std::numeric_limits<int32_t>::is_signed ? 1 : 0);
|
||||
|
||||
// Scale exponentially: 1 x RetryDelay on 1st attempt, 2x on 2nd, 4x on 3rd, 8x on 4th ... all the
|
||||
// way up to std::numeric_limits<RetryNumber>::max() * RetryDelay.
|
||||
// way up to std::numeric_limits<int32_t>::max() * RetryDelay.
|
||||
auto exponentialRetryAfter = retryOptions.RetryDelay
|
||||
* ((attempt <= beforeLastBit) ? (1 << attempt) : std::numeric_limits<RetryNumber>::max());
|
||||
|
||||
// jitterFactor is a random double number in the range [0.8 .. 1.3)
|
||||
auto jitterFactor = 0.8 + (static_cast<double>(std::rand()) / RAND_MAX) * 0.5;
|
||||
* (((attempt - 1) <= beforeLastBit) ? (1 << (attempt - 1))
|
||||
: std::numeric_limits<int32_t>::max());
|
||||
|
||||
// Multiply exponentialRetryAfter by jitterFactor
|
||||
exponentialRetryAfter = Delay(static_cast<Delay::rep>(
|
||||
(std::chrono::duration<double, Delay::period>(exponentialRetryAfter) * jitterFactor)
|
||||
exponentialRetryAfter = std::chrono::milliseconds(static_cast<std::chrono::milliseconds::rep>(
|
||||
(std::chrono::duration<double, std::chrono::milliseconds::period>(exponentialRetryAfter)
|
||||
* jitterFactor)
|
||||
.count()));
|
||||
|
||||
return std::min(exponentialRetryAfter, retryOptions.MaxRetryDelay);
|
||||
}
|
||||
|
||||
bool WasLastAttempt(RetryOptions const& retryOptions, RetryNumber attempt)
|
||||
bool WasLastAttempt(RetryOptions const& retryOptions, int32_t attempt)
|
||||
{
|
||||
return attempt > retryOptions.MaxRetries;
|
||||
}
|
||||
|
||||
bool ShouldRetryOnTransportFailure(
|
||||
RetryOptions const& retryOptions,
|
||||
RetryNumber attempt,
|
||||
Delay& retryAfter)
|
||||
{
|
||||
// Are we out of retry attempts?
|
||||
if (WasLastAttempt(retryOptions, attempt))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
retryAfter = CalculateExponentialDelay(retryOptions, attempt);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShouldRetryOnResponse(
|
||||
RawResponse const& response,
|
||||
RetryOptions const& retryOptions,
|
||||
RetryNumber attempt,
|
||||
Delay& retryAfter)
|
||||
{
|
||||
using Azure::Core::Diagnostics::Logger;
|
||||
using Azure::Core::Diagnostics::_internal::Log;
|
||||
// Are we out of retry attempts?
|
||||
if (WasLastAttempt(retryOptions, attempt))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Should we retry on the given response retry code?
|
||||
{
|
||||
auto const& statusCodes = retryOptions.StatusCodes;
|
||||
auto const sc = response.GetStatusCode();
|
||||
if (statusCodes.find(sc) == statusCodes.end())
|
||||
{
|
||||
if (Log::ShouldWrite(Logger::Level::Warning))
|
||||
{
|
||||
Log::Write(
|
||||
Logger::Level::Warning,
|
||||
std::string("HTTP status code ") + std::to_string(static_cast<int>(sc))
|
||||
+ " won't be retried.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (Log::ShouldWrite(Logger::Level::Informational))
|
||||
{
|
||||
Log::Write(
|
||||
Logger::Level::Informational,
|
||||
std::string("HTTP status code ") + std::to_string(static_cast<int>(sc))
|
||||
+ " will be retried.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!GetResponseHeaderBasedDelay(response, retryAfter))
|
||||
{
|
||||
retryAfter = CalculateExponentialDelay(retryOptions, attempt);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
Context::Key const RetryKey;
|
||||
}
|
||||
Context::Key const RetryKey;
|
||||
|
||||
/**
|
||||
* @brief Creates a new #Context node from \p parent with the information about the retrying while
|
||||
@ -155,7 +97,7 @@ namespace {
|
||||
* @param parent The parent context for the new created.
|
||||
* @return Context with information about retry counter.
|
||||
*/
|
||||
Context inline CreateRetryContext(Context const& parent)
|
||||
inline Context CreateRetryContext(Context const& parent)
|
||||
{
|
||||
// First try as default
|
||||
int retryCount = 0;
|
||||
@ -167,7 +109,7 @@ Context inline CreateRetryContext(Context const& parent)
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int RetryPolicy::GetRetryNumber(Context const& context)
|
||||
int32_t RetryPolicy::GetRetryNumber(Context const& context)
|
||||
{
|
||||
if (!context.HasKey(RetryKey))
|
||||
{
|
||||
@ -178,7 +120,7 @@ int RetryPolicy::GetRetryNumber(Context const& context)
|
||||
// ...
|
||||
return -1;
|
||||
}
|
||||
return context.GetValue<int>(RetryKey);
|
||||
return context.GetValue<int32_t>(RetryKey);
|
||||
}
|
||||
|
||||
std::unique_ptr<RawResponse> RetryPolicy::Send(
|
||||
@ -191,9 +133,9 @@ std::unique_ptr<RawResponse> RetryPolicy::Send(
|
||||
|
||||
auto retryContext = CreateRetryContext(ctx);
|
||||
|
||||
for (RetryNumber attempt = 1;; ++attempt)
|
||||
for (int32_t attempt = 1;; ++attempt)
|
||||
{
|
||||
Delay retryAfter{};
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
request.StartTry();
|
||||
// creates a copy of original query parameters from request
|
||||
auto originalQueryParameters = request.GetUrl().GetQueryParameters();
|
||||
@ -249,3 +191,68 @@ std::unique_ptr<RawResponse> RetryPolicy::Send(
|
||||
retryContext = CreateRetryContext(retryContext);
|
||||
}
|
||||
}
|
||||
|
||||
bool RetryPolicy::ShouldRetryOnTransportFailure(
|
||||
RetryOptions const& retryOptions,
|
||||
int32_t attempt,
|
||||
std::chrono::milliseconds& retryAfter,
|
||||
double jitterFactor) const
|
||||
{
|
||||
// Are we out of retry attempts?
|
||||
if (WasLastAttempt(retryOptions, attempt))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
retryAfter = CalculateExponentialDelay(retryOptions, attempt, jitterFactor);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RetryPolicy::ShouldRetryOnResponse(
|
||||
RawResponse const& response,
|
||||
RetryOptions const& retryOptions,
|
||||
int32_t attempt,
|
||||
std::chrono::milliseconds& retryAfter,
|
||||
double jitterFactor) const
|
||||
{
|
||||
using Azure::Core::Diagnostics::Logger;
|
||||
using Azure::Core::Diagnostics::_internal::Log;
|
||||
|
||||
// Are we out of retry attempts?
|
||||
if (WasLastAttempt(retryOptions, attempt))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Should we retry on the given response retry code?
|
||||
{
|
||||
auto const& statusCodes = retryOptions.StatusCodes;
|
||||
auto const sc = response.GetStatusCode();
|
||||
if (statusCodes.find(sc) == statusCodes.end())
|
||||
{
|
||||
if (Log::ShouldWrite(Logger::Level::Warning))
|
||||
{
|
||||
Log::Write(
|
||||
Logger::Level::Warning,
|
||||
std::string("HTTP status code ") + std::to_string(static_cast<int>(sc))
|
||||
+ " won't be retried.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (Log::ShouldWrite(Logger::Level::Informational))
|
||||
{
|
||||
Log::Write(
|
||||
Logger::Level::Informational,
|
||||
std::string("HTTP status code ") + std::to_string(static_cast<int>(sc))
|
||||
+ " will be retried.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!GetResponseHeaderBasedDelay(response, retryAfter))
|
||||
{
|
||||
retryAfter = CalculateExponentialDelay(retryOptions, attempt, jitterFactor);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ include(GoogleTest)
|
||||
add_executable (
|
||||
azure-core-test
|
||||
base64.cpp
|
||||
bearer_token_authentication_policy.cpp
|
||||
bodystream.cpp
|
||||
case_insensitive_containers.cpp
|
||||
client_options.cpp
|
||||
@ -41,6 +42,7 @@ add_executable (
|
||||
etag.cpp
|
||||
http.cpp
|
||||
json.cpp
|
||||
log_policy.cpp
|
||||
logging.cpp
|
||||
macro_guard.cpp
|
||||
main.cpp
|
||||
@ -52,7 +54,9 @@ add_executable (
|
||||
operation_status.cpp
|
||||
pipeline.cpp
|
||||
policy.cpp
|
||||
request_id_policy.cpp
|
||||
response_t.cpp
|
||||
retry_policy.cpp
|
||||
simplified_header.cpp
|
||||
string.cpp
|
||||
telemetry_policy.cpp
|
||||
|
||||
@ -0,0 +1,204 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <azure/core/http/policies/policy.hpp>
|
||||
#include <azure/core/internal/http/pipeline.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace {
|
||||
class TestTokenCredential : public Azure::Core::Credentials::TokenCredential {
|
||||
private:
|
||||
std::shared_ptr<Azure::Core::Credentials::AccessToken const> m_accessToken;
|
||||
|
||||
public:
|
||||
explicit TestTokenCredential(
|
||||
std::shared_ptr<Azure::Core::Credentials::AccessToken const> accessToken)
|
||||
: m_accessToken(accessToken)
|
||||
{
|
||||
}
|
||||
|
||||
Azure::Core::Credentials::AccessToken GetToken(
|
||||
Azure::Core::Credentials::TokenRequestContext const&,
|
||||
Azure::Core::Context const&) const override
|
||||
{
|
||||
return *m_accessToken;
|
||||
}
|
||||
};
|
||||
|
||||
class TestTransportPolicy : public Azure::Core::Http::Policies::HttpPolicy {
|
||||
public:
|
||||
std::unique_ptr<Azure::Core::Http::RawResponse> Send(
|
||||
Azure::Core::Http::Request&,
|
||||
Azure::Core::Http::Policies::NextHttpPolicy,
|
||||
Azure::Core::Context const&) const override
|
||||
{
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
std::unique_ptr<HttpPolicy> Clone() const override
|
||||
{
|
||||
return std::make_unique<TestTransportPolicy>(*this);
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(BearerTokenAuthenticationPolicy, InitialGet)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
auto accessToken = std::make_shared<Azure::Core::Credentials::AccessToken>();
|
||||
|
||||
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
|
||||
|
||||
policies.emplace_back(
|
||||
std::make_unique<Azure::Core::Http::Policies::_internal::BearerTokenAuthenticationPolicy>(
|
||||
std::make_shared<TestTokenCredential>(accessToken),
|
||||
Azure::Core::Credentials::TokenRequestContext{{"https://microsoft.com/.default"}}));
|
||||
|
||||
policies.emplace_back(std::make_unique<TestTransportPolicy>());
|
||||
|
||||
Azure::Core::Http::_internal::HttpPipeline pipeline(policies);
|
||||
|
||||
{
|
||||
Azure::Core::Http::Request request(
|
||||
Azure::Core::Http::HttpMethod::Get, Azure::Core::Url("https://www.azure.com"));
|
||||
|
||||
*accessToken = {"ACCESSTOKEN1", std::chrono::system_clock::now() + 1h};
|
||||
|
||||
pipeline.Send(request, Azure::Core::Context());
|
||||
|
||||
{
|
||||
auto const headers = request.GetHeaders();
|
||||
auto const authHeader = headers.find("authorization");
|
||||
EXPECT_NE(authHeader, headers.end());
|
||||
EXPECT_EQ(authHeader->second, "Bearer ACCESSTOKEN1");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BearerTokenAuthenticationPolicy, ReuseWhileValid)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
auto accessToken = std::make_shared<Azure::Core::Credentials::AccessToken>();
|
||||
|
||||
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
|
||||
|
||||
policies.emplace_back(
|
||||
std::make_unique<Azure::Core::Http::Policies::_internal::BearerTokenAuthenticationPolicy>(
|
||||
std::make_shared<TestTokenCredential>(accessToken),
|
||||
Azure::Core::Credentials::TokenRequestContext{{"https://microsoft.com/.default"}}));
|
||||
|
||||
policies.emplace_back(std::make_unique<TestTransportPolicy>());
|
||||
|
||||
Azure::Core::Http::_internal::HttpPipeline pipeline(policies);
|
||||
|
||||
{
|
||||
Azure::Core::Http::Request request(
|
||||
Azure::Core::Http::HttpMethod::Get, Azure::Core::Url("https://www.azure.com"));
|
||||
|
||||
*accessToken = {"ACCESSTOKEN1", std::chrono::system_clock::now() + 5min};
|
||||
|
||||
pipeline.Send(request, Azure::Core::Context());
|
||||
}
|
||||
|
||||
{
|
||||
Azure::Core::Http::Request request(
|
||||
Azure::Core::Http::HttpMethod::Get, Azure::Core::Url("https://www.azure.com"));
|
||||
|
||||
*accessToken = {"ACCESSTOKEN2", std::chrono::system_clock::now() + 1h};
|
||||
|
||||
pipeline.Send(request, Azure::Core::Context());
|
||||
|
||||
{
|
||||
auto const headers = request.GetHeaders();
|
||||
auto const authHeader = headers.find("authorization");
|
||||
EXPECT_NE(authHeader, headers.end());
|
||||
EXPECT_EQ(authHeader->second, "Bearer ACCESSTOKEN1");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BearerTokenAuthenticationPolicy, RefreshNearExpiry)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
auto accessToken = std::make_shared<Azure::Core::Credentials::AccessToken>();
|
||||
|
||||
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
|
||||
|
||||
policies.emplace_back(
|
||||
std::make_unique<Azure::Core::Http::Policies::_internal::BearerTokenAuthenticationPolicy>(
|
||||
std::make_shared<TestTokenCredential>(accessToken),
|
||||
Azure::Core::Credentials::TokenRequestContext{{"https://microsoft.com/.default"}}));
|
||||
|
||||
policies.emplace_back(std::make_unique<TestTransportPolicy>());
|
||||
|
||||
Azure::Core::Http::_internal::HttpPipeline pipeline(policies);
|
||||
|
||||
{
|
||||
Azure::Core::Http::Request request(
|
||||
Azure::Core::Http::HttpMethod::Get, Azure::Core::Url("https://www.azure.com"));
|
||||
|
||||
*accessToken = {"ACCESSTOKEN1", std::chrono::system_clock::now() + 2min};
|
||||
|
||||
pipeline.Send(request, Azure::Core::Context());
|
||||
}
|
||||
|
||||
{
|
||||
Azure::Core::Http::Request request(
|
||||
Azure::Core::Http::HttpMethod::Get, Azure::Core::Url("https://www.azure.com"));
|
||||
|
||||
*accessToken = {"ACCESSTOKEN2", std::chrono::system_clock::now() + 1h};
|
||||
|
||||
pipeline.Send(request, Azure::Core::Context());
|
||||
|
||||
{
|
||||
auto const headers = request.GetHeaders();
|
||||
auto const authHeader = headers.find("authorization");
|
||||
EXPECT_NE(authHeader, headers.end());
|
||||
EXPECT_EQ(authHeader->second, "Bearer ACCESSTOKEN2");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BearerTokenAuthenticationPolicy, RefreshAfterExpiry)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
auto accessToken = std::make_shared<Azure::Core::Credentials::AccessToken>();
|
||||
|
||||
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
|
||||
|
||||
policies.emplace_back(
|
||||
std::make_unique<Azure::Core::Http::Policies::_internal::BearerTokenAuthenticationPolicy>(
|
||||
std::make_shared<TestTokenCredential>(accessToken),
|
||||
Azure::Core::Credentials::TokenRequestContext{{"https://microsoft.com/.default"}}));
|
||||
|
||||
policies.emplace_back(std::make_unique<TestTransportPolicy>());
|
||||
|
||||
Azure::Core::Http::_internal::HttpPipeline pipeline(policies);
|
||||
|
||||
{
|
||||
Azure::Core::Http::Request request(
|
||||
Azure::Core::Http::HttpMethod::Get, Azure::Core::Url("https://www.azure.com"));
|
||||
|
||||
*accessToken = {"ACCESSTOKEN1", std::chrono::system_clock::now()};
|
||||
|
||||
pipeline.Send(request, Azure::Core::Context());
|
||||
}
|
||||
|
||||
{
|
||||
Azure::Core::Http::Request request(
|
||||
Azure::Core::Http::HttpMethod::Get, Azure::Core::Url("https://www.azure.com"));
|
||||
|
||||
*accessToken = {"ACCESSTOKEN2", std::chrono::system_clock::now() + 1h};
|
||||
|
||||
pipeline.Send(request, Azure::Core::Context());
|
||||
|
||||
{
|
||||
auto const headers = request.GetHeaders();
|
||||
auto const authHeader = headers.find("authorization");
|
||||
EXPECT_NE(authHeader, headers.end());
|
||||
EXPECT_EQ(authHeader->second, "Bearer ACCESSTOKEN2");
|
||||
}
|
||||
}
|
||||
}
|
||||
263
sdk/core/azure-core/test/ut/log_policy.cpp
Normal file
263
sdk/core/azure-core/test/ut/log_policy.cpp
Normal file
@ -0,0 +1,263 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <azure/core/diagnostics/logger.hpp>
|
||||
#include <azure/core/http/policies/policy.hpp>
|
||||
#include <azure/core/internal/http/pipeline.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using Azure::Core::Diagnostics::Logger;
|
||||
using Azure::Core::Http::Policies::LogOptions;
|
||||
|
||||
namespace {
|
||||
void SendRequest(LogOptions const& logOptions)
|
||||
{
|
||||
using namespace Azure::Core;
|
||||
using namespace Azure::Core::IO;
|
||||
using namespace Azure::Core::Http;
|
||||
using namespace Azure::Core::Http::_internal;
|
||||
using namespace Azure::Core::Http::Policies;
|
||||
using namespace Azure::Core::Http::Policies::_internal;
|
||||
|
||||
class TestTransportPolicy : public HttpPolicy {
|
||||
public:
|
||||
std::unique_ptr<HttpPolicy> Clone() const override
|
||||
{
|
||||
return std::make_unique<TestTransportPolicy>(*this);
|
||||
}
|
||||
|
||||
std::unique_ptr<RawResponse> Send(Request&, NextHttpPolicy, Context const&) const override
|
||||
{
|
||||
static constexpr uint8_t const responseBody[] = "Response Body";
|
||||
static constexpr uint8_t const responseBodyStream[] = "Request Body Stream";
|
||||
|
||||
auto response = std::make_unique<RawResponse>(1, 1, HttpStatusCode::Ok, "OKAY");
|
||||
|
||||
response->SetBody(std::vector<uint8_t>(responseBody, responseBody + sizeof(responseBody)));
|
||||
|
||||
response->SetBodyStream(
|
||||
std::make_unique<MemoryBodyStream>(responseBodyStream, sizeof(responseBodyStream) - 1));
|
||||
|
||||
return response;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr uint8_t const requestBodyStream[] = "Request Body Stream";
|
||||
auto const bodyStream
|
||||
= std::make_unique<MemoryBodyStream>(requestBodyStream, sizeof(requestBodyStream) - 1);
|
||||
|
||||
Request request(
|
||||
HttpMethod::Get,
|
||||
Url("https://"
|
||||
"www.microsoft.com"
|
||||
"?qparam1=qVal1"
|
||||
"&Qparam2=Qval2"
|
||||
"&qParam3=qval3"
|
||||
"&qparam%204=qval%204"
|
||||
"&qparam%25204=QVAL%25204"),
|
||||
bodyStream.get());
|
||||
|
||||
request.SetHeader("hEaDeR1", "HvAlUe1");
|
||||
request.SetHeader("HeAdEr2", "hVaLuE2");
|
||||
|
||||
{
|
||||
std::vector<std::unique_ptr<HttpPolicy>> policies;
|
||||
|
||||
policies.emplace_back(std::make_unique<LogPolicy>(logOptions));
|
||||
policies.emplace_back(std::make_unique<TestTransportPolicy>());
|
||||
|
||||
HttpPipeline(policies).Send(request, Azure::Core::Context());
|
||||
}
|
||||
}
|
||||
|
||||
class TestLogger {
|
||||
private:
|
||||
static void Deinitialize()
|
||||
{
|
||||
Logger::SetLevel(Logger::Level::Error);
|
||||
Logger::SetListener(nullptr);
|
||||
}
|
||||
|
||||
TestLogger(TestLogger const&) = delete;
|
||||
void operator=(TestLogger const&) = delete;
|
||||
|
||||
public:
|
||||
struct LogMessage
|
||||
{
|
||||
Logger::Level Level;
|
||||
std::string Message;
|
||||
};
|
||||
|
||||
std::vector<LogMessage> Entries;
|
||||
|
||||
~TestLogger() { Deinitialize(); }
|
||||
|
||||
TestLogger()
|
||||
try
|
||||
{
|
||||
Logger::SetLevel(Logger::Level::Verbose);
|
||||
Logger::SetListener([&](auto lvl, auto msg) { Entries.push_back({lvl, msg}); });
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Deinitialize();
|
||||
throw;
|
||||
}
|
||||
};
|
||||
|
||||
bool StartsWith(std::string const& str, std::string const& with)
|
||||
{
|
||||
return str.substr(0, with.size()) == with;
|
||||
}
|
||||
|
||||
bool EndsWith(std::string const& str, std::string const& with)
|
||||
{
|
||||
if (str.size() < with.size())
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
return str.substr(str.size() - with.size(), with.size()) == with;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(LogPolicy, Default)
|
||||
{
|
||||
TestLogger const Log;
|
||||
SendRequest(LogOptions());
|
||||
|
||||
EXPECT_EQ(Log.Entries.size(), 2);
|
||||
|
||||
auto const entry1 = Log.Entries.at(0);
|
||||
auto const entry2 = Log.Entries.at(1);
|
||||
|
||||
EXPECT_EQ(entry1.Level, Logger::Level::Informational);
|
||||
EXPECT_EQ(entry2.Level, Logger::Level::Informational);
|
||||
|
||||
EXPECT_EQ(
|
||||
entry1.Message,
|
||||
"HTTP Request : GET https://www.microsoft.com"
|
||||
"?Qparam2=REDACTED"
|
||||
"&qParam3=REDACTED"
|
||||
"&qparam%204=REDACTED"
|
||||
"&qparam%25204=REDACTED"
|
||||
"&qparam1=REDACTED"
|
||||
"\nheader1 : REDACTED"
|
||||
"\nheader2 : REDACTED");
|
||||
|
||||
EXPECT_TRUE(StartsWith(entry2.Message, "HTTP Response ("));
|
||||
EXPECT_TRUE(EndsWith(entry2.Message, "ms) : 200 OKAY"));
|
||||
}
|
||||
|
||||
TEST(LogPolicy, Headers)
|
||||
{
|
||||
TestLogger const Log;
|
||||
SendRequest(LogOptions({{}, {"HeAder1", "heaDer3"}}));
|
||||
|
||||
EXPECT_EQ(Log.Entries.size(), 2);
|
||||
|
||||
auto const entry1 = Log.Entries.at(0);
|
||||
auto const entry2 = Log.Entries.at(1);
|
||||
|
||||
EXPECT_EQ(entry1.Level, Logger::Level::Informational);
|
||||
EXPECT_EQ(entry2.Level, Logger::Level::Informational);
|
||||
|
||||
EXPECT_EQ(
|
||||
entry1.Message,
|
||||
"HTTP Request : GET https://www.microsoft.com"
|
||||
"?Qparam2=REDACTED"
|
||||
"&qParam3=REDACTED"
|
||||
"&qparam%204=REDACTED"
|
||||
"&qparam%25204=REDACTED"
|
||||
"&qparam1=REDACTED"
|
||||
"\nheader1 : HvAlUe1"
|
||||
"\nheader2 : REDACTED");
|
||||
|
||||
EXPECT_TRUE(StartsWith(entry2.Message, "HTTP Response ("));
|
||||
EXPECT_TRUE(EndsWith(entry2.Message, "ms) : 200 OKAY"));
|
||||
}
|
||||
|
||||
TEST(LogPolicy, QueryParams)
|
||||
{
|
||||
TestLogger const Log;
|
||||
SendRequest(LogOptions({{"qparam1", "qparam2", "qParam3"}, {}}));
|
||||
|
||||
EXPECT_EQ(Log.Entries.size(), 2);
|
||||
|
||||
auto const entry1 = Log.Entries.at(0);
|
||||
auto const entry2 = Log.Entries.at(1);
|
||||
|
||||
EXPECT_EQ(entry1.Level, Logger::Level::Informational);
|
||||
EXPECT_EQ(entry2.Level, Logger::Level::Informational);
|
||||
|
||||
EXPECT_EQ(
|
||||
entry1.Message,
|
||||
"HTTP Request : GET https://www.microsoft.com"
|
||||
"?Qparam2=REDACTED"
|
||||
"&qParam3=qval3"
|
||||
"&qparam%204=REDACTED"
|
||||
"&qparam%25204=REDACTED"
|
||||
"&qparam1=qVal1"
|
||||
"\nheader1 : REDACTED"
|
||||
"\nheader2 : REDACTED");
|
||||
|
||||
EXPECT_TRUE(StartsWith(entry2.Message, "HTTP Response ("));
|
||||
EXPECT_TRUE(EndsWith(entry2.Message, "ms) : 200 OKAY"));
|
||||
}
|
||||
|
||||
TEST(LogPolicy, QueryParamsUnencoded)
|
||||
{
|
||||
TestLogger const Log;
|
||||
SendRequest(LogOptions({{"qparam 4"}, {}}));
|
||||
|
||||
EXPECT_EQ(Log.Entries.size(), 2);
|
||||
|
||||
auto const entry1 = Log.Entries.at(0);
|
||||
auto const entry2 = Log.Entries.at(1);
|
||||
|
||||
EXPECT_EQ(entry1.Level, Logger::Level::Informational);
|
||||
EXPECT_EQ(entry2.Level, Logger::Level::Informational);
|
||||
|
||||
EXPECT_EQ(
|
||||
entry1.Message,
|
||||
"HTTP Request : GET https://www.microsoft.com"
|
||||
"?Qparam2=REDACTED"
|
||||
"&qParam3=REDACTED"
|
||||
"&qparam%204=qval%204"
|
||||
"&qparam%25204=REDACTED"
|
||||
"&qparam1=REDACTED"
|
||||
"\nheader1 : REDACTED"
|
||||
"\nheader2 : REDACTED");
|
||||
|
||||
EXPECT_TRUE(StartsWith(entry2.Message, "HTTP Response ("));
|
||||
EXPECT_TRUE(EndsWith(entry2.Message, "ms) : 200 OKAY"));
|
||||
}
|
||||
|
||||
TEST(LogPolicy, QueryParamsEncoded)
|
||||
{
|
||||
TestLogger const Log;
|
||||
SendRequest(LogOptions({{"qparam%204"}, {}}));
|
||||
|
||||
EXPECT_EQ(Log.Entries.size(), 2);
|
||||
|
||||
auto const entry1 = Log.Entries.at(0);
|
||||
auto const entry2 = Log.Entries.at(1);
|
||||
|
||||
EXPECT_EQ(entry1.Level, Logger::Level::Informational);
|
||||
EXPECT_EQ(entry2.Level, Logger::Level::Informational);
|
||||
|
||||
EXPECT_EQ(
|
||||
entry1.Message,
|
||||
"HTTP Request : GET https://www.microsoft.com"
|
||||
"?Qparam2=REDACTED"
|
||||
"&qParam3=REDACTED"
|
||||
"&qparam%204=REDACTED"
|
||||
"&qparam%25204=QVAL%25204"
|
||||
"&qparam1=REDACTED"
|
||||
"\nheader1 : REDACTED"
|
||||
"\nheader2 : REDACTED");
|
||||
|
||||
EXPECT_TRUE(StartsWith(entry2.Message, "HTTP Response ("));
|
||||
EXPECT_TRUE(EndsWith(entry2.Message, "ms) : 200 OKAY"));
|
||||
}
|
||||
94
sdk/core/azure-core/test/ut/request_id_policy.cpp
Normal file
94
sdk/core/azure-core/test/ut/request_id_policy.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <azure/core/http/policies/policy.hpp>
|
||||
#include <azure/core/internal/http/pipeline.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace Azure::Core;
|
||||
using namespace Azure::Core::Http;
|
||||
using namespace Azure::Core::Http::Policies;
|
||||
using namespace Azure::Core::Http::Policies::_internal;
|
||||
|
||||
namespace {
|
||||
class NoOpPolicy : public HttpPolicy {
|
||||
public:
|
||||
std::unique_ptr<HttpPolicy> Clone() const override { return std::make_unique<NoOpPolicy>(*this); }
|
||||
|
||||
std::unique_ptr<RawResponse> Send(Request&, NextHttpPolicy, Azure::Core::Context const&)
|
||||
const override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr char const* RequestIdHeaderName = "x-ms-client-request-id";
|
||||
} // namespace
|
||||
|
||||
TEST(RequestIdPolicy, Basic)
|
||||
{
|
||||
Request request(HttpMethod::Get, Url("https://www.microsoft.com"));
|
||||
|
||||
{
|
||||
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
|
||||
|
||||
policies.emplace_back(std::make_unique<RequestIdPolicy>());
|
||||
policies.emplace_back(std::make_unique<NoOpPolicy>());
|
||||
|
||||
Azure::Core::Http::_internal::HttpPipeline(policies).Send(request, Azure::Core::Context());
|
||||
}
|
||||
|
||||
auto const headers = request.GetHeaders();
|
||||
auto const requestIdHeader = headers.find(RequestIdHeaderName);
|
||||
EXPECT_NE(requestIdHeader, headers.end());
|
||||
EXPECT_EQ(requestIdHeader->second.length(), 36);
|
||||
}
|
||||
|
||||
TEST(RequestIdPolicy, Unique)
|
||||
{
|
||||
std::string guid1;
|
||||
std::string guid2;
|
||||
|
||||
{
|
||||
Request request(HttpMethod::Get, Url("https://www.microsoft.com"));
|
||||
|
||||
{
|
||||
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
|
||||
|
||||
policies.emplace_back(std::make_unique<RequestIdPolicy>());
|
||||
policies.emplace_back(std::make_unique<NoOpPolicy>());
|
||||
|
||||
Azure::Core::Http::_internal::HttpPipeline(policies).Send(request, Azure::Core::Context());
|
||||
}
|
||||
|
||||
auto const headers = request.GetHeaders();
|
||||
auto const requestIdHeader = headers.find(RequestIdHeaderName);
|
||||
EXPECT_NE(requestIdHeader, headers.end());
|
||||
|
||||
guid1 = requestIdHeader->second;
|
||||
EXPECT_EQ(guid1.length(), 36);
|
||||
}
|
||||
|
||||
{
|
||||
Request request(HttpMethod::Get, Url("https://www.microsoft.com"));
|
||||
|
||||
{
|
||||
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
|
||||
|
||||
policies.emplace_back(std::make_unique<RequestIdPolicy>());
|
||||
policies.emplace_back(std::make_unique<NoOpPolicy>());
|
||||
|
||||
Azure::Core::Http::_internal::HttpPipeline(policies).Send(request, Azure::Core::Context());
|
||||
}
|
||||
|
||||
auto const headers = request.GetHeaders();
|
||||
auto const requestIdHeader = headers.find(RequestIdHeaderName);
|
||||
EXPECT_NE(requestIdHeader, headers.end());
|
||||
|
||||
guid2 = requestIdHeader->second;
|
||||
EXPECT_EQ(guid2.length(), 36);
|
||||
}
|
||||
|
||||
EXPECT_NE(guid1, guid2);
|
||||
}
|
||||
719
sdk/core/azure-core/test/ut/retry_policy.cpp
Normal file
719
sdk/core/azure-core/test/ut/retry_policy.cpp
Normal file
@ -0,0 +1,719 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "azure/core/http/policies/policy.hpp"
|
||||
#include "azure/core/internal/http/pipeline.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
using namespace Azure::Core::Http;
|
||||
using namespace Azure::Core::Http::Policies;
|
||||
using namespace Azure::Core::Http::Policies::_internal;
|
||||
|
||||
namespace {
|
||||
class TestTransportPolicy : public HttpPolicy {
|
||||
private:
|
||||
std::function<std::unique_ptr<RawResponse>()> m_send;
|
||||
|
||||
public:
|
||||
TestTransportPolicy(std::function<std::unique_ptr<RawResponse>()> send) : m_send(send) {}
|
||||
|
||||
std::unique_ptr<Azure::Core::Http::RawResponse> Send(
|
||||
Request&,
|
||||
NextHttpPolicy,
|
||||
Azure::Core::Context const&) const override
|
||||
{
|
||||
return m_send();
|
||||
};
|
||||
|
||||
std::unique_ptr<HttpPolicy> Clone() const override
|
||||
{
|
||||
return std::make_unique<TestTransportPolicy>(*this);
|
||||
};
|
||||
};
|
||||
|
||||
class RetryPolicyTest : public RetryPolicy {
|
||||
private:
|
||||
std::function<bool(RetryOptions const&, int32_t, std::chrono::milliseconds&, double)>
|
||||
m_shouldRetryOnTransportFailure;
|
||||
|
||||
std::function<
|
||||
bool(RawResponse const&, RetryOptions const&, int32_t, std::chrono::milliseconds&, double)>
|
||||
m_shouldRetryOnResponse;
|
||||
|
||||
public:
|
||||
RetryPolicyTest(
|
||||
RetryOptions const& retryOptions,
|
||||
std::function<bool(RetryOptions const&, int32_t, std::chrono::milliseconds&, double)>
|
||||
shouldRetryOnTransportFailure,
|
||||
std::function<bool(
|
||||
RawResponse const&,
|
||||
RetryOptions const&,
|
||||
int32_t,
|
||||
std::chrono::milliseconds&,
|
||||
double)> shouldRetryOnResponse)
|
||||
: RetryPolicy(retryOptions), m_shouldRetryOnTransportFailure(shouldRetryOnTransportFailure),
|
||||
m_shouldRetryOnResponse(shouldRetryOnResponse)
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<HttpPolicy> Clone() const override
|
||||
{
|
||||
return std::make_unique<RetryPolicyTest>(*this);
|
||||
};
|
||||
|
||||
protected:
|
||||
bool ShouldRetryOnTransportFailure(
|
||||
RetryOptions const& retryOptions,
|
||||
int32_t attempt,
|
||||
std::chrono::milliseconds& retryAfter,
|
||||
double jitterFactor) const override
|
||||
{
|
||||
return m_shouldRetryOnTransportFailure(retryOptions, attempt, retryAfter, jitterFactor);
|
||||
}
|
||||
|
||||
bool ShouldRetryOnResponse(
|
||||
RawResponse const& response,
|
||||
RetryOptions const& retryOptions,
|
||||
int32_t attempt,
|
||||
std::chrono::milliseconds& retryAfter,
|
||||
double jitterFactor) const override
|
||||
{
|
||||
return m_shouldRetryOnResponse(response, retryOptions, attempt, retryAfter, jitterFactor);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST(RetryPolicy, ShouldRetryOnResponse)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
RetryOptions const retryOptions{5, 10s, 5min, {HttpStatusCode::Ok}};
|
||||
|
||||
RawResponse const* responsePtrSent = nullptr;
|
||||
|
||||
RawResponse const* responsePtrReceived = nullptr;
|
||||
RetryOptions retryOptionsReceived{0, 0ms, 0ms, {}};
|
||||
int32_t attemptReceived = -1234;
|
||||
double jitterReceived = -5678;
|
||||
|
||||
int onTransportFailureInvoked = 0;
|
||||
int onResponseInvoked = 0;
|
||||
|
||||
{
|
||||
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
|
||||
policies.emplace_back(std::make_unique<RetryPolicyTest>(
|
||||
retryOptions,
|
||||
[&](auto options, auto attempt, auto, auto jitter) {
|
||||
++onTransportFailureInvoked;
|
||||
retryOptionsReceived = options;
|
||||
attemptReceived = attempt;
|
||||
jitterReceived = jitter;
|
||||
|
||||
return false;
|
||||
},
|
||||
[&](RawResponse const& response, auto options, auto attempt, auto, auto jitter) {
|
||||
++onResponseInvoked;
|
||||
responsePtrReceived = &response;
|
||||
retryOptionsReceived = options;
|
||||
attemptReceived = attempt;
|
||||
jitterReceived = jitter;
|
||||
|
||||
return false;
|
||||
}));
|
||||
|
||||
policies.emplace_back(std::make_unique<TestTransportPolicy>([&]() {
|
||||
auto response = std::make_unique<RawResponse>(1, 1, HttpStatusCode::Ok, "Test");
|
||||
|
||||
responsePtrSent = response.get();
|
||||
|
||||
return response;
|
||||
}));
|
||||
|
||||
Azure::Core::Http::_internal::HttpPipeline pipeline(policies);
|
||||
|
||||
Request request(HttpMethod::Get, Azure::Core::Url("https://www.microsoft.com"));
|
||||
pipeline.Send(request, Azure::Core::Context());
|
||||
}
|
||||
|
||||
EXPECT_EQ(onTransportFailureInvoked, 0);
|
||||
EXPECT_EQ(onResponseInvoked, 1);
|
||||
|
||||
EXPECT_NE(responsePtrSent, nullptr);
|
||||
EXPECT_EQ(responsePtrSent, responsePtrReceived);
|
||||
|
||||
EXPECT_EQ(retryOptionsReceived.MaxRetries, retryOptions.MaxRetries);
|
||||
EXPECT_EQ(retryOptionsReceived.RetryDelay, retryOptions.RetryDelay);
|
||||
EXPECT_EQ(retryOptionsReceived.MaxRetryDelay, retryOptions.MaxRetryDelay);
|
||||
EXPECT_EQ(retryOptionsReceived.StatusCodes, retryOptions.StatusCodes);
|
||||
|
||||
EXPECT_EQ(attemptReceived, 1);
|
||||
EXPECT_EQ(jitterReceived, -1);
|
||||
|
||||
// 3 attempts
|
||||
responsePtrSent = nullptr;
|
||||
|
||||
responsePtrReceived = nullptr;
|
||||
retryOptionsReceived = RetryOptions{0, 0ms, 0ms, {}};
|
||||
attemptReceived = -1234;
|
||||
jitterReceived = -5678;
|
||||
|
||||
onTransportFailureInvoked = 0;
|
||||
onResponseInvoked = 0;
|
||||
|
||||
{
|
||||
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
|
||||
policies.emplace_back(std::make_unique<RetryPolicyTest>(
|
||||
retryOptions,
|
||||
[&](auto options, auto attempt, auto, auto jitter) {
|
||||
++onTransportFailureInvoked;
|
||||
retryOptionsReceived = options;
|
||||
attemptReceived = attempt;
|
||||
jitterReceived = jitter;
|
||||
|
||||
return false;
|
||||
},
|
||||
[&](RawResponse const& response, auto options, auto attempt, auto retryAfter, auto jitter) {
|
||||
++onResponseInvoked;
|
||||
responsePtrReceived = &response;
|
||||
retryOptionsReceived = options;
|
||||
attemptReceived = attempt;
|
||||
jitterReceived = jitter;
|
||||
|
||||
retryAfter = 1ms;
|
||||
return onResponseInvoked < 3;
|
||||
}));
|
||||
|
||||
policies.emplace_back(std::make_unique<TestTransportPolicy>([&]() {
|
||||
auto response = std::make_unique<RawResponse>(1, 1, HttpStatusCode::Ok, "Test");
|
||||
|
||||
responsePtrSent = response.get();
|
||||
|
||||
return response;
|
||||
}));
|
||||
|
||||
Azure::Core::Http::_internal::HttpPipeline pipeline(policies);
|
||||
|
||||
Request request(HttpMethod::Get, Azure::Core::Url("https://www.microsoft.com"));
|
||||
pipeline.Send(request, Azure::Core::Context());
|
||||
}
|
||||
|
||||
EXPECT_EQ(onTransportFailureInvoked, 0);
|
||||
EXPECT_EQ(onResponseInvoked, 3);
|
||||
|
||||
EXPECT_NE(responsePtrSent, nullptr);
|
||||
EXPECT_EQ(responsePtrSent, responsePtrReceived);
|
||||
|
||||
EXPECT_EQ(retryOptionsReceived.MaxRetries, retryOptions.MaxRetries);
|
||||
EXPECT_EQ(retryOptionsReceived.RetryDelay, retryOptions.RetryDelay);
|
||||
EXPECT_EQ(retryOptionsReceived.MaxRetryDelay, retryOptions.MaxRetryDelay);
|
||||
EXPECT_EQ(retryOptionsReceived.StatusCodes, retryOptions.StatusCodes);
|
||||
|
||||
EXPECT_EQ(attemptReceived, 3);
|
||||
EXPECT_EQ(jitterReceived, -1);
|
||||
}
|
||||
|
||||
TEST(RetryPolicy, ShouldRetryOnTransportFailure)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
RetryOptions const retryOptions{5, 10s, 5min, {HttpStatusCode::Ok}};
|
||||
|
||||
RetryOptions retryOptionsReceived{0, 0ms, 0ms, {}};
|
||||
int32_t attemptReceived = -1234;
|
||||
double jitterReceived = -5678;
|
||||
|
||||
int onTransportFailureInvoked = 0;
|
||||
int onResponseInvoked = 0;
|
||||
|
||||
{
|
||||
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
|
||||
policies.emplace_back(std::make_unique<RetryPolicyTest>(
|
||||
retryOptions,
|
||||
[&](auto options, auto attempt, auto, auto jitter) {
|
||||
++onTransportFailureInvoked;
|
||||
retryOptionsReceived = options;
|
||||
attemptReceived = attempt;
|
||||
jitterReceived = jitter;
|
||||
|
||||
return false;
|
||||
},
|
||||
[&](auto, auto options, auto attempt, auto, auto jitter) {
|
||||
++onResponseInvoked;
|
||||
retryOptionsReceived = options;
|
||||
attemptReceived = attempt;
|
||||
jitterReceived = jitter;
|
||||
|
||||
return false;
|
||||
}));
|
||||
|
||||
policies.emplace_back(std::make_unique<TestTransportPolicy>(
|
||||
[]() -> std::unique_ptr<RawResponse> { throw TransportException("Test"); }));
|
||||
|
||||
Azure::Core::Http::_internal::HttpPipeline pipeline(policies);
|
||||
|
||||
Request request(HttpMethod::Get, Azure::Core::Url("https://www.microsoft.com"));
|
||||
EXPECT_THROW(pipeline.Send(request, Azure::Core::Context()), TransportException);
|
||||
}
|
||||
|
||||
EXPECT_EQ(onTransportFailureInvoked, 1);
|
||||
EXPECT_EQ(onResponseInvoked, 0);
|
||||
|
||||
EXPECT_EQ(retryOptionsReceived.MaxRetries, retryOptions.MaxRetries);
|
||||
EXPECT_EQ(retryOptionsReceived.RetryDelay, retryOptions.RetryDelay);
|
||||
EXPECT_EQ(retryOptionsReceived.MaxRetryDelay, retryOptions.MaxRetryDelay);
|
||||
EXPECT_EQ(retryOptionsReceived.StatusCodes, retryOptions.StatusCodes);
|
||||
|
||||
EXPECT_EQ(attemptReceived, 1);
|
||||
EXPECT_EQ(jitterReceived, -1);
|
||||
|
||||
// 3 attempts
|
||||
retryOptionsReceived = RetryOptions{0, 0ms, 0ms, {}};
|
||||
attemptReceived = -1234;
|
||||
jitterReceived = -5678;
|
||||
|
||||
onTransportFailureInvoked = 0;
|
||||
onResponseInvoked = 0;
|
||||
|
||||
{
|
||||
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
|
||||
policies.emplace_back(std::make_unique<RetryPolicyTest>(
|
||||
retryOptions,
|
||||
[&](auto options, auto attempt, auto, auto jitter) {
|
||||
++onTransportFailureInvoked;
|
||||
retryOptionsReceived = options;
|
||||
attemptReceived = attempt;
|
||||
jitterReceived = jitter;
|
||||
|
||||
return onTransportFailureInvoked < 3;
|
||||
},
|
||||
[&](auto, auto options, auto attempt, auto retryAfter, auto jitter) {
|
||||
++onResponseInvoked;
|
||||
retryOptionsReceived = options;
|
||||
attemptReceived = attempt;
|
||||
jitterReceived = jitter;
|
||||
|
||||
retryAfter = 1ms;
|
||||
return false;
|
||||
}));
|
||||
|
||||
policies.emplace_back(std::make_unique<TestTransportPolicy>(
|
||||
[]() -> std::unique_ptr<RawResponse> { throw TransportException("Test"); }));
|
||||
|
||||
Azure::Core::Http::_internal::HttpPipeline pipeline(policies);
|
||||
|
||||
Request request(HttpMethod::Get, Azure::Core::Url("https://www.microsoft.com"));
|
||||
EXPECT_THROW(pipeline.Send(request, Azure::Core::Context()), TransportException);
|
||||
}
|
||||
|
||||
EXPECT_EQ(onTransportFailureInvoked, 3);
|
||||
EXPECT_EQ(onResponseInvoked, 0);
|
||||
|
||||
EXPECT_EQ(retryOptionsReceived.MaxRetries, retryOptions.MaxRetries);
|
||||
EXPECT_EQ(retryOptionsReceived.RetryDelay, retryOptions.RetryDelay);
|
||||
EXPECT_EQ(retryOptionsReceived.MaxRetryDelay, retryOptions.MaxRetryDelay);
|
||||
EXPECT_EQ(retryOptionsReceived.StatusCodes, retryOptions.StatusCodes);
|
||||
|
||||
EXPECT_EQ(attemptReceived, 3);
|
||||
EXPECT_EQ(jitterReceived, -1);
|
||||
}
|
||||
|
||||
namespace {
|
||||
class RetryLogic : private RetryPolicy {
|
||||
RetryLogic() : RetryPolicy(RetryOptions()){};
|
||||
~RetryLogic(){};
|
||||
|
||||
static RetryLogic const g_retryPolicy;
|
||||
|
||||
public:
|
||||
static bool TestShouldRetryOnTransportFailure(
|
||||
RetryOptions const& retryOptions,
|
||||
int32_t attempt,
|
||||
std::chrono::milliseconds& retryAfter,
|
||||
double jitterFactor)
|
||||
{
|
||||
return g_retryPolicy.ShouldRetryOnTransportFailure(
|
||||
retryOptions, attempt, retryAfter, jitterFactor);
|
||||
}
|
||||
|
||||
static bool TestShouldRetryOnResponse(
|
||||
RawResponse const& response,
|
||||
RetryOptions const& retryOptions,
|
||||
int32_t attempt,
|
||||
std::chrono::milliseconds& retryAfter,
|
||||
double jitterFactor)
|
||||
{
|
||||
return g_retryPolicy.ShouldRetryOnResponse(
|
||||
response, retryOptions, attempt, retryAfter, jitterFactor);
|
||||
}
|
||||
};
|
||||
|
||||
RetryLogic const RetryLogic::g_retryPolicy;
|
||||
} // namespace
|
||||
|
||||
TEST(RetryPolicy, Exponential)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
RetryOptions const options{3, 1s, 2min, {}};
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 1, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 1s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 2, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 2s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 3, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 4s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 4, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, false);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RetryPolicy, LessThan2Retries)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure({1, 1s, 2min, {}}, 1, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 1s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure({0, 1s, 2min, {}}, 1, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, false);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure({-1, 1s, 2min, {}}, 1, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, false);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RetryPolicy, NotExceedingMaxRetryDelay)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
RetryOptions const options{7, 1s, 20s, {}};
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 1, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 1s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 2, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 2s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 3, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 4s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 4, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 8s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 5, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 16s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 6, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 20s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 7, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 20s);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RetryPolicy, NotExceedingInt32Max)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
RetryOptions const options{35, 1s, 9999999999999s, {}};
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 31, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 1073741824s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 32, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 2147483647s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 33, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 2147483647s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 34, retryAfter, 1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 2147483647s);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RetryPolicy, Jitter)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
RetryOptions const options{3, 10s, 20min, {}};
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 1, retryAfter, 0.8);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 8s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 1, retryAfter, 1.3);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 13s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 2, retryAfter, 0.8);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 16s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure(options, 2, retryAfter, 1.3);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 26s);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RetryPolicy, JitterExtremes)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure({3, 1ms, 2min, {}}, 1, retryAfter, 0.8);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 0ms);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure({3, 2ms, 2min, {}}, 1, retryAfter, 0.8);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 1ms);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure({3, 10s, 21s, {}}, 2, retryAfter, 1.3);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 21s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry
|
||||
= RetryLogic::TestShouldRetryOnTransportFailure({3, 10s, 21s, {}}, 3, retryAfter, 1.3);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 21s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry = RetryLogic::TestShouldRetryOnTransportFailure(
|
||||
{35, 1s, 9999999999999s, {}}, 33, retryAfter, 1.3);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 2791728741100ms);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RetryPolicy, HttpStatusCode)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry = RetryLogic::TestShouldRetryOnResponse(
|
||||
RawResponse(1, 1, HttpStatusCode::RequestTimeout, ""),
|
||||
{3, 3210s, 3h, {HttpStatusCode::RequestTimeout}},
|
||||
1,
|
||||
retryAfter,
|
||||
1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 3210s);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry = RetryLogic::TestShouldRetryOnResponse(
|
||||
RawResponse(1, 1, HttpStatusCode::RequestTimeout, ""),
|
||||
{3, 654s, 3h, {HttpStatusCode::Ok}},
|
||||
1,
|
||||
retryAfter,
|
||||
1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, false);
|
||||
}
|
||||
|
||||
{
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry = RetryLogic::TestShouldRetryOnResponse(
|
||||
RawResponse(1, 1, HttpStatusCode::Ok, ""),
|
||||
{3, 987s, 3h, {HttpStatusCode::Ok}},
|
||||
1,
|
||||
retryAfter,
|
||||
1.0);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 987s);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RetryPolicy, RetryAfterMs)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
{
|
||||
RawResponse response(1, 1, HttpStatusCode::RequestTimeout, "");
|
||||
response.SetHeader("rEtRy-aFtEr-mS", "1234");
|
||||
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry = RetryLogic::TestShouldRetryOnResponse(
|
||||
response, {3, 1s, 2min, {HttpStatusCode::RequestTimeout}}, 1, retryAfter, 1.3);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 1234ms);
|
||||
}
|
||||
|
||||
{
|
||||
RawResponse response(1, 1, HttpStatusCode::RequestTimeout, "");
|
||||
response.SetHeader("X-mS-ReTrY-aFtEr-MS", "5678");
|
||||
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry = RetryLogic::TestShouldRetryOnResponse(
|
||||
response, {3, 1s, 2min, {HttpStatusCode::RequestTimeout}}, 1, retryAfter, 0.8);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 5678ms);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RetryPolicy, RetryAfter)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
{
|
||||
RawResponse response(1, 1, HttpStatusCode::RequestTimeout, "");
|
||||
response.SetHeader("rEtRy-aFtEr", "90");
|
||||
|
||||
std::chrono::milliseconds retryAfter{};
|
||||
bool const shouldRetry = RetryLogic::TestShouldRetryOnResponse(
|
||||
response, {3, 1s, 2min, {HttpStatusCode::RequestTimeout}}, 1, retryAfter, 1.1);
|
||||
|
||||
EXPECT_EQ(shouldRetry, true);
|
||||
EXPECT_EQ(retryAfter, 90s);
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,7 @@ using namespace Azure::Core::Http::Policies::_internal;
|
||||
namespace {
|
||||
|
||||
class NoOpPolicy : public HttpPolicy {
|
||||
private:
|
||||
std::unique_ptr<RawResponse> Send(Request& request, NextHttpPolicy policy, Context const& context)
|
||||
const override
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user