Add policy tests (#2009)

Closes #271
Closes #1570
Closes #1596
This commit is contained in:
Anton Kolesnyk 2021-04-06 02:10:30 +00:00 committed by GitHub
parent 93bc8c4218
commit 3b36c9091a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1426 additions and 108 deletions

View File

@ -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;
};
/**

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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");
}
}
}

View 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"));
}

View 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);
}

View 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);
}
}

View File

@ -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
{