Add core-test recording-playback (#2685)

Recording feature
This commit is contained in:
Victor Vazquez 2021-08-09 15:38:57 -07:00 committed by GitHub
parent b91dce9deb
commit 0c041c9bfa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1030 additions and 19 deletions

View File

@ -12,3 +12,8 @@ add_subdirectory(azure-core)
if (BUILD_PERFORMANCE_TESTS)
add_subdirectory(perf)
endif()
# Playback & record framework (a.k.a. azure-core-test)
if (BUILD_TESTING)
add_subdirectory(azure-core-test)
endif()

View File

@ -0,0 +1,45 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# SPDX-License-Identifier: MIT
cmake_minimum_required (VERSION 3.13)
set(TARGET_NAME "azure-core-test-fw")
project(azure-core-test-fw LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(
AZURE_CORE_TEST_HEADER
inc/azure/core/test/interceptor_manager.hpp
inc/azure/core/test/network_models.hpp
inc/azure/core/test/playback_http_client.hpp
inc/azure/core/test/record_network_call_policy.hpp
inc/azure/core/test/test_base.hpp
inc/azure/core/test/test_context_manager.hpp
)
set(
AZURE_CORE_TEST_SOURCE
src/private/environment.hpp
src/private/package_version.hpp
src/environment.cpp
src/interceptor_manager.cpp
src/playback_http_transport.cpp
src/record_policy.cpp
src/test_base.cpp
)
add_library (
azure-core-test-fw
${AZURE_CORE_TEST_HEADER}
${AZURE_CORE_TEST_SOURCE}
)
target_include_directories (azure-core-test-fw
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc>
$<INSTALL_INTERFACE:include/az_core_test>)
# make sure that users can consume the project as a library.
add_library (Azure::Core::Test ALIAS azure-core-test-fw)
target_link_libraries(azure-core-test-fw PRIVATE azure-core gtest)

View File

@ -0,0 +1,32 @@
azure-sdk-for-cpp
NOTICES AND INFORMATION
Do Not Translate or Localize
This software incorporates material from third parties. Microsoft makes certain
open source code available at https://3rdpartysource.microsoft.com, or you may
send a check or money order for US $5.00, including the product name, the open
source component name, and version number, to:
Source Code Compliance Team
Microsoft Corporation
One Microsoft Way
Redmond, WA 98052
USA
Notwithstanding any other terms, you may reverse engineer this software to the
extent required to debug changes to any libraries licensed under the GNU Lesser
General Public License.
------------------------------------------------------------------------------
Azure SDK for C++ uses third-party libraries or other resources that may be
distributed under licenses different than the Azure SDK for C++ software.
In the event that we accidentally failed to list a required notice, please
bring it to our attention. Post an issue or email us:
azcppsdkhelp@microsoft.com
The attached notices are provided for information only.

View File

@ -0,0 +1,53 @@
# Azure SDK Core test Library for C++
Azure::Core::Test (`azure-core-test`) provides shared primitives, abstractions, and helpers for modern Azure SDK client test libraries written in the C++. These libraries follow the [Azure SDK Design Guidelines for C++][azure_sdk_cpp_development_guidelines].
## Getting started
## Key concepts
## Contributing
For details on contributing to this repository, see the [contributing guide][azure_sdk_for_cpp_contributing].
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.microsoft.com.
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
### Additional Helpful Links for Contributors
Many people all over the world have helped make this project better. You'll want to check out:
* [What are some good first issues for new contributors to the repo?](https://github.com/azure/azure-sdk-for-cpp/issues?q=is%3Aopen+is%3Aissue+label%3A%22up+for+grabs%22)
* [How to build and test your change][azure_sdk_for_cpp_contributing_developer_guide]
* [How you can make a change happen!][azure_sdk_for_cpp_contributing_pull_requests]
* Frequently Asked Questions (FAQ) and Conceptual Topics in the detailed [Azure SDK for C++ wiki](https://github.com/azure/azure-sdk-for-cpp/wiki).
<!-- ### Community-->
### Reporting security issues and security bugs
Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) <secure@microsoft.com>. You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Further information, including the MSRC PGP key, can be found in the [Security TechCenter](https://www.microsoft.com/msrc/faqs-report-an-issue).
### License
Azure SDK for C++ is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-cpp/blob/master/sdk/core/azure-core/LICENSE) license.
<!-- LINKS -->
[azure_sdk_for_cpp_contributing]: https://github.com/Azure/azure-sdk-for-cpp/blob/master/CONTRIBUTING.md
[azure_sdk_for_cpp_contributing_developer_guide]: https://github.com/Azure/azure-sdk-for-cpp/blob/master/CONTRIBUTING.md#developer-guide
[azure_sdk_for_cpp_contributing_pull_requests]: https://github.com/Azure/azure-sdk-for-cpp/blob/master/CONTRIBUTING.md#pull-requests
[azure_sdk_cpp_development_guidelines]: https://azure.github.io/azure-sdk/cpp_introduction.html
[azure_cli]: https://docs.microsoft.com/cli/azure
[azure_pattern_circuit_breaker]: https://docs.microsoft.com/azure/architecture/patterns/circuit-breaker
[azure_pattern_retry]: https://docs.microsoft.com/azure/architecture/patterns/retry
[azure_portal]: https://portal.azure.com
[azure_sub]: https://azure.microsoft.com/free/
[c_compiler]: https://visualstudio.microsoft.com/vs/features/cplusplus/
[cloud_shell]: https://docs.microsoft.com/azure/cloud-shell/overview
[cloud_shell_bash]: https://shell.azure.com/bash

View File

@ -0,0 +1,25 @@
{
"Registrations": [
{
"Component": {
"Type": "git",
"git": {
"RepositoryUrl": "https://github.com/google/googletest",
"CommitHash": "703bd9caab50b139428cea1aaff9974ebee5742e"
}
},
"DevelopmentDependency": true
},
{
"Component": {
"Type": "other",
"Other": {
"Name": "clang-format",
"Version": "9.0.0-2",
"DownloadUrl": "https://ubuntu.pkgs.org/18.04/ubuntu-updates-universe-amd64/clang-format-9_9-2~ubuntu18.04.2_amd64.deb.html"
}
},
"DevelopmentDependency": true
}
]
}

View File

@ -0,0 +1,102 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @brief Keep the state of the playback-record-live tests.
*
* @remark The interceptor is a singleton that is init during the test configuration.
* Depending on the test mode, the interceptor will handle the recorder data.
*
* - If test mode is LIVE, the Interceptor will not affect the test behavior.
* - If test mode is RECORD, the Interceptor will init the `record data` to be written after
* capturing each request going out to the network and also recording the server response for that
* request.
* - If test mode is PLAYBACK, the interceptor will load the `record data` and use it to answer HTTP
* client request without sending the request to the network.
*
* @remark The interceptor handle the `recorded data, provides the HTTP transport adapter and the
* record policy. However, adding the policy and adapter to a pipeline is done by the user.
*/
#pragma once
#include <azure/core/http/policies/policy.hpp>
#include <memory>
#include <string>
#include "azure/core/test/network_models.hpp"
#include "azure/core/test/playback_http_client.hpp"
#include "azure/core/test/record_network_call_policy.hpp"
#include "azure/core/test/test_context_manager.hpp"
namespace Azure { namespace Core { namespace Test {
/**
* @brief A class that keeps track of network calls by either reading the data from an existing
* test session record or recording the network calls in memory.
*
*/
class InterceptorManager {
private:
Azure::Core::Test::RecordedData m_recordedData;
// Using a reference because the context lives in the test_base class and we don't want to make
// a copy.
Azure::Core::Test::TestContextManager& m_testContext;
public:
/**
* @brief Enables to init an interceptor with empty values.
*
*/
InterceptorManager(Azure::Core::Test::TestContextManager& testContext)
: m_testContext(testContext)
{
}
/**
* Gets the recorded data reference that InterceptorManager is keeping track of.
*
* @return The recorded data reference managed by InterceptorManager.
*/
Azure::Core::Test::RecordedData& GetRecordedData() { return m_recordedData; }
/**
* Gets HTTP pipeline policy that records network calls and its data is managed by the
* InterceptorManager.
*
* @return HttpPipelinePolicy to record network calls.
*/
std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy> GetRecordPolicy()
{
return std::make_unique<Azure::Core::Test::RecordNetworkCallPolicy>(this);
}
/**
* Gets a new HTTP client that plays back test session records managed by {@link
* InterceptorManager}.
*
* @return An HTTP client that plays back network calls from its recorded data.
*/
std::unique_ptr<Azure::Core::Http::HttpTransport> GetPlaybackClient()
{
return std::make_unique<Azure::Core::Test::PlaybackClient>(this);
}
/**
* @brief Get the Test Context object.
*
* @return Azure::Core::Test::TestContextManager const&
*/
Azure::Core::Test::TestContextManager const& GetTestContext() const { return m_testContext; }
/**
* @brief Read from environment and parse the a test mode.
*
* @remark If the AZURE_TEST_MODE variable is not found, default test mode is LIVE mode.
*
* @return TestMode
*/
static TestMode GetTestMode();
};
}}} // namespace Azure::Core::Test

View File

@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @brief Defines the network models for recording HTTP requests from the network.
*
*/
#pragma once
#include <list>
#include <map>
#include <string>
namespace Azure { namespace Core { namespace Test {
/**
* @brief The mode how the tests cases wil behave.
*
*/
enum class TestMode
{
PLAYBACK,
RECORD,
LIVE,
};
/**
* @brief Keeps track of network call records from each unit test session.
*
*/
struct NetworkCallRecord
{
std::string Method;
std::string Url;
std::map<std::string, std::string> Headers;
std::map<std::string, std::string> Response;
};
/**
* @brief Keeps track of the network calls and variable names that were made in a test
* session.
*
*/
class RecordedData {
public:
std::list<NetworkCallRecord> NetworkCallRecords;
std::list<std::string> Variables;
};
}}} // namespace Azure::Core::Test

View File

@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @brief HTTP client that plays back NetworkCallRecord NetworkCallRecords.
*/
#pragma once
#include <string>
#include <azure/core/context.hpp>
#include <azure/core/http/http.hpp>
#include <azure/core/http/policies/policy.hpp>
#include <azure/core/response.hpp>
#include "azure/core/test/network_models.hpp"
namespace Azure { namespace Core { namespace Test {
// Partial class. Required to reference the Interceptor that is defined in the implementation.
class InterceptorManager;
/**
* @brief Creates an HTTP Transport adapter that answer to requests using recorded data.
*
*/
class PlaybackClient : public Azure::Core::Http::HttpTransport {
private:
Azure::Core::Test::InterceptorManager* m_interceptorManager;
public:
/**
* @brief Construct a new Playback Client that uses \p recordedData to answer to the HTTP
* request.
*
* @param interceptorManager A reference to the interceptor manager holding the recorded data.
*/
PlaybackClient(Azure::Core::Test::InterceptorManager* interceptorManager)
: m_interceptorManager(interceptorManager)
{
}
/**
* @brief Override the HTTPTransport `send` contract.
*
* @param context The context that can cancel the request.
* @param request The HTTP request details.
* @return The HTTP raw response containing code, headers and payload.
*/
std::unique_ptr<Azure::Core::Http::RawResponse> Send(
Azure::Core::Http::Request& request,
Azure::Core::Context const& context) override;
};
}}} // namespace Azure::Core::Test

View File

@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @brief HTTP Pipeline policy that keeps track of each HTTP request and response that
* flows through the pipeline.
*/
#pragma once
#include <memory>
#include <string>
#include <azure/core/context.hpp>
#include <azure/core/http/http.hpp>
#include <azure/core/http/policies/policy.hpp>
#include <azure/core/response.hpp>
#include "azure/core/test/network_models.hpp"
namespace Azure { namespace Core { namespace Test {
// Partial class. Required to reference the Interceptor that is defined in the implementation.
class InterceptorManager;
/**
* @brief Creates a policy that records network calls into recordedData.
*
*/
class RecordNetworkCallPolicy : public Azure::Core::Http::Policies::HttpPolicy {
private:
Azure::Core::Test::InterceptorManager* m_interceptorManager;
public:
/**
* @brief Disable default constructor.
*
*/
RecordNetworkCallPolicy() = delete;
/**
* @brief Construct the record network policy which will save the HTTP request and response to
* the \p recordedData.
*
* @param interceptorManager A reference to the interceptor manager which holds the recorded
* data.
*/
RecordNetworkCallPolicy(Azure::Core::Test::InterceptorManager* interceptorManager)
: m_interceptorManager(interceptorManager)
{
}
/**
* @brief Cronstructs a new record network policy with the same recorded data.
*
* @return A record network policy with the same recorded data.
*/
std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy> Clone() const override
{
return std::make_unique<RecordNetworkCallPolicy>(m_interceptorManager);
}
/**
* @brief Record HTTP data from request, then call next policy. Record HTTP response before
* returning.
*
* @param ctx The context while sending the request to the network.
* @param request The HTTP request details.
* @param nextHttpPolicy The next policy in the pipeline to be called.
* @return The HTTP raw response from the network after it is recorded.
*/
std::unique_ptr<Azure::Core::Http::RawResponse> Send(
Azure::Core::Http::Request& request,
Azure::Core::Http::Policies::NextHttpPolicy nextHttpPolicy,
const Azure::Core::Context& ctx) const override;
};
}}} // namespace Azure::Core::Test

View File

@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* Base class for running live and playback tests using the interceptor manager
*/
#include <gtest/gtest.h>
#include "azure/core/test/interceptor_manager.hpp"
#include "azure/core/test/network_models.hpp"
#include "azure/core/test/test_context_manager.hpp"
#include <memory>
namespace Azure { namespace Core { namespace Test {
/**
* @brief The base class provides the tools for a test to use the Record&PlayBack functionalities.
*
*/
class TestBase : public ::testing::Test {
protected:
Azure::Core::Test::TestContextManager m_testContext;
std::unique_ptr<Azure::Core::Test::InterceptorManager> m_interceptor;
/**
* @brief Run before each test.
*
*/
void SetUpBase(std::string const& recordingPath)
{
// Use the test info to init the test context and interceptor.
auto testNameInfo = ::testing::UnitTest::GetInstance()->current_test_info();
// set the interceptor for the current test
m_testContext.RenameTest(testNameInfo->test_suite_name(), testNameInfo->name());
m_testContext.RecordingPath = recordingPath;
m_testContext.TestMode = Azure::Core::Test::InterceptorManager::GetTestMode();
m_interceptor = std::make_unique<Azure::Core::Test::InterceptorManager>(m_testContext);
}
/**
* @brief Run after each test
*
*/
void TearDown() override;
};
}}} // namespace Azure::Core::Test

View File

@ -0,0 +1,139 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* This class handles managing context about a test.
*/
#pragma once
#include <gtest/gtest.h>
#include <string>
#include "azure/core/test/network_models.hpp"
namespace Azure { namespace Core { namespace Test {
/**
* @brief The test context is used to hold information about a test, such as the recording path,
* running test mode, name, etc.
*
*/
class TestContextManager {
public:
/**
* @brief The path where the tests recordings are written.
*
*/
std::string RecordingPath;
/**
* @brief The mode how the test is running.
*
*/
Azure::Core::Test::TestMode TestMode;
/**
* @brief Whenever the test must be ran on live mode only.
*
* @remark This configuration allow tests to ignore the recording or playback setting and run
* without it and amount other tests which are using the recording and playback.
*
*/
bool LiveOnly = false;
/**
* @brief Construct a new Test Context Manager object
*
*/
TestContextManager() { SetLiveOnly(); }
/**
* @brief Change the name of the running test.
*
* @param testName The new name for the test.
*/
void RenameTest(std::string const& testName)
{
m_testName = testName;
SetLiveOnly();
}
/**
* @brief Change the name of the test suite and test name.
*
* @param testSuite The new name for the test suite.
* @param testName The new name for the test name.
*/
void RenameTest(std::string const& testSuite, std::string const& testName)
{
m_testSuite = testSuite;
RenameTest(testName);
}
/**
* @brief The test suite name plus the test name.
*
* @return m_testSuiteName.m_testName
*/
std::string GetTestPlaybackRecordingName() const
{
std::string fullName(m_testSuite);
fullName.append(".");
fullName.append(m_testName);
return fullName;
}
/**
* @brief Get the Test Mode object for the current test.
*
* @return Either LIVE, PLAYBACK or RECORD.
*/
Azure::Core::Test::TestMode GetTestMode() const { return TestMode; }
/**
* @brief Check if test is expected to be recorded.
*
* @return `true` when test is expected to be recorded or `false` when it is not.
*/
bool DoNotRecordTest() const
{
return TestMode != Azure::Core::Test::TestMode::RECORD || LiveOnly;
}
/**
* @brief Whenever the test is running on playback mode.
*
* @return true if the test is using recorded data as server responses.
*/
bool IsPlaybackMode() const { return TestMode == Azure::Core::Test::TestMode::PLAYBACK; }
/**
* @brief Whenever the test is running on live mode.
*
* @return true if the test is not recording or returning recorded data.
*/
bool IsLiveMode() const { return TestMode == Azure::Core::Test::TestMode::LIVE; }
protected:
constexpr static const char* LiveOnlyToken = "LIVE";
private:
std::string m_testName;
std::string m_testSuite;
void SetLiveOnly()
{
// Naming a test with a prefix `LIVE` will set it up to be only live mode supported.
// It won't be recorded and it won't be ran when playback mode is on.
std::string liveOnlyToken(Azure::Core::Test::TestContextManager::LiveOnlyToken);
if (m_testName.size() > liveOnlyToken.size())
{
if (m_testName.find(liveOnlyToken) == 0)
{
LiveOnly = true;
}
}
}
};
}}} // namespace Azure::Core::Test

View File

@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "private/environment.hpp"
#include "azure/core/platform.hpp"
#include <azure/core/internal/strings.hpp>
#include <cstdlib>
#include <stdexcept>
#if defined(AZ_PLATFORM_WINDOWS)
#if !defined(WIN32_LEAN_AND_MEAN)
#define WIN32_LEAN_AND_MEAN
#endif
#if !defined(NOMINMAX)
#define NOMINMAX
#endif
#include <windows.h>
#endif
using namespace Azure::Core::Test::_detail;
std::string Environment::GetVariable(const char* name)
{
#if !defined(WINAPI_PARTITION_DESKTOP) \
|| WINAPI_PARTITION_DESKTOP // See azure/core/platform.hpp for explanation.
#if defined(_MSC_VER)
#pragma warning(push)
// warning C4996: 'getenv': This function or variable may be unsafe. Consider using _dupenv_s
// instead.
#pragma warning(disable : 4996)
#endif
if (auto envVar = std::getenv(name))
{
return std::string(envVar);
}
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#endif
return std::string();
}
Azure::Core::Test::TestMode Environment::GetTestMode()
{
auto value = Environment::GetVariable("AZURE_TEST_MODE");
if (value.empty())
{
return Azure::Core::Test::TestMode::LIVE;
}
if (Azure::Core::_internal::StringExtensions::LocaleInvariantCaseInsensitiveEqual(
value, "RECORD"))
{
return Azure::Core::Test::TestMode::RECORD;
}
else if (Azure::Core::_internal::StringExtensions::LocaleInvariantCaseInsensitiveEqual(
value, "PLAYBACK"))
{
return Azure::Core::Test::TestMode::PLAYBACK;
}
else if (Azure::Core::_internal::StringExtensions::LocaleInvariantCaseInsensitiveEqual(
value, "LIVE"))
{
return Azure::Core::Test::TestMode::LIVE;
}
// unexpected variable value
throw std::runtime_error("Invalid environment variable: " + value);
}

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS // for std::getenv()
#endif
#include <azure/core/internal/strings.hpp>
#include "azure/core/test/interceptor_manager.hpp"
#include "private/environment.hpp"
#include <stdexcept>
Azure::Core::Test::TestMode Azure::Core::Test::InterceptorManager::GetTestMode()
{
return Azure::Core::Test::_detail::Environment::GetTestMode();
}

View File

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <azure/core/http/http.hpp>
#include "azure/core/test/interceptor_manager.hpp"
#include "azure/core/test/playback_http_client.hpp"
#include <string>
std::unique_ptr<Azure::Core::Http::RawResponse> Azure::Core::Test::PlaybackClient::Send(
Azure::Core::Http::Request& request,
Azure::Core::Context const& context)
{
(void)(context);
(void)(request);
(void)m_interceptorManager->GetTestContext().GetTestPlaybackRecordingName();
throw;
}

View File

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#pragma once
#include <azure/core/test/network_models.hpp>
#include <string>
namespace Azure { namespace Core { namespace Test { namespace _detail {
class Environment final {
private:
Environment() = delete;
~Environment() = delete;
public:
static std::string GetVariable(const char* name);
static Azure::Core::Test::TestMode GetTestMode();
};
}}}} // namespace Azure::Core::Test::_detail

View File

@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
* @brief Provides version information.
*/
#pragma once
#include <string>
#define AZURE_CORETEST_VERSION_MAJOR 1
#define AZURE_CORETEST_VERSION_MINOR 0
#define AZURE_CORETEST_VERSION_PATCH 0
#define AZURE_CORETEST_VERSION_PRERELEASE "beta.1"
namespace Azure { namespace Core {
/**
* @brief Provides version information.
*/
class Version {
public:
/// Major numeric identifier.
static constexpr int Major = AZURE_CORETEST_VERSION_MAJOR;
/// Minor numeric identifier.
static constexpr int Minor = AZURE_CORETEST_VERSION_MINOR;
/// Patch numeric identifier.
static constexpr int Patch = AZURE_CORETEST_VERSION_PATCH;
/// Optional pre-release identifier. SDK is in a pre-release state when not empty.
static std::string const PreRelease;
/**
* @brief The version in string format used for telemetry following the `semver.org` standard
* (https://semver.org).
*/
static std::string VersionString();
private:
// To avoid leaking out the #define values we smuggle out the value
// which will later be used to initialize the PreRelease std::string
static constexpr const char* secret = AZURE_CORETEST_VERSION_PRERELEASE;
};
}} // namespace Azure::Core
#undef AZURE_CORETEST_VERSION_MAJOR
#undef AZURE_CORETEST_VERSION_MINOR
#undef AZURE_CORETEST_VERSION_PATCH
#undef AZURE_CORETEST_VERSION_PRERELEASE

View File

@ -0,0 +1,114 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "azure/core/test/interceptor_manager.hpp"
#include "azure/core/test/network_models.hpp"
#include "azure/core/test/record_network_call_policy.hpp"
#include "private/environment.hpp"
#include <azure/core/internal/strings.hpp>
#include <string>
#include <vector>
using namespace Azure::Core::Http::Policies;
using namespace Azure::Core::Http;
using namespace Azure::Core::Test;
using namespace Azure::Core::_internal;
/**
* @brief Records network request and response into RecordedData.
*
* @param ctx The context for canceling the request.
* @param request The HTTP request that is sent.
* @param nextHttpPolicy The next policy in the pipeline.
* @return The HTTP raw response.
*/
std::unique_ptr<RawResponse> RecordNetworkCallPolicy::Send(
Request& request,
NextHttpPolicy nextHttpPolicy,
Context const& ctx) const
{
if (m_interceptorManager->GetTestMode() != TestMode::RECORD)
{
return nextHttpPolicy.Send(request, ctx);
}
// Init recordedRecord
NetworkCallRecord record;
record.Method = request.GetMethod().ToString();
// Capture headers
{
std::vector<std::string> headersToBeCaptured
= {"x-ms-client-request-id", "Content-Type", "x-ms-version", "User-Agent"};
for (auto const& header : request.GetHeaders())
{
if (std::find_if(
headersToBeCaptured.begin(),
headersToBeCaptured.end(),
[header](std::string const& compare) {
return StringExtensions::LocaleInvariantCaseInsensitiveEqual(compare, header.first);
})
!= headersToBeCaptured.end())
record.Headers.emplace(header.first, header.second);
}
}
// Remove sensitive information such as SAS token signatures from the recording.
{
auto const& url = request.GetUrl();
Azure::Core::Url redactedUrl;
redactedUrl.SetScheme(url.GetScheme());
auto host = url.GetHost();
auto hostWithNoAccount = std::find(host.begin(), host.end(), '.');
redactedUrl.SetHost("REDACTED" + std::string(hostWithNoAccount, host.end()));
// Query parameters
for (auto const& qp : url.GetQueryParameters())
{
if (qp.first == "sig")
{
redactedUrl.AppendQueryParameter("sig", "REDACTED");
}
else
{
redactedUrl.AppendQueryParameter(qp.first, qp.second);
}
}
record.Url = redactedUrl.GetAbsoluteUrl();
}
// At this point, the request has been recorded. Send it to capture the response.
auto response = nextHttpPolicy.Send(request, ctx);
// BodyStreams are currently not supported
if (response->ExtractBodyStream() != nullptr)
{
throw std::runtime_error("Record mode don't support recording a body stream");
}
record.Response.emplace(
"STATUS_CODE",
std::to_string(static_cast<typename std::underlying_type<Http::HttpStatusCode>::type>(
response->GetStatusCode())));
for (auto const& header : response->GetHeaders())
{
if (header.first == "x-ms-encryption-key-sha256")
{
record.Response.emplace(header.first, "REDACTED");
}
else
{
record.Response.emplace(header.first, header.second);
}
}
// Capture response
auto const& body = response->GetBody();
record.Response.emplace("BODY", std::string(body.begin(), body.end()));
m_interceptorManager->GetRecordedData().NetworkCallRecords.push_back(record);
return response;
}

View File

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <azure/core/internal/json/json.hpp>
#include "azure/core/test/interceptor_manager.hpp"
#include "azure/core/test/network_models.hpp"
#include "azure/core/test/test_base.hpp"
#include <fstream>
#include <iostream>
using namespace Azure::Core::Json::_internal;
void Azure::Core::Test::TestBase::TearDown()
{
json root;
json records;
auto const& recordData = m_interceptor->GetRecordedData();
for (auto const& record : recordData.NetworkCallRecords)
{
json recordJson;
recordJson["Headers"] = json(record.Headers);
recordJson["Response"] = json(record.Response);
recordJson["Method"] = record.Method;
recordJson["Url"] = record.Url;
records.push_back(recordJson);
}
root["networkCallRecords"] = records;
// Write json to file
std::ofstream outFile;
// AZURE_TEST_RECORDING_DIR is exported from CMAKE
std::string testPath(m_testContext.RecordingPath);
outFile.open(testPath + "/" + m_testContext.GetTestPlaybackRecordingName() + ".json");
outFile << root.dump(2) << std::endl;
outFile.close();
}

View File

@ -15,6 +15,8 @@ while(RANGE LESS 100)
endwhile()
add_compile_definitions(AZURE_TEST_DATA_PATH="${CMAKE_BINARY_DIR}")
add_compile_definitions(AZURE_TEST_RECORDING_DIR="${CMAKE_CURRENT_LIST_DIR}")
project (azure-core-test LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED True)

View File

@ -9,6 +9,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED True)
include(GoogleTest)
# Export the test folder for recordings access.
add_compile_definitions(AZURE_TEST_RECORDING_DIR="${CMAKE_CURRENT_LIST_DIR}")
################## Unit Tests ##########################
add_executable (
azure-security-keyvault-keys-test
@ -54,7 +57,7 @@ if (MSVC)
target_compile_options(azure-security-keyvault-keys-test-live PUBLIC /wd6326 /wd26495 /wd26812 /wd4389)
endif()
target_link_libraries(azure-security-keyvault-keys-test-live PRIVATE azure-security-keyvault-keys azure-identity gtest gmock)
target_link_libraries(azure-security-keyvault-keys-test-live PRIVATE azure-security-keyvault-keys azure-identity azure-core-test-fw gtest gmock)
# Adding private headers so we can test the private APIs with no relative paths include.
target_include_directories (azure-security-keyvault-keys-test-live PRIVATE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../../src>)

View File

@ -24,8 +24,8 @@ using namespace Azure::Core::Json::_internal;
TEST_F(KeyVaultClientTest, BackupKey)
{
KeyClient keyClient(m_keyVaultUrl, m_credential);
std::string keyName = GetUniqueName();
auto const& client = GetClientForTest("BackupKey");
std::cout
<< std::endl
@ -33,40 +33,40 @@ TEST_F(KeyVaultClientTest, BackupKey)
{
std::cout << std::endl << "- Create key";
auto response = keyClient.CreateKey(keyName, KeyVaultKeyType::Ec);
auto response = client.CreateKey(keyName, KeyVaultKeyType::Ec);
CheckValidResponse(response);
}
// backup
std::cout << std::endl << "- Backup key";
auto backUpResponse = keyClient.BackupKey(keyName);
auto backUpResponse = client.BackupKey(keyName);
CheckValidResponse(backUpResponse);
{
// Delete
std::cout << std::endl << "- Delete key";
auto response = keyClient.StartDeleteKey(keyName);
auto response = client.StartDeleteKey(keyName);
response.PollUntilDone(m_testPollingIntervalMinutes);
}
{
// Purge
std::cout << std::endl << "- Purge key";
auto response = keyClient.PurgeDeletedKey(keyName);
auto response = client.PurgeDeletedKey(keyName);
CheckValidResponse(response, Azure::Core::Http::HttpStatusCode::NoContent);
// Purge can take up to 2 min
std::this_thread::sleep_for(std::chrono::minutes(4));
}
{ // Check key is gone
EXPECT_THROW(keyClient.GetKey(keyName), Azure::Core::RequestFailedException);
EXPECT_THROW(client.GetKey(keyName), Azure::Core::RequestFailedException);
}
{
// Restore
std::cout << std::endl << "- Restore key";
auto respone = keyClient.RestoreKeyBackup(backUpResponse.Value.BackupKey);
auto respone = client.RestoreKeyBackup(backUpResponse.Value.BackupKey);
CheckValidResponse(backUpResponse);
}
{
// Check key is restored
auto response = keyClient.GetKey(keyName);
auto response = client.GetKey(keyName);
CheckValidResponse(response);
EXPECT_EQ(keyName, response.Value.Name());
}

View File

@ -13,6 +13,8 @@
#include <azure/identity/client_secret_credential.hpp>
#include <azure/keyvault/keyvault_keys.hpp>
#include <azure/core/test/test_base.hpp>
#include <chrono>
#include <cstdio>
#include <iostream>
@ -20,12 +22,10 @@
namespace Azure { namespace Security { namespace KeyVault { namespace Keys { namespace Test {
class KeyVaultClientTest : public ::testing::TestWithParam<int> {
protected:
int m_testPollingTimeOutMinutes = 20;
std::chrono::minutes m_testPollingIntervalMinutes = std::chrono::minutes(1);
class KeyVaultClientTest : public Azure::Core::Test::TestBase,
public ::testing::WithParamInterface<int> {
private:
std::unique_ptr<Azure::Security::KeyVault::Keys::KeyClient> m_client;
std::string GetEnv(const std::string& name, std::string const& defaultValue = std::string())
{
const char* ret = std::getenv(name.data());
@ -44,14 +44,28 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Keys { nam
}
protected:
int m_testPollingTimeOutMinutes = 20;
std::chrono::minutes m_testPollingIntervalMinutes = std::chrono::minutes(1);
std::shared_ptr<Azure::Identity::ClientSecretCredential> m_credential;
std::string m_keyVaultUrl;
std::string m_keyVaultHsmUrl;
std::unique_ptr<Azure::Security::KeyVault::Keys::KeyClient> m_client;
Azure::Security::KeyVault::Keys::KeyClient const& GetClientForTest(std::string const& testName)
{
// set the interceptor for the current test
m_testContext.RenameTest(testName);
return *m_client;
}
// Create
virtual void SetUp() override
{
// Init interceptor from PlayBackRecorder
std::string recordingPath(AZURE_TEST_RECORDING_DIR);
recordingPath.append("/recordings");
Azure::Core::Test::TestBase::SetUpBase(recordingPath);
std::string tenantId = GetEnv("AZURE_TENANT_ID");
std::string clientId = GetEnv("AZURE_CLIENT_ID");
std::string secretId = GetEnv("AZURE_CLIENT_SECRET");
@ -61,6 +75,22 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Keys { nam
m_keyVaultUrl = GetEnv("AZURE_KEYVAULT_URL");
m_keyVaultHsmUrl = GetEnv("AZURE_KEYVAULT_HSM_URL");
// Create default client for the test
KeyClientOptions options;
// Replace default transport adapter for playback
if (m_testContext.IsPlaybackMode())
{
options.Transport.Transport = m_interceptor->GetPlaybackClient();
}
// Insert Recording policy when Record mode is on (non playback and non LiveMode)
else if (!m_testContext.IsLiveMode())
{
// AZURE_TEST_RECORDING_DIR is exported by CMAKE
options.PerRetryPolicies.push_back(m_interceptor->GetRecordPolicy());
}
m_client = std::make_unique<KeyClient>(m_keyVaultUrl, m_credential, options);
// When running live tests, service can return 429 error response if the client is sending
// multiple requests per second. This can happen if the network is fast and tests are running
// without any delay between them.

View File

@ -16,21 +16,22 @@
using namespace Azure::Security::KeyVault::Keys::Test;
TEST_F(KeyVaultClientTest, CreateKey)
TEST_F(KeyVaultClientTest, CreateKey123)
{
Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
auto keyName = GetUniqueName();
auto const& client
= GetClientForTest(::testing::UnitTest::GetInstance()->current_test_info()->name());
{
auto keyResponse
= keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyVaultKeyType::Ec);
= client.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyVaultKeyType::Ec);
CheckValidResponse(keyResponse);
auto keyVaultKey = keyResponse.Value;
EXPECT_EQ(keyVaultKey.Name(), keyName);
}
{
// Now get the key
auto keyResponse = keyClient.GetKey(keyName);
auto keyResponse = client.GetKey(keyName);
CheckValidResponse(keyResponse);
auto keyVaultKey = keyResponse.Value;
EXPECT_EQ(keyVaultKey.Name(), keyName);