Nullable type (#204)

* Nullable type
Add Nullable type and tests
This commit is contained in:
Rick Winter 2020-06-25 13:58:27 -07:00 committed by GitHub
parent 6a4a092c61
commit 692cab5651
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 770 additions and 348 deletions

View File

@ -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": []
}
]
}

View File

@ -0,0 +1,235 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#pragma once
#include <cstdlib> // for abort
#include <new> // for placement new
#include <type_traits>
#include <utility> // for swap and move
namespace Azure { namespace Core {
namespace Details {
struct NontrivialEmptyType
{
constexpr NontrivialEmptyType() noexcept {}
};
} // namespace Details
template <class T> 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<T>::value)
: m_hasValue(other.m_hasValue)
{
if (m_hasValue)
{
::new (static_cast<void*>(&m_value)) T(other.m_value);
}
}
Nullable(Nullable&& other) noexcept(std::is_nothrow_move_constructible<T>::value)
: m_hasValue(other.m_hasValue)
{
if (m_hasValue)
{
::new (static_cast<void*>(&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<T>::value)
{
if (m_hasValue)
{
if (other.m_hasValue)
{
using std::swap;
swap(m_value, other.m_value);
}
else
{
::new (static_cast<void*>(&other.m_value)) T(std::move(m_value)); // throws
other.m_hasValue = true;
Reset();
}
}
else if (other.m_hasValue)
{
::new (static_cast<void*>(&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<T>::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<T>::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<typename std::remove_reference<U>::type>::type>::
value // Avoid repeated assignment
&& !(
std::is_scalar<U>::value
&& std::is_same<T, typename std::decay<U>::type>::value) // Avoid repeated
// assignment of
// equivallent scaler
// types
&& std::is_constructible<T, U>::value // Ensure the type is constructible
&& std::is_assignable<T&, U>::value, // Ensure the type is assignable
int>::type
= 0>
Nullable& operator=(U&& other) noexcept(
std::is_nothrow_constructible<T, U>::value&& std::is_nothrow_assignable<T&, U>::value)
{
if (m_hasValue)
{
m_value = std::forward<U>(other);
}
else
{
::new (static_cast<void*>(&m_value)) T(std::forward<U>(other));
m_hasValue = true;
}
return *this;
}
template <class... U>
T& Emplace(U&&... Args) noexcept(std::is_nothrow_constructible<T, U...>::value)
{
Reset();
::new (static_cast<void*>(&m_value)) T(std::forward<U>(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<T>::type> && std::is_convertible_v<U, T>,
int>::type
= 0>
constexpr typename std::remove_cv<T>::type ValueOr(U&& other) const&
{
if (m_hasValue)
{
return m_value;
}
return static_cast<typename std::remove_cv<T>::type>(std::forward<U>(other));
}
template <
class U = T,
typename std::enable_if<
std::is_convertible_v<
T,
typename std::remove_cv<T>::type> && std::is_convertible_v<U, T>,
int>::type
= 0>
constexpr typename std::remove_cv<T>::type ValueOr(U&& other) &&
{
if (m_hasValue)
{
return std::move(m_value);
}
return static_cast<typename std::remove_cv<T>::type>(std::forward<U>(other));
}
};
}} // namespace Azure::Core

View File

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

View File

@ -0,0 +1,326 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "gtest/gtest.h"
#include <http/http.hpp>
#include <internal/credentials_internal.hpp>
#include <string>
#include <vector>
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);
}
}
}
}
}
}

View File

@ -2,325 +2,9 @@
// SPDX-License-Identifier: MIT
#include "gtest/gtest.h"
#include <http/http.hpp>
#include <internal/credentials_internal.hpp>
#include <string>
#include <vector>
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();
}

View File

@ -0,0 +1,176 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "gtest/gtest.h"
#include <nullable.hpp>
#include <string>
#include <vector>
using namespace Azure::Core;
TEST(Nullable, Basic)
{
Nullable<std::string> testString{"hello world"};
EXPECT_TRUE(testString.HasValue());
EXPECT_TRUE(testString.GetValue() == "hello world");
Nullable<int> testInt{54321};
EXPECT_TRUE(testInt.HasValue());
EXPECT_TRUE(testInt.GetValue() == 54321);
Nullable<double> testDouble{10.0};
EXPECT_TRUE(testDouble.HasValue());
EXPECT_TRUE(testDouble.GetValue() == 10.0);
}
TEST(Nullable, Empty)
{
Nullable<std::string> testString{};
EXPECT_FALSE(testString.HasValue());
EXPECT_TRUE(!testString);
Nullable<std::string> testString2;
EXPECT_FALSE(testString2.HasValue());
EXPECT_TRUE(!testString2);
Nullable<int> testInt{};
EXPECT_FALSE(testInt.HasValue());
EXPECT_TRUE(!testInt);
Nullable<int> testInt2;
EXPECT_FALSE(testInt2.HasValue());
EXPECT_TRUE(!testInt2);
Nullable<double> testDouble{};
EXPECT_FALSE(testDouble.HasValue());
EXPECT_TRUE(!testDouble);
Nullable<double> testDouble2;
EXPECT_FALSE(testDouble2.HasValue());
EXPECT_TRUE(!testDouble2);
}
TEST(Nullable, Assignment)
{
Nullable<std::string> 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<int> intVal;
EXPECT_FALSE(intVal.HasValue());
intVal = 7;
EXPECT_TRUE(intVal.HasValue());
EXPECT_TRUE(intVal.GetValue() == 7);
Nullable<double> doubleVal;
EXPECT_FALSE(doubleVal.HasValue());
doubleVal = 10.12345;
EXPECT_TRUE(doubleVal.HasValue());
EXPECT_TRUE(doubleVal.GetValue() == 10.12345);
Nullable<std::string> 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<int> val1;
Nullable<int> val2;
Nullable<int> val3(12345);
Nullable<int> 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<int> val1;
Nullable<int> val2(val1);
EXPECT_FALSE(val1);
EXPECT_FALSE(val2);
//Non-Empty
Nullable<int> val3(12345);
Nullable<int> val4(val3);
EXPECT_TRUE(val3);
EXPECT_TRUE(val4);
EXPECT_TRUE(val3.GetValue() == 12345);
EXPECT_TRUE(val4.GetValue() == 12345);
//Literal
Nullable<int> val5 = 54321;
EXPECT_TRUE(val5);
EXPECT_TRUE(val5.GetValue() == 54321);
//Value
const int i = 1;
Nullable<int> val6(i);
EXPECT_TRUE(val6);
EXPECT_TRUE(val6.GetValue() == 1);
}
TEST(Nullable, Disengage)
{
Nullable<int> val1(12345);
val1.Reset();
EXPECT_FALSE(val1);
}
TEST(Nullable, ValueOr)
{
Nullable<int> val1(12345);
Nullable<int> 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);
}