parent
33e56e1bbf
commit
b3cfe0148e
@ -20,6 +20,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <azure/core/http/http.hpp>
|
||||
#include <azure/core/http/policies/policy.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@ -97,6 +98,23 @@ namespace Azure { namespace Core { namespace Test {
|
||||
* @return TestMode
|
||||
*/
|
||||
static TestMode GetTestMode();
|
||||
|
||||
/**
|
||||
* @brief This function is expected to be called by the playback transport adapter.
|
||||
*
|
||||
* @remark The name of the test is known and set when the test is actually started. That's why
|
||||
* the recorded data can't be loaded until the test is already running (Can't load on SetUp).
|
||||
*
|
||||
*/
|
||||
void LoadTestData();
|
||||
|
||||
/**
|
||||
* @brief Removes sensitive info from a request Url.
|
||||
*
|
||||
* @param url The request Url.
|
||||
* @return Azure::Core::Url
|
||||
*/
|
||||
Azure::Core::Url RedactUrl(Azure::Core::Url const& url);
|
||||
};
|
||||
|
||||
}}} // namespace Azure::Core::Test
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#include <azure/core/context.hpp>
|
||||
#include <azure/core/http/http.hpp>
|
||||
#include <azure/core/http/policies/policy.hpp>
|
||||
#include <azure/core/io/body_stream.hpp>
|
||||
#include <azure/core/response.hpp>
|
||||
|
||||
#include "azure/core/test/network_models.hpp"
|
||||
@ -21,6 +22,42 @@ namespace Azure { namespace Core { namespace Test {
|
||||
// Partial class. Required to reference the Interceptor that is defined in the implementation.
|
||||
class InterceptorManager;
|
||||
|
||||
/**
|
||||
* @brief A body stream which holds the memory inside.
|
||||
*
|
||||
* @remark The playback http uses this body stream to be returned as part of the raw response so
|
||||
* the transport policy can read from it.
|
||||
*
|
||||
*/
|
||||
class WithMemoryBodyStream : public Azure::Core::IO::BodyStream {
|
||||
private:
|
||||
std::vector<uint8_t> m_memory;
|
||||
Azure::Core::IO::MemoryBodyStream m_streamer;
|
||||
|
||||
size_t OnRead(uint8_t* buffer, size_t count, Azure::Core::Context const& context) override
|
||||
{
|
||||
return m_streamer.Read(buffer, count, context);
|
||||
};
|
||||
|
||||
public:
|
||||
// Forbid constructor for rval so we don't end up storing dangling ptr
|
||||
WithMemoryBodyStream(std::vector<uint8_t> const&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Construct using vector of bytes.
|
||||
*
|
||||
* @param buffer Vector of bytes with the contents to provide the data from to the readers.
|
||||
*/
|
||||
WithMemoryBodyStream(std::vector<uint8_t> const& buffer)
|
||||
: m_memory(buffer), m_streamer(m_memory)
|
||||
{
|
||||
}
|
||||
|
||||
int64_t Length() const override { return m_streamer.Length(); }
|
||||
|
||||
void Rewind() override { m_streamer.Rewind(); }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Creates an HTTP Transport adapter that answer to requests using recorded data.
|
||||
*
|
||||
|
||||
@ -5,14 +5,75 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS // for std::getenv()
|
||||
#endif
|
||||
|
||||
#include <azure/core/internal/json/json.hpp>
|
||||
#include <azure/core/internal/strings.hpp>
|
||||
|
||||
#include "azure/core/test/interceptor_manager.hpp"
|
||||
#include "private/environment.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
Azure::Core::Test::TestMode Azure::Core::Test::InterceptorManager::GetTestMode()
|
||||
using namespace Azure::Core::Test;
|
||||
using namespace Azure::Core::Json::_internal;
|
||||
using namespace Azure::Core;
|
||||
|
||||
TestMode InterceptorManager::GetTestMode() { return _detail::Environment::GetTestMode(); }
|
||||
|
||||
void InterceptorManager::LoadTestData()
|
||||
{
|
||||
return Azure::Core::Test::_detail::Environment::GetTestMode();
|
||||
if (m_recordedData.NetworkCallRecords.size() > 0)
|
||||
{
|
||||
// TestData was loaded it before.
|
||||
return;
|
||||
}
|
||||
|
||||
std::string const recordingName(
|
||||
m_testContext.RecordingPath + "/" + m_testContext.GetTestPlaybackRecordingName() + ".json");
|
||||
std::ifstream readFile(recordingName);
|
||||
if (!readFile.is_open())
|
||||
{
|
||||
throw std::runtime_error("Can't open recording: " + recordingName);
|
||||
}
|
||||
|
||||
std::string recordingContent(
|
||||
(std::istreambuf_iterator<char>(readFile)), std::istreambuf_iterator<char>());
|
||||
|
||||
auto const jsonRecord = json::parse(recordingContent);
|
||||
auto const networkRecords = jsonRecord["networkCallRecords"];
|
||||
for (auto const& record : networkRecords)
|
||||
{
|
||||
NetworkCallRecord modelRecord;
|
||||
modelRecord.Method = record["Method"];
|
||||
modelRecord.Url = record["Url"];
|
||||
modelRecord.Headers = record["Headers"].get<std::map<std::string, std::string>>();
|
||||
modelRecord.Response = record["Response"].get<std::map<std::string, std::string>>();
|
||||
m_recordedData.NetworkCallRecords.push_back(modelRecord);
|
||||
}
|
||||
readFile.close();
|
||||
}
|
||||
|
||||
Url InterceptorManager::RedactUrl(Url const& url)
|
||||
{
|
||||
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()));
|
||||
redactedUrl.SetPath(url.GetPath());
|
||||
// Query parameters
|
||||
for (auto const& qp : url.GetQueryParameters())
|
||||
{
|
||||
if (qp.first == "sig")
|
||||
{
|
||||
redactedUrl.AppendQueryParameter("sig", "REDACTED");
|
||||
}
|
||||
else
|
||||
{
|
||||
redactedUrl.AppendQueryParameter(qp.first, qp.second);
|
||||
}
|
||||
}
|
||||
return redactedUrl;
|
||||
}
|
||||
|
||||
@ -6,15 +6,62 @@
|
||||
#include "azure/core/test/interceptor_manager.hpp"
|
||||
#include "azure/core/test/playback_http_client.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
std::unique_ptr<Azure::Core::Http::RawResponse> Azure::Core::Test::PlaybackClient::Send(
|
||||
Azure::Core::Http::Request& request,
|
||||
using namespace Azure::Core::Http;
|
||||
using namespace Azure::Core::Test;
|
||||
|
||||
std::unique_ptr<RawResponse> PlaybackClient::Send(
|
||||
Request& request,
|
||||
Azure::Core::Context const& context)
|
||||
{
|
||||
(void)(context);
|
||||
(void)(request);
|
||||
(void)m_interceptorManager->GetTestContext().GetTestPlaybackRecordingName();
|
||||
context.ThrowIfCancelled();
|
||||
|
||||
throw;
|
||||
// The test name can't be known before the test is started. That's why the test data is loaded up
|
||||
// to this point instead of loading it on test SetUp.
|
||||
// The test data will be loaded just one time.
|
||||
m_interceptorManager->LoadTestData();
|
||||
|
||||
auto& recordedData = m_interceptorManager->GetRecordedData();
|
||||
Azure::Core::Url const redactedUrl = m_interceptorManager->RedactUrl(request.GetUrl());
|
||||
|
||||
for (auto record = recordedData.NetworkCallRecords.begin();
|
||||
record != recordedData.NetworkCallRecords.end();)
|
||||
{
|
||||
auto url = redactedUrl.GetAbsoluteUrl();
|
||||
auto m = request.GetMethod().ToString();
|
||||
// Use the first occurrence and remove it from the recording.
|
||||
if (m == record->Method && url == record->Url)
|
||||
{
|
||||
// StatusCode
|
||||
auto const statusCode
|
||||
= HttpStatusCode(std::stoi(record->Response.find("STATUS_CODE")->second));
|
||||
auto response = std::make_unique<RawResponse>(1, 1, statusCode, "recorded response");
|
||||
|
||||
// Headers
|
||||
for (auto const& header : record->Response)
|
||||
{
|
||||
if (header.first != "STATUS_CODE" && header.first != "BODY")
|
||||
{
|
||||
response->SetHeader(header.first, header.second);
|
||||
}
|
||||
}
|
||||
|
||||
// Body
|
||||
auto body = record->Response.find("BODY")->second;
|
||||
std::vector<uint8_t> bodyVector(body.begin(), body.end());
|
||||
response->SetBodyStream(std::make_unique<WithMemoryBodyStream>(bodyVector));
|
||||
|
||||
// take the record out of the recording
|
||||
record = recordedData.NetworkCallRecords.erase(record);
|
||||
|
||||
return response;
|
||||
}
|
||||
++record;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Did not found a response for the request in the recordings.");
|
||||
}
|
||||
|
||||
@ -58,24 +58,7 @@ std::unique_ptr<RawResponse> RecordNetworkCallPolicy::Send(
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
Azure::Core::Url const redactedUrl = m_interceptorManager->RedactUrl(request.GetUrl());
|
||||
record.Url = redactedUrl.GetAbsoluteUrl();
|
||||
}
|
||||
|
||||
|
||||
@ -14,9 +14,23 @@ using namespace Azure::Core::Json::_internal;
|
||||
|
||||
void Azure::Core::Test::TestBase::TearDown()
|
||||
{
|
||||
|
||||
if (m_testContext.IsLiveMode() || m_testContext.IsPlaybackMode())
|
||||
{
|
||||
// Don't want to record here
|
||||
return;
|
||||
}
|
||||
|
||||
json root;
|
||||
json records;
|
||||
auto const& recordData = m_interceptor->GetRecordedData();
|
||||
|
||||
if (recordData.NetworkCallRecords.size() == 0)
|
||||
{
|
||||
// Don't make empty recordings
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto const& record : recordData.NetworkCallRecords)
|
||||
{
|
||||
json recordJson;
|
||||
|
||||
@ -16,9 +16,9 @@
|
||||
|
||||
using namespace Azure::Security::KeyVault::Keys::Test;
|
||||
|
||||
TEST_F(KeyVaultClientTest, CreateKey123)
|
||||
TEST_F(KeyVaultClientTest, CreateKey)
|
||||
{
|
||||
auto keyName = GetUniqueName();
|
||||
auto keyName = "CreateKeyWithThisName";
|
||||
auto const& client
|
||||
= GetClientForTest(::testing::UnitTest::GetInstance()->current_test_info()->name());
|
||||
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
{
|
||||
"networkCallRecords": [
|
||||
{
|
||||
"Headers": {
|
||||
"content-type": "application/json",
|
||||
"user-agent": "azsdk-cpp-keyvault-keys/7.2 (Linux 5.4.0-1048-azure x86_64 #50~18.04.1-Ubuntu SMP Fri May 14 15:30:12 UTC 2021)",
|
||||
"x-ms-client-request-id": "91c8f74c-4a6b-4a4a-5c6f-084d8620c1e3"
|
||||
},
|
||||
"Method": "POST",
|
||||
"Response": {
|
||||
"BODY": "{\"key\":{\"kid\":\"https://doesNotMatter.vault.azure.net/keys/CreateKeyWithThisName/4abbde2456dd4aa5b1cd82d2212cb67c\",\"kty\":\"EC\",\"key_ops\":[\"sign\",\"verify\"],\"crv\":\"P-256\",\"x\":\"sJDOZ-TgRQISPgI8cog7r1GPDr5_5SfCNHW1es3jPTY\",\"y\":\"C2-3YQh16DDe-uw92l40p6lJUI9ZTIMxqIG2wUDqXr0\"},\"attributes\":{\"enabled\":true,\"created\":1628713611,\"updated\":1628713611,\"recoveryLevel\":\"Recoverable+Purgeable\",\"recoverableDays\":90}}",
|
||||
"STATUS_CODE": "200",
|
||||
"cache-control": "no-cache",
|
||||
"content-length": "398",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"date": "Wed, 11 Aug 2021 20:26:51 GMT",
|
||||
"expires": "-1",
|
||||
"pragma": "no-cache",
|
||||
"strict-transport-security": "max-age=31536000;includeSubDomains",
|
||||
"x-content-type-options": "nosniff",
|
||||
"x-ms-client-request-id": "91c8f74c-4a6b-4a4a-5c6f-084d8620c1e3",
|
||||
"x-ms-keyvault-network-info": "conn_type=Ipv4;addr=20.49.4.206;act_addr_fam=InterNetwork;",
|
||||
"x-ms-keyvault-region": "westus2",
|
||||
"x-ms-keyvault-service-version": "1.9.48.0",
|
||||
"x-ms-request-id": "e43b0631-91d0-4c8d-887b-9e08cb8b7eaa",
|
||||
"x-powered-by": "ASP.NET"
|
||||
},
|
||||
"Url": "https://REDACTED.vault.azure.net/keys/CreateKeyWithThisName/create?api-version=7.2"
|
||||
},
|
||||
{
|
||||
"Headers": {
|
||||
"user-agent": "azsdk-cpp-keyvault-keys/7.2 (Linux 5.4.0-1048-azure x86_64 #50~18.04.1-Ubuntu SMP Fri May 14 15:30:12 UTC 2021)",
|
||||
"x-ms-client-request-id": "3737836e-02b0-4077-5841-1959854cdb45"
|
||||
},
|
||||
"Method": "GET",
|
||||
"Response": {
|
||||
"BODY": "{\"key\":{\"kid\":\"https://doesNotMatter.vault.azure.net/keys/CreateKeyWithThisName/4abbde2456dd4aa5b1cd82d2212cb67c\",\"kty\":\"EC\",\"key_ops\":[\"sign\",\"verify\"],\"crv\":\"P-256\",\"x\":\"sJDOZ-TgRQISPgI8cog7r1GPDr5_5SfCNHW1es3jPTY\",\"y\":\"C2-3YQh16DDe-uw92l40p6lJUI9ZTIMxqIG2wUDqXr0\"},\"attributes\":{\"enabled\":true,\"created\":1628713611,\"updated\":1628713611,\"recoveryLevel\":\"Recoverable+Purgeable\",\"recoverableDays\":90}}",
|
||||
"STATUS_CODE": "200",
|
||||
"cache-control": "no-cache",
|
||||
"content-length": "398",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"date": "Wed, 11 Aug 2021 20:26:51 GMT",
|
||||
"expires": "-1",
|
||||
"pragma": "no-cache",
|
||||
"strict-transport-security": "max-age=31536000;includeSubDomains",
|
||||
"x-content-type-options": "nosniff",
|
||||
"x-ms-client-request-id": "3737836e-02b0-4077-5841-1959854cdb45",
|
||||
"x-ms-keyvault-network-info": "conn_type=Ipv4;addr=20.49.4.206;act_addr_fam=InterNetwork;",
|
||||
"x-ms-keyvault-region": "westus2",
|
||||
"x-ms-keyvault-service-version": "1.9.48.0",
|
||||
"x-ms-request-id": "cdd8fe71-5202-46ab-8001-aa5e7c94f8f4",
|
||||
"x-powered-by": "ASP.NET"
|
||||
},
|
||||
"Url": "https://REDACTED.vault.azure.net/keys/CreateKeyWithThisName?api-version=7.2"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user