From 3b36c9091a6088281ca7fb17faa5335b1b04ffa3 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk <41349689+antkmsft@users.noreply.github.com> Date: Tue, 6 Apr 2021 02:10:30 +0000 Subject: [PATCH] Add policy tests (#2009) Closes #271 Closes #1570 Closes #1596 --- .../inc/azure/core/http/policies/policy.hpp | 18 +- sdk/core/azure-core/src/http/log_policy.cpp | 54 +- sdk/core/azure-core/src/http/retry_policy.cpp | 177 ++--- sdk/core/azure-core/test/ut/CMakeLists.txt | 4 + .../ut/bearer_token_authentication_policy.cpp | 204 +++++ sdk/core/azure-core/test/ut/log_policy.cpp | 263 +++++++ .../azure-core/test/ut/request_id_policy.cpp | 94 +++ sdk/core/azure-core/test/ut/retry_policy.cpp | 719 ++++++++++++++++++ .../azure-core/test/ut/telemetry_policy.cpp | 1 + 9 files changed, 1426 insertions(+), 108 deletions(-) create mode 100644 sdk/core/azure-core/test/ut/bearer_token_authentication_policy.cpp create mode 100644 sdk/core/azure-core/test/ut/log_policy.cpp create mode 100644 sdk/core/azure-core/test/ut/request_id_policy.cpp create mode 100644 sdk/core/azure-core/test/ut/retry_policy.cpp diff --git a/sdk/core/azure-core/inc/azure/core/http/policies/policy.hpp b/sdk/core/azure-core/inc/azure/core/http/policies/policy.hpp index f2931039f..bdf5109bc 100644 --- a/sdk/core/azure-core/inc/azure/core/http/policies/policy.hpp +++ b/sdk/core/azure-core/inc/azure/core/http/policies/policy.hpp @@ -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; }; /** diff --git a/sdk/core/azure-core/src/http/log_policy.cpp b/sdk/core/azure-core/src/http/log_policy.cpp index c0d08c01b..9c3f48c7a 100644 --- a/sdk/core/azure-core/src/http/log_policy.cpp +++ b/sdk/core/azure-core/src/http/log_policy.cpp @@ -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::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::type>::type - encodedAllowedRequestQueryParams; - for (auto const& encodedRequestQueryParam : encodedRequestQueryParams) + std::remove_const::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::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); diff --git a/sdk/core/azure-core/src/http/retry_policy.cpp b/sdk/core/azure-core/src/http/retry_policy.cpp index ffd855a19..2df6eb1af 100644 --- a/sdk/core/azure-core/src/http/retry_policy.cpp +++ b/sdk/core/azure-core/src/http/retry_policy.cpp @@ -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::digits - - (std::numeric_limits::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(static_cast(std::rand())) / RAND_MAX) * 0.5); + } + + constexpr auto beforeLastBit + = std::numeric_limits::digits - (std::numeric_limits::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::max() * RetryDelay. + // way up to std::numeric_limits::max() * RetryDelay. auto exponentialRetryAfter = retryOptions.RetryDelay - * ((attempt <= beforeLastBit) ? (1 << attempt) : std::numeric_limits::max()); - - // jitterFactor is a random double number in the range [0.8 .. 1.3) - auto jitterFactor = 0.8 + (static_cast(std::rand()) / RAND_MAX) * 0.5; + * (((attempt - 1) <= beforeLastBit) ? (1 << (attempt - 1)) + : std::numeric_limits::max()); // Multiply exponentialRetryAfter by jitterFactor - exponentialRetryAfter = Delay(static_cast( - (std::chrono::duration(exponentialRetryAfter) * jitterFactor) + exponentialRetryAfter = std::chrono::milliseconds(static_cast( + (std::chrono::duration(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(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(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(RetryKey); + return context.GetValue(RetryKey); } std::unique_ptr RetryPolicy::Send( @@ -191,9 +133,9 @@ std::unique_ptr 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 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(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(sc)) + + " will be retried."); + } + } + + if (!GetResponseHeaderBasedDelay(response, retryAfter)) + { + retryAfter = CalculateExponentialDelay(retryOptions, attempt, jitterFactor); + } + + return true; +} diff --git a/sdk/core/azure-core/test/ut/CMakeLists.txt b/sdk/core/azure-core/test/ut/CMakeLists.txt index 36b5cac02..cfa0474b0 100644 --- a/sdk/core/azure-core/test/ut/CMakeLists.txt +++ b/sdk/core/azure-core/test/ut/CMakeLists.txt @@ -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 diff --git a/sdk/core/azure-core/test/ut/bearer_token_authentication_policy.cpp b/sdk/core/azure-core/test/ut/bearer_token_authentication_policy.cpp new file mode 100644 index 000000000..7ba01c13a --- /dev/null +++ b/sdk/core/azure-core/test/ut/bearer_token_authentication_policy.cpp @@ -0,0 +1,204 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include +#include + +#include + +namespace { +class TestTokenCredential : public Azure::Core::Credentials::TokenCredential { +private: + std::shared_ptr m_accessToken; + +public: + explicit TestTokenCredential( + std::shared_ptr 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 Send( + Azure::Core::Http::Request&, + Azure::Core::Http::Policies::NextHttpPolicy, + Azure::Core::Context const&) const override + { + return nullptr; + }; + + std::unique_ptr Clone() const override + { + return std::make_unique(*this); + }; +}; + +} // namespace + +TEST(BearerTokenAuthenticationPolicy, InitialGet) +{ + using namespace std::chrono_literals; + auto accessToken = std::make_shared(); + + std::vector> policies; + + policies.emplace_back( + std::make_unique( + std::make_shared(accessToken), + Azure::Core::Credentials::TokenRequestContext{{"https://microsoft.com/.default"}})); + + policies.emplace_back(std::make_unique()); + + 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(); + + std::vector> policies; + + policies.emplace_back( + std::make_unique( + std::make_shared(accessToken), + Azure::Core::Credentials::TokenRequestContext{{"https://microsoft.com/.default"}})); + + policies.emplace_back(std::make_unique()); + + 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(); + + std::vector> policies; + + policies.emplace_back( + std::make_unique( + std::make_shared(accessToken), + Azure::Core::Credentials::TokenRequestContext{{"https://microsoft.com/.default"}})); + + policies.emplace_back(std::make_unique()); + + 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(); + + std::vector> policies; + + policies.emplace_back( + std::make_unique( + std::make_shared(accessToken), + Azure::Core::Credentials::TokenRequestContext{{"https://microsoft.com/.default"}})); + + policies.emplace_back(std::make_unique()); + + 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"); + } + } +} diff --git a/sdk/core/azure-core/test/ut/log_policy.cpp b/sdk/core/azure-core/test/ut/log_policy.cpp new file mode 100644 index 000000000..0ad7b15a6 --- /dev/null +++ b/sdk/core/azure-core/test/ut/log_policy.cpp @@ -0,0 +1,263 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include +#include +#include + +#include + +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 Clone() const override + { + return std::make_unique(*this); + } + + std::unique_ptr 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(1, 1, HttpStatusCode::Ok, "OKAY"); + + response->SetBody(std::vector(responseBody, responseBody + sizeof(responseBody))); + + response->SetBodyStream( + std::make_unique(responseBodyStream, sizeof(responseBodyStream) - 1)); + + return response; + } + }; + + constexpr uint8_t const requestBodyStream[] = "Request Body Stream"; + auto const bodyStream + = std::make_unique(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> policies; + + policies.emplace_back(std::make_unique(logOptions)); + policies.emplace_back(std::make_unique()); + + 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 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")); +} diff --git a/sdk/core/azure-core/test/ut/request_id_policy.cpp b/sdk/core/azure-core/test/ut/request_id_policy.cpp new file mode 100644 index 000000000..6f7849b2f --- /dev/null +++ b/sdk/core/azure-core/test/ut/request_id_policy.cpp @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include +#include + +#include + +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 Clone() const override { return std::make_unique(*this); } + + std::unique_ptr 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> policies; + + policies.emplace_back(std::make_unique()); + policies.emplace_back(std::make_unique()); + + 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> policies; + + policies.emplace_back(std::make_unique()); + policies.emplace_back(std::make_unique()); + + 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> policies; + + policies.emplace_back(std::make_unique()); + policies.emplace_back(std::make_unique()); + + 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); +} diff --git a/sdk/core/azure-core/test/ut/retry_policy.cpp b/sdk/core/azure-core/test/ut/retry_policy.cpp new file mode 100644 index 000000000..7aab62a9d --- /dev/null +++ b/sdk/core/azure-core/test/ut/retry_policy.cpp @@ -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 + +#include + +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()> m_send; + +public: + TestTransportPolicy(std::function()> send) : m_send(send) {} + + std::unique_ptr Send( + Request&, + NextHttpPolicy, + Azure::Core::Context const&) const override + { + return m_send(); + }; + + std::unique_ptr Clone() const override + { + return std::make_unique(*this); + }; +}; + +class RetryPolicyTest : public RetryPolicy { +private: + std::function + m_shouldRetryOnTransportFailure; + + std::function< + bool(RawResponse const&, RetryOptions const&, int32_t, std::chrono::milliseconds&, double)> + m_shouldRetryOnResponse; + +public: + RetryPolicyTest( + RetryOptions const& retryOptions, + std::function + shouldRetryOnTransportFailure, + std::function shouldRetryOnResponse) + : RetryPolicy(retryOptions), m_shouldRetryOnTransportFailure(shouldRetryOnTransportFailure), + m_shouldRetryOnResponse(shouldRetryOnResponse) + { + } + + std::unique_ptr Clone() const override + { + return std::make_unique(*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> policies; + policies.emplace_back(std::make_unique( + 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([&]() { + auto response = std::make_unique(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> policies; + policies.emplace_back(std::make_unique( + 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([&]() { + auto response = std::make_unique(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> policies; + policies.emplace_back(std::make_unique( + 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( + []() -> std::unique_ptr { 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> policies; + policies.emplace_back(std::make_unique( + 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( + []() -> std::unique_ptr { 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); + } +} diff --git a/sdk/core/azure-core/test/ut/telemetry_policy.cpp b/sdk/core/azure-core/test/ut/telemetry_policy.cpp index 22d15a6ba..3e7096f02 100644 --- a/sdk/core/azure-core/test/ut/telemetry_policy.cpp +++ b/sdk/core/azure-core/test/ut/telemetry_policy.cpp @@ -14,6 +14,7 @@ using namespace Azure::Core::Http::Policies::_internal; namespace { class NoOpPolicy : public HttpPolicy { +private: std::unique_ptr Send(Request& request, NextHttpPolicy policy, Context const& context) const override {