From 692cab5651b47b6a3a9ea2b7930848451dd89d85 Mon Sep 17 00:00:00 2001 From: Rick Winter Date: Thu, 25 Jun 2020 13:58:27 -0700 Subject: [PATCH] Nullable type (#204) * Nullable type Add Nullable type and tests --- CMakeSettings.json | 52 ++-- sdk/core/azure-core/inc/nullable.hpp | 235 +++++++++++++++++ sdk/core/azure-core/test/CMakeLists.txt | 5 +- sdk/core/azure-core/test/http.cpp | 326 ++++++++++++++++++++++++ sdk/core/azure-core/test/main.cpp | 324 +---------------------- sdk/core/azure-core/test/nullable.cpp | 176 +++++++++++++ 6 files changed, 770 insertions(+), 348 deletions(-) create mode 100644 sdk/core/azure-core/inc/nullable.hpp create mode 100644 sdk/core/azure-core/test/http.cpp create mode 100644 sdk/core/azure-core/test/nullable.cpp diff --git a/CMakeSettings.json b/CMakeSettings.json index 055478cac..2c1ae0cc6 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -1,28 +1,28 @@ { - "configurations": [ - { - "name": "x64-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "inheritEnvironments": [ "msvc_x64_x64" ], - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeCommandArgs": "-DINSTALL_GTEST=OFF -DBUILD_TESTING=OFF -DBUILD_CURL_TRANSPORT=ON", - "buildCommandArgs": "-v", - "ctestCommandArgs": "", - "variables": [] - }, - { - "name": "x86-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "", - "ctestCommandArgs": "", - "inheritEnvironments": [ "msvc_x86" ], - "variables": [] - } - ] + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "-DINSTALL_GTEST=OFF -DBUILD_TESTING=ON -DBUILD_CURL_TRANSPORT=ON -DBUILD_STORAGE_SAMPLES=ON", + "buildCommandArgs": "-v", + "ctestCommandArgs": "", + "variables": [] + }, + { + "name": "x86-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x86" ], + "variables": [] + } + ] } \ No newline at end of file diff --git a/sdk/core/azure-core/inc/nullable.hpp b/sdk/core/azure-core/inc/nullable.hpp new file mode 100644 index 000000000..23665819f --- /dev/null +++ b/sdk/core/azure-core/inc/nullable.hpp @@ -0,0 +1,235 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once + +#include // for abort +#include // for placement new +#include +#include // for swap and move + +namespace Azure { namespace Core { + namespace Details { + struct NontrivialEmptyType + { + constexpr NontrivialEmptyType() noexcept {} + }; + } // namespace Details + + template class Nullable { + union + { + Details::NontrivialEmptyType + m_disengaged; // due to constexpr rules for the default constructor + T m_value; + }; + + bool m_hasValue; + + public: + constexpr Nullable() : m_disengaged{}, m_hasValue(false) {} + constexpr Nullable(const T& initialValue) : m_value(initialValue), m_hasValue(true) {} + + Nullable(const Nullable& other) noexcept(std::is_nothrow_copy_constructible::value) + : m_hasValue(other.m_hasValue) + { + if (m_hasValue) + { + ::new (static_cast(&m_value)) T(other.m_value); + } + } + + Nullable(Nullable&& other) noexcept(std::is_nothrow_move_constructible::value) + : m_hasValue(other.m_hasValue) + { + if (m_hasValue) + { + ::new (static_cast(&m_value)) T(std::move(other.m_value)); + } + } + + ~Nullable() + { + if (m_hasValue) + { + m_value.~T(); + } + } + + void Reset() noexcept /* enforces termination */ + { + if (m_hasValue) + { + m_value.~T(); + m_hasValue = false; + } + } + + // this assumes that swap can't throw if T is nothrow move constructible because + // is_nothrow_swappable is added in C++17 + void Swap(Nullable& other) noexcept(std::is_nothrow_move_constructible::value) + { + if (m_hasValue) + { + if (other.m_hasValue) + { + using std::swap; + swap(m_value, other.m_value); + } + else + { + ::new (static_cast(&other.m_value)) T(std::move(m_value)); // throws + other.m_hasValue = true; + Reset(); + } + } + else if (other.m_hasValue) + { + ::new (static_cast(&m_value)) T(std::move(other.m_value)); // throws + m_hasValue = true; + other.Reset(); + } + } + + // Intentionally lowercase to follow the Swappable requirements + // https://en.cppreference.com/w/cpp/named_req/Swappable + // + friend void swap(Nullable& lhs, Nullable& rhs) noexcept( + std::is_nothrow_move_constructible::value) + { + lhs.Swap(rhs); + } + + Nullable& operator=(const Nullable& other) + { + // this copy and swap may be inefficient for some Ts but + // it's a lot less code than the standard implementation :) + Nullable{other}.Swap(*this); + return *this; + } + + Nullable& operator=(Nullable&& other) noexcept(std::is_nothrow_move_constructible::value) + { + // this move and swap may be inefficient for some Ts but + // it's a lot less code than the standard implementation :) + Nullable{std::move(other)}.Swap(*this); + return *this; + } + + template < + class U = T, + typename std::enable_if< + !std::is_same< + Nullable, + typename std::remove_cv::type>::type>:: + value // Avoid repeated assignment + && !( + std::is_scalar::value + && std::is_same::type>::value) // Avoid repeated + // assignment of + // equivallent scaler + // types + && std::is_constructible::value // Ensure the type is constructible + && std::is_assignable::value, // Ensure the type is assignable + int>::type + = 0> + Nullable& operator=(U&& other) noexcept( + std::is_nothrow_constructible::value&& std::is_nothrow_assignable::value) + { + if (m_hasValue) + { + m_value = std::forward(other); + } + else + { + ::new (static_cast(&m_value)) T(std::forward(other)); + m_hasValue = true; + } + return *this; + } + + template + T& Emplace(U&&... Args) noexcept(std::is_nothrow_constructible::value) + { + Reset(); + ::new (static_cast(&m_value)) T(std::forward(Args)...); + return m_value; + } + + bool HasValue() const noexcept { return m_hasValue; } + + const T& GetValue() const& noexcept + { + if (!m_hasValue) + { + // throwing here prohibited by our guidelines + // https://azure.github.io/azure-sdk/cpp_design.html#pre-conditions + std::abort(); + } + + return m_value; + } + + T& GetValue() & noexcept + { + if (!m_hasValue) + { + // throwing here prohibited by our guidelines + // https://azure.github.io/azure-sdk/cpp_design.html#pre-conditions + std::abort(); + } + + return m_value; + } + + T&& GetValue() && noexcept + { + if (!m_hasValue) + { + // throwing here prohibited by our guidelines + // https://azure.github.io/azure-sdk/cpp_design.html#pre-conditions + std::abort(); + } + + return std::move(m_value); + } + + explicit operator bool() const noexcept { return HasValue(); } + + template < + class U = T, + typename std::enable_if< + std::is_convertible_v< + const T&, + typename std::remove_cv::type> && std::is_convertible_v, + int>::type + = 0> + constexpr typename std::remove_cv::type ValueOr(U&& other) const& + { + if (m_hasValue) + { + return m_value; + } + + return static_cast::type>(std::forward(other)); + } + + template < + class U = T, + typename std::enable_if< + std::is_convertible_v< + T, + typename std::remove_cv::type> && std::is_convertible_v, + int>::type + = 0> + constexpr typename std::remove_cv::type ValueOr(U&& other) && + { + if (m_hasValue) + { + return std::move(m_value); + } + + return static_cast::type>(std::forward(other)); + } + }; +}} // namespace Azure::Core diff --git a/sdk/core/azure-core/test/CMakeLists.txt b/sdk/core/azure-core/test/CMakeLists.txt index ac9932d4a..28b122622 100644 --- a/sdk/core/azure-core/test/CMakeLists.txt +++ b/sdk/core/azure-core/test/CMakeLists.txt @@ -11,8 +11,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) add_executable ( ${TARGET_NAME} - main.cpp -) + main.cpp + nullable.cpp + http.cpp) target_link_libraries(${TARGET_NAME} PRIVATE azure-core) add_gtest(${TARGET_NAME}) diff --git a/sdk/core/azure-core/test/http.cpp b/sdk/core/azure-core/test/http.cpp new file mode 100644 index 000000000..af40883c4 --- /dev/null +++ b/sdk/core/azure-core/test/http.cpp @@ -0,0 +1,326 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "gtest/gtest.h" +#include +#include +#include +#include + +using namespace Azure::Core; + +TEST(Http_Request, getters) +{ + Http::HttpMethod httpMethod = Http::HttpMethod::Get; + std::string url = "http://test.url.com"; + Http::Request req(httpMethod, url); + + // EXPECT_PRED works better than just EQ because it will print values in log + EXPECT_PRED2( + [](Http::HttpMethod a, Http::HttpMethod b) { return a == b; }, req.GetMethod(), httpMethod); + EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, req.GetEncodedUrl(), url); + + EXPECT_NO_THROW(req.AddHeader("name", "value")); + EXPECT_NO_THROW(req.AddHeader("name2", "value2")); + + auto headers = req.GetHeaders(); + + EXPECT_TRUE(headers.count("name")); + EXPECT_TRUE(headers.count("name2")); + EXPECT_FALSE(headers.count("newHeader")); + + auto value = headers.find("name"); + EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value->second, "value"); + auto value2 = headers.find("name2"); + EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value2->second, "value2"); + + // now add to retry headers + req.StartRetry(); + // same headers first, then one new + EXPECT_NO_THROW(req.AddHeader("name", "retryValue")); + EXPECT_NO_THROW(req.AddHeader("name2", "retryValue2")); + EXPECT_NO_THROW(req.AddHeader("newHeader", "new")); + + headers = req.GetHeaders(); + + EXPECT_TRUE(headers.count("name")); + EXPECT_TRUE(headers.count("name2")); + EXPECT_TRUE(headers.count("newHeader")); + + value = headers.find("name"); + EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value->second, "retryValue"); + value2 = headers.find("name2"); + EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value2->second, "retryValue2"); + auto value3 = headers.find("newHeader"); + EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value3->second, "new"); +} + +TEST(Http_Request, query_parameter) +{ + Http::HttpMethod httpMethod = Http::HttpMethod::Put; + std::string url = "http://test.com"; + Http::Request req(httpMethod, url); + + EXPECT_NO_THROW(req.AddQueryParameter("query", "value")); + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, + req.GetEncodedUrl(), + url + "?query=value"); + + std::string url_with_query = "http://test.com?query=1"; + Http::Request req_with_query(httpMethod, url_with_query); + + // ignore if adding same query parameter key that is already in url + EXPECT_NO_THROW(req_with_query.AddQueryParameter("query", "value")); + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, + req_with_query.GetEncodedUrl(), + url_with_query); + + // retry query params testing + req.StartRetry(); + // same query parameter should override previous + EXPECT_NO_THROW(req.AddQueryParameter("query", "retryValue")); + + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, + req.GetEncodedUrl(), + url + "?query=retryValue"); +} + +TEST(Http_Request, add_path) +{ + Http::HttpMethod httpMethod = Http::HttpMethod::Post; + std::string url = "http://test.com"; + Http::Request req(httpMethod, url); + + EXPECT_NO_THROW(req.AppendPath("path")); + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, req.GetEncodedUrl(), url + "/path"); + + EXPECT_NO_THROW(req.AddQueryParameter("query", "value")); + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, + req.GetEncodedUrl(), + url + "/path?query=value"); + + EXPECT_NO_THROW(req.AppendPath("path2")); + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, + req.GetEncodedUrl(), + url + "/path/path2?query=value"); + + EXPECT_NO_THROW(req.AppendPath("path3")); + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, + req.GetEncodedUrl(), + url + "/path/path2/path3?query=value"); +} + +class Azure::Core::Credentials::Details::CredentialTest : public ClientSecretCredential { +public: + CredentialTest( + std::string const& tenantId, + std::string const& clientId, + std::string const& clientSecret) + : ClientSecretCredential(tenantId, clientId, clientSecret) + { + } + + std::string NewTokenString; + std::chrono::system_clock::time_point NewExpiration; + bool IsExpired; + + std::string GetTenantId() const + { + return this->ClientSecretCredential::m_clientSecret->m_tenantId; + } + + std::string GetClientId() const + { + return this->ClientSecretCredential::m_clientSecret->m_clientId; + } + + std::string GetClientSecret() const + { + return this->ClientSecretCredential::m_clientSecret->m_clientSecret; + } + + std::string GetScopes() const { return this->ClientSecretCredential::m_clientSecret->m_scopes; } + + bool IsTokenPtrNull() const { return !this->TokenCredential::m_token; } + + std::string GetTokenString() const { return this->TokenCredential::m_token->m_tokenString; } + + std::chrono::system_clock::time_point GetExpiration() const + { + return this->TokenCredential::m_token->m_expiresAt; + } + +private: + void RefreshToken( + std::string& newTokenString, + std::chrono::system_clock::time_point& newExpiration) override + { + newTokenString = this->NewTokenString; + newExpiration = this->NewExpiration; + } + + bool IsTokenExpired(std::chrono::system_clock::time_point const&) const override + { + return this->IsExpired; + } +}; + +TEST(Credential, ClientSecretCredential) +{ + // Client Secret credential properties + std::string const tenantId = "tenantId"; + std::string const clientId = "clientId"; + std::string const clientSecret = "clientSecret"; + + Credentials::Details::CredentialTest clientSecretCredential(tenantId, clientId, clientSecret); + + EXPECT_EQ(clientSecretCredential.GetTenantId(), tenantId); + EXPECT_EQ(clientSecretCredential.GetClientId(), clientId); + EXPECT_EQ(clientSecretCredential.GetClientSecret(), clientSecret); + + // Token credential + { + auto const emptyString = std::string(); + auto const defaultTime = std::chrono::system_clock::time_point(); + { + // Default values + { + EXPECT_EQ(clientSecretCredential.IsTokenPtrNull(), true); + } + + { + // Set scopes + std::string const scopes = "scope"; + { + Credentials::Credential::Internal::SetScopes(clientSecretCredential, scopes); + EXPECT_EQ(clientSecretCredential.IsTokenPtrNull(), true); + } + + // Get token + { + std::string const olderToken = "olderToken"; + std::string const newToken = "newToken"; + auto const olderTime = defaultTime + std::chrono::minutes(10); + auto const newTime = olderTime + std::chrono::minutes(10); + + { + clientSecretCredential.IsExpired = true; + clientSecretCredential.NewTokenString = olderToken; + clientSecretCredential.NewExpiration = olderTime; + + auto const tokenReceived + = Credentials::TokenCredential::Internal::GetToken(clientSecretCredential); + + EXPECT_EQ(clientSecretCredential.IsTokenPtrNull(), false); + EXPECT_EQ(tokenReceived, olderToken); + EXPECT_EQ(clientSecretCredential.GetTokenString(), olderToken); + EXPECT_EQ(clientSecretCredential.GetScopes(), scopes); + EXPECT_EQ(clientSecretCredential.GetExpiration(), olderTime); + } + + // Attemp to get the token when it is not expired yet + { + clientSecretCredential.IsExpired = false; + clientSecretCredential.NewTokenString = newToken; + clientSecretCredential.NewExpiration = newTime; + + auto const tokenReceived + = Credentials::TokenCredential::Internal::GetToken(clientSecretCredential); + + EXPECT_EQ(tokenReceived, olderToken); + EXPECT_EQ(clientSecretCredential.GetTokenString(), olderToken); + EXPECT_EQ(clientSecretCredential.GetScopes(), scopes); + EXPECT_EQ(clientSecretCredential.GetExpiration(), olderTime); + } + + // Attempt to get token after it expired + { + clientSecretCredential.IsExpired = true; + + auto const tokenReceived + = Credentials::TokenCredential::Internal::GetToken(clientSecretCredential); + + EXPECT_EQ(tokenReceived, newToken); + EXPECT_EQ(clientSecretCredential.GetTokenString(), newToken); + EXPECT_EQ(clientSecretCredential.GetScopes(), scopes); + EXPECT_EQ(clientSecretCredential.GetExpiration(), newTime); + + clientSecretCredential.IsExpired = false; + } + + // Setting the very same scopes set earlier does not reset token + { + std::string const scopesCopy + = scopes.substr(0, scopes.length() / 2) + scopes.substr(scopes.length() / 2); + + { + auto const scopesPtr = scopes.c_str(); + auto const scopesCopyPtr = scopesCopy.c_str(); + EXPECT_NE(scopesPtr, scopesCopyPtr); + EXPECT_EQ(scopes, scopesCopy); + } + + Credentials::Credential::Internal::SetScopes(clientSecretCredential, scopesCopy); + + EXPECT_EQ(clientSecretCredential.GetTenantId(), tenantId); + EXPECT_EQ(clientSecretCredential.GetClientId(), clientId); + EXPECT_EQ(clientSecretCredential.GetClientSecret(), clientSecret); + + auto const tokenReceived + = Credentials::TokenCredential::Internal::GetToken(clientSecretCredential); + + EXPECT_EQ(tokenReceived, newToken); + EXPECT_EQ(clientSecretCredential.GetTokenString(), newToken); + EXPECT_EQ(clientSecretCredential.GetScopes(), scopes); + EXPECT_EQ(clientSecretCredential.GetExpiration(), newTime); + } + + // Updating scopes does reset the token + { + clientSecretCredential.IsExpired = false; + + std::string const anotherScopes = "anotherScopes"; + std::string const anotherToken = "anotherToken"; + auto const anotherTime = newTime + std::chrono::minutes(10); + + clientSecretCredential.NewTokenString = anotherToken; + clientSecretCredential.NewExpiration = anotherTime; + + auto tokenReceived + = Credentials::TokenCredential::Internal::GetToken(clientSecretCredential); + + EXPECT_EQ(tokenReceived, newToken); + EXPECT_EQ(clientSecretCredential.GetTokenString(), newToken); + EXPECT_EQ(clientSecretCredential.GetScopes(), scopes); + EXPECT_EQ(clientSecretCredential.GetExpiration(), newTime); + + Credentials::Credential::Internal::SetScopes( + clientSecretCredential, std::string(anotherScopes)); + + EXPECT_EQ(clientSecretCredential.GetTenantId(), tenantId); + EXPECT_EQ(clientSecretCredential.GetClientId(), clientId); + EXPECT_EQ(clientSecretCredential.GetClientSecret(), clientSecret); + EXPECT_EQ(clientSecretCredential.GetScopes(), anotherScopes); + EXPECT_EQ(clientSecretCredential.IsTokenPtrNull(), true); + + tokenReceived + = Credentials::TokenCredential::Internal::GetToken(clientSecretCredential); + + EXPECT_EQ(clientSecretCredential.IsTokenPtrNull(), false); + EXPECT_EQ(tokenReceived, anotherToken); + EXPECT_EQ(clientSecretCredential.GetTokenString(), anotherToken); + EXPECT_EQ(clientSecretCredential.GetScopes(), anotherScopes); + EXPECT_EQ(clientSecretCredential.GetExpiration(), anotherTime); + } + } + } + } + } +} diff --git a/sdk/core/azure-core/test/main.cpp b/sdk/core/azure-core/test/main.cpp index af40883c4..d400b711f 100644 --- a/sdk/core/azure-core/test/main.cpp +++ b/sdk/core/azure-core/test/main.cpp @@ -2,325 +2,9 @@ // SPDX-License-Identifier: MIT #include "gtest/gtest.h" -#include -#include -#include -#include -using namespace Azure::Core; - -TEST(Http_Request, getters) +int main(int argc, char** argv) { - Http::HttpMethod httpMethod = Http::HttpMethod::Get; - std::string url = "http://test.url.com"; - Http::Request req(httpMethod, url); - - // EXPECT_PRED works better than just EQ because it will print values in log - EXPECT_PRED2( - [](Http::HttpMethod a, Http::HttpMethod b) { return a == b; }, req.GetMethod(), httpMethod); - EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, req.GetEncodedUrl(), url); - - EXPECT_NO_THROW(req.AddHeader("name", "value")); - EXPECT_NO_THROW(req.AddHeader("name2", "value2")); - - auto headers = req.GetHeaders(); - - EXPECT_TRUE(headers.count("name")); - EXPECT_TRUE(headers.count("name2")); - EXPECT_FALSE(headers.count("newHeader")); - - auto value = headers.find("name"); - EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value->second, "value"); - auto value2 = headers.find("name2"); - EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value2->second, "value2"); - - // now add to retry headers - req.StartRetry(); - // same headers first, then one new - EXPECT_NO_THROW(req.AddHeader("name", "retryValue")); - EXPECT_NO_THROW(req.AddHeader("name2", "retryValue2")); - EXPECT_NO_THROW(req.AddHeader("newHeader", "new")); - - headers = req.GetHeaders(); - - EXPECT_TRUE(headers.count("name")); - EXPECT_TRUE(headers.count("name2")); - EXPECT_TRUE(headers.count("newHeader")); - - value = headers.find("name"); - EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value->second, "retryValue"); - value2 = headers.find("name2"); - EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value2->second, "retryValue2"); - auto value3 = headers.find("newHeader"); - EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value3->second, "new"); -} - -TEST(Http_Request, query_parameter) -{ - Http::HttpMethod httpMethod = Http::HttpMethod::Put; - std::string url = "http://test.com"; - Http::Request req(httpMethod, url); - - EXPECT_NO_THROW(req.AddQueryParameter("query", "value")); - EXPECT_PRED2( - [](std::string a, std::string b) { return a == b; }, - req.GetEncodedUrl(), - url + "?query=value"); - - std::string url_with_query = "http://test.com?query=1"; - Http::Request req_with_query(httpMethod, url_with_query); - - // ignore if adding same query parameter key that is already in url - EXPECT_NO_THROW(req_with_query.AddQueryParameter("query", "value")); - EXPECT_PRED2( - [](std::string a, std::string b) { return a == b; }, - req_with_query.GetEncodedUrl(), - url_with_query); - - // retry query params testing - req.StartRetry(); - // same query parameter should override previous - EXPECT_NO_THROW(req.AddQueryParameter("query", "retryValue")); - - EXPECT_PRED2( - [](std::string a, std::string b) { return a == b; }, - req.GetEncodedUrl(), - url + "?query=retryValue"); -} - -TEST(Http_Request, add_path) -{ - Http::HttpMethod httpMethod = Http::HttpMethod::Post; - std::string url = "http://test.com"; - Http::Request req(httpMethod, url); - - EXPECT_NO_THROW(req.AppendPath("path")); - EXPECT_PRED2( - [](std::string a, std::string b) { return a == b; }, req.GetEncodedUrl(), url + "/path"); - - EXPECT_NO_THROW(req.AddQueryParameter("query", "value")); - EXPECT_PRED2( - [](std::string a, std::string b) { return a == b; }, - req.GetEncodedUrl(), - url + "/path?query=value"); - - EXPECT_NO_THROW(req.AppendPath("path2")); - EXPECT_PRED2( - [](std::string a, std::string b) { return a == b; }, - req.GetEncodedUrl(), - url + "/path/path2?query=value"); - - EXPECT_NO_THROW(req.AppendPath("path3")); - EXPECT_PRED2( - [](std::string a, std::string b) { return a == b; }, - req.GetEncodedUrl(), - url + "/path/path2/path3?query=value"); -} - -class Azure::Core::Credentials::Details::CredentialTest : public ClientSecretCredential { -public: - CredentialTest( - std::string const& tenantId, - std::string const& clientId, - std::string const& clientSecret) - : ClientSecretCredential(tenantId, clientId, clientSecret) - { - } - - std::string NewTokenString; - std::chrono::system_clock::time_point NewExpiration; - bool IsExpired; - - std::string GetTenantId() const - { - return this->ClientSecretCredential::m_clientSecret->m_tenantId; - } - - std::string GetClientId() const - { - return this->ClientSecretCredential::m_clientSecret->m_clientId; - } - - std::string GetClientSecret() const - { - return this->ClientSecretCredential::m_clientSecret->m_clientSecret; - } - - std::string GetScopes() const { return this->ClientSecretCredential::m_clientSecret->m_scopes; } - - bool IsTokenPtrNull() const { return !this->TokenCredential::m_token; } - - std::string GetTokenString() const { return this->TokenCredential::m_token->m_tokenString; } - - std::chrono::system_clock::time_point GetExpiration() const - { - return this->TokenCredential::m_token->m_expiresAt; - } - -private: - void RefreshToken( - std::string& newTokenString, - std::chrono::system_clock::time_point& newExpiration) override - { - newTokenString = this->NewTokenString; - newExpiration = this->NewExpiration; - } - - bool IsTokenExpired(std::chrono::system_clock::time_point const&) const override - { - return this->IsExpired; - } -}; - -TEST(Credential, ClientSecretCredential) -{ - // Client Secret credential properties - std::string const tenantId = "tenantId"; - std::string const clientId = "clientId"; - std::string const clientSecret = "clientSecret"; - - Credentials::Details::CredentialTest clientSecretCredential(tenantId, clientId, clientSecret); - - EXPECT_EQ(clientSecretCredential.GetTenantId(), tenantId); - EXPECT_EQ(clientSecretCredential.GetClientId(), clientId); - EXPECT_EQ(clientSecretCredential.GetClientSecret(), clientSecret); - - // Token credential - { - auto const emptyString = std::string(); - auto const defaultTime = std::chrono::system_clock::time_point(); - { - // Default values - { - EXPECT_EQ(clientSecretCredential.IsTokenPtrNull(), true); - } - - { - // Set scopes - std::string const scopes = "scope"; - { - Credentials::Credential::Internal::SetScopes(clientSecretCredential, scopes); - EXPECT_EQ(clientSecretCredential.IsTokenPtrNull(), true); - } - - // Get token - { - std::string const olderToken = "olderToken"; - std::string const newToken = "newToken"; - auto const olderTime = defaultTime + std::chrono::minutes(10); - auto const newTime = olderTime + std::chrono::minutes(10); - - { - clientSecretCredential.IsExpired = true; - clientSecretCredential.NewTokenString = olderToken; - clientSecretCredential.NewExpiration = olderTime; - - auto const tokenReceived - = Credentials::TokenCredential::Internal::GetToken(clientSecretCredential); - - EXPECT_EQ(clientSecretCredential.IsTokenPtrNull(), false); - EXPECT_EQ(tokenReceived, olderToken); - EXPECT_EQ(clientSecretCredential.GetTokenString(), olderToken); - EXPECT_EQ(clientSecretCredential.GetScopes(), scopes); - EXPECT_EQ(clientSecretCredential.GetExpiration(), olderTime); - } - - // Attemp to get the token when it is not expired yet - { - clientSecretCredential.IsExpired = false; - clientSecretCredential.NewTokenString = newToken; - clientSecretCredential.NewExpiration = newTime; - - auto const tokenReceived - = Credentials::TokenCredential::Internal::GetToken(clientSecretCredential); - - EXPECT_EQ(tokenReceived, olderToken); - EXPECT_EQ(clientSecretCredential.GetTokenString(), olderToken); - EXPECT_EQ(clientSecretCredential.GetScopes(), scopes); - EXPECT_EQ(clientSecretCredential.GetExpiration(), olderTime); - } - - // Attempt to get token after it expired - { - clientSecretCredential.IsExpired = true; - - auto const tokenReceived - = Credentials::TokenCredential::Internal::GetToken(clientSecretCredential); - - EXPECT_EQ(tokenReceived, newToken); - EXPECT_EQ(clientSecretCredential.GetTokenString(), newToken); - EXPECT_EQ(clientSecretCredential.GetScopes(), scopes); - EXPECT_EQ(clientSecretCredential.GetExpiration(), newTime); - - clientSecretCredential.IsExpired = false; - } - - // Setting the very same scopes set earlier does not reset token - { - std::string const scopesCopy - = scopes.substr(0, scopes.length() / 2) + scopes.substr(scopes.length() / 2); - - { - auto const scopesPtr = scopes.c_str(); - auto const scopesCopyPtr = scopesCopy.c_str(); - EXPECT_NE(scopesPtr, scopesCopyPtr); - EXPECT_EQ(scopes, scopesCopy); - } - - Credentials::Credential::Internal::SetScopes(clientSecretCredential, scopesCopy); - - EXPECT_EQ(clientSecretCredential.GetTenantId(), tenantId); - EXPECT_EQ(clientSecretCredential.GetClientId(), clientId); - EXPECT_EQ(clientSecretCredential.GetClientSecret(), clientSecret); - - auto const tokenReceived - = Credentials::TokenCredential::Internal::GetToken(clientSecretCredential); - - EXPECT_EQ(tokenReceived, newToken); - EXPECT_EQ(clientSecretCredential.GetTokenString(), newToken); - EXPECT_EQ(clientSecretCredential.GetScopes(), scopes); - EXPECT_EQ(clientSecretCredential.GetExpiration(), newTime); - } - - // Updating scopes does reset the token - { - clientSecretCredential.IsExpired = false; - - std::string const anotherScopes = "anotherScopes"; - std::string const anotherToken = "anotherToken"; - auto const anotherTime = newTime + std::chrono::minutes(10); - - clientSecretCredential.NewTokenString = anotherToken; - clientSecretCredential.NewExpiration = anotherTime; - - auto tokenReceived - = Credentials::TokenCredential::Internal::GetToken(clientSecretCredential); - - EXPECT_EQ(tokenReceived, newToken); - EXPECT_EQ(clientSecretCredential.GetTokenString(), newToken); - EXPECT_EQ(clientSecretCredential.GetScopes(), scopes); - EXPECT_EQ(clientSecretCredential.GetExpiration(), newTime); - - Credentials::Credential::Internal::SetScopes( - clientSecretCredential, std::string(anotherScopes)); - - EXPECT_EQ(clientSecretCredential.GetTenantId(), tenantId); - EXPECT_EQ(clientSecretCredential.GetClientId(), clientId); - EXPECT_EQ(clientSecretCredential.GetClientSecret(), clientSecret); - EXPECT_EQ(clientSecretCredential.GetScopes(), anotherScopes); - EXPECT_EQ(clientSecretCredential.IsTokenPtrNull(), true); - - tokenReceived - = Credentials::TokenCredential::Internal::GetToken(clientSecretCredential); - - EXPECT_EQ(clientSecretCredential.IsTokenPtrNull(), false); - EXPECT_EQ(tokenReceived, anotherToken); - EXPECT_EQ(clientSecretCredential.GetTokenString(), anotherToken); - EXPECT_EQ(clientSecretCredential.GetScopes(), anotherScopes); - EXPECT_EQ(clientSecretCredential.GetExpiration(), anotherTime); - } - } - } - } - } -} + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/sdk/core/azure-core/test/nullable.cpp b/sdk/core/azure-core/test/nullable.cpp new file mode 100644 index 000000000..272869bfc --- /dev/null +++ b/sdk/core/azure-core/test/nullable.cpp @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "gtest/gtest.h" +#include +#include +#include + +using namespace Azure::Core; + +TEST(Nullable, Basic) +{ + Nullable testString{"hello world"}; + EXPECT_TRUE(testString.HasValue()); + EXPECT_TRUE(testString.GetValue() == "hello world"); + + Nullable testInt{54321}; + EXPECT_TRUE(testInt.HasValue()); + EXPECT_TRUE(testInt.GetValue() == 54321); + + Nullable testDouble{10.0}; + EXPECT_TRUE(testDouble.HasValue()); + EXPECT_TRUE(testDouble.GetValue() == 10.0); +} + +TEST(Nullable, Empty) +{ + Nullable testString{}; + EXPECT_FALSE(testString.HasValue()); + EXPECT_TRUE(!testString); + + Nullable testString2; + EXPECT_FALSE(testString2.HasValue()); + EXPECT_TRUE(!testString2); + + Nullable testInt{}; + EXPECT_FALSE(testInt.HasValue()); + EXPECT_TRUE(!testInt); + + Nullable testInt2; + EXPECT_FALSE(testInt2.HasValue()); + EXPECT_TRUE(!testInt2); + + Nullable testDouble{}; + EXPECT_FALSE(testDouble.HasValue()); + EXPECT_TRUE(!testDouble); + + Nullable testDouble2; + EXPECT_FALSE(testDouble2.HasValue()); + EXPECT_TRUE(!testDouble2); +} + +TEST(Nullable, Assignment) +{ + Nullable instance{"hello world"}; + auto instance2 = instance; + EXPECT_TRUE(instance2.HasValue()); + EXPECT_TRUE(instance2.GetValue() == "hello world"); + + auto instance3 = std::move(instance); + EXPECT_TRUE(instance3.HasValue()); + EXPECT_TRUE(instance3.GetValue() == "hello world"); + + EXPECT_TRUE(instance.HasValue()); + + // This is not a guarantee that the string will be empty + // It is an implementation detail that the contents are moved + // Should a future compiler change this assumption this test will need updates + EXPECT_TRUE(instance.GetValue() == ""); + EXPECT_TRUE(instance.HasValue()); +} + +TEST(Nullable, ValueAssignment) +{ + Nullable intVal; + EXPECT_FALSE(intVal.HasValue()); + intVal = 7; + EXPECT_TRUE(intVal.HasValue()); + EXPECT_TRUE(intVal.GetValue() == 7); + + Nullable doubleVal; + EXPECT_FALSE(doubleVal.HasValue()); + doubleVal = 10.12345; + EXPECT_TRUE(doubleVal.HasValue()); + EXPECT_TRUE(doubleVal.GetValue() == 10.12345); + + Nullable strVal; + EXPECT_FALSE(strVal.HasValue()); + strVal = std::string("Hello World"); + EXPECT_TRUE(strVal.HasValue()); + EXPECT_TRUE(strVal.GetValue() == "Hello World"); + + strVal = "New String"; + EXPECT_TRUE(strVal.GetValue() == "New String"); + + strVal.Reset(); + EXPECT_FALSE(strVal.HasValue()); +} + +TEST(Nullable, Swap) +{ + Nullable val1; + Nullable val2; + Nullable val3(12345); + Nullable val4(678910); + + val1.Swap(val2); + EXPECT_FALSE(val1); + EXPECT_FALSE(val2); + + val3.Swap(val4); + EXPECT_TRUE(val3); + EXPECT_TRUE(val4); + EXPECT_TRUE(val3.GetValue() == 678910); + EXPECT_TRUE(val4.GetValue() == 12345); + + val1.Swap(val3); + EXPECT_TRUE(val1); + EXPECT_FALSE(val3); + EXPECT_TRUE(val1.GetValue() == 678910); + EXPECT_FALSE(val3.HasValue()); +} + +TEST(Nullable, CopyConstruction) +{ + //Empty + Nullable val1; + Nullable val2(val1); + EXPECT_FALSE(val1); + EXPECT_FALSE(val2); + + //Non-Empty + Nullable val3(12345); + Nullable val4(val3); + EXPECT_TRUE(val3); + EXPECT_TRUE(val4); + EXPECT_TRUE(val3.GetValue() == 12345); + EXPECT_TRUE(val4.GetValue() == 12345); + + //Literal + Nullable val5 = 54321; + EXPECT_TRUE(val5); + EXPECT_TRUE(val5.GetValue() == 54321); + + //Value + const int i = 1; + Nullable val6(i); + EXPECT_TRUE(val6); + EXPECT_TRUE(val6.GetValue() == 1); + +} + +TEST(Nullable, Disengage) +{ + Nullable val1(12345); + val1.Reset(); + EXPECT_FALSE(val1); +} + +TEST(Nullable, ValueOr) +{ + Nullable val1(12345); + Nullable val2; + + EXPECT_TRUE(val1); + EXPECT_TRUE(val1.ValueOr(678910) == 12345); + // Ensure the value was unmodified in ValueOr + EXPECT_TRUE(val1.GetValue() == 12345); + + EXPECT_FALSE(val2); + EXPECT_TRUE(val2.ValueOr(678910) == 678910); + // Ensure val2 is still disengaged after call to ValueOr + EXPECT_FALSE(val2); + +} +