Skip to content

Commit

Permalink
CurlHandle is now able to returned predefined responses per query use…
Browse files Browse the repository at this point in the history
…ful for tests
  • Loading branch information
sjanel committed May 14, 2023
1 parent 1c67eee commit 6b267ea
Show file tree
Hide file tree
Showing 17 changed files with 272 additions and 139 deletions.
10 changes: 10 additions & 0 deletions data/secret/secret_test.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
{
"bithumb": {
"cha": {
"accountOwner": {
"enName": "ChaSonghyon",
"koName": "차성현"
},
"key": "test0000/0000000000000000+00000000000000000000000+000000",
"private": "abc/defghijkl+mnopqrstuvwxyzABCDEFGHIJKLMNOPQ+RSTUVWXYZ012345678+9/abcdefghijklmnopqrs=="
}
},
"kraken": {
"jack": {
"key": "test1000/0000000000000000+00000000000000000000000+000000",
Expand Down
14 changes: 5 additions & 9 deletions src/api-objects/src/apikeysprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,11 @@ namespace cct::api {
namespace {

std::string_view GetSecretFileName(settings::RunMode runMode) {
switch (runMode) {
case settings::RunMode::kTestKeys:
[[fallthrough]];
case settings::RunMode::kTestKeysWithProxy:
log::info("Test mode activated, shifting to secret_test.json file.");
return "secret_test.json";
default:
return "secret.json";
if (settings::AreTestKeysRequested(runMode)) {
log::info("Test mode activated, shifting to secret_test.json file.");
return "secret_test.json";
}
return "secret.json";
}

} // namespace
Expand Down Expand Up @@ -75,7 +71,7 @@ APIKeysProvider::APIKeysMap APIKeysProvider::ParseAPIKeys(std::string_view dataD
} else {
std::string_view secretFileName = GetSecretFileName(runMode);
File secretsFile(dataDir, File::Type::kSecret, secretFileName,
AreTestKeysRequested(runMode) ? File::IfError::kThrow : File::IfError::kNoThrow);
settings::AreTestKeysRequested(runMode) ? File::IfError::kThrow : File::IfError::kNoThrow);
json jsonData = secretsFile.readAllJson();
for (auto& [publicExchangeName, keyObj] : jsonData.items()) {
const auto& exchangesWithoutSecrets = exchangeSecretsInfo.exchangesWithoutSecrets();
Expand Down
2 changes: 1 addition & 1 deletion src/api/common/src/cryptowatchapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

namespace cct::api {
namespace {
string Query(CurlHandle& curlHandle, std::string_view endpoint, CurlPostData&& postData = CurlPostData()) {
std::string_view Query(CurlHandle& curlHandle, std::string_view endpoint, CurlPostData&& postData = CurlPostData()) {
return curlHandle.query(endpoint,
CurlOptions(HttpRequestType::kGet, std::move(postData), "Cryptowatch C++ API Client"));
}
Expand Down
5 changes: 3 additions & 2 deletions src/api/common/src/fiatconverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ std::optional<double> FiatConverter::queryCurrencyRate(Market mk) {
method.append(opts.getPostData().str());
opts.getPostData().clear();

string dataStr = _curlHandle.query(method, opts);
json data = json::parse(dataStr, nullptr, false /* allow exceptions */);
std::string_view dataStr = _curlHandle.query(method, opts);
static constexpr bool kAllowExceptions = false;
json data = json::parse(dataStr, nullptr, kAllowExceptions);
//{"query":{"count":1},"results":{"EUR_KRW":{"id":"EUR_KRW","val":1329.475323,"to":"KRW","fr":"EUR"}}}
if (data == json::value_t::discarded || !data.contains("results") || !data["results"].contains(qStr)) {
log::error("No JSON data received from fiat currency converter service");
Expand Down
5 changes: 3 additions & 2 deletions src/api/common/test/fiatconverter_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ CurlHandle::CurlHandle([[maybe_unused]] const BestURLPicker &bestURLPicker,
: _handle(nullptr), _bestUrlPicker(kSomeFakeURL) {}

// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
string CurlHandle::query(std::string_view endpoint, [[maybe_unused]] const CurlOptions &opts) {
std::string_view CurlHandle::query(std::string_view endpoint, [[maybe_unused]] const CurlOptions &opts) {
json jsonData;
if (endpoint.find("currencies") != std::string_view::npos) {
// Currencies
Expand Down Expand Up @@ -69,7 +69,8 @@ string CurlHandle::query(std::string_view endpoint, [[maybe_unused]] const CurlO
jsonData["results"][marketStr]["val"] = rate;
}
}
return jsonData.dump();
_queryData = jsonData.dump();
return _queryData;
}

CurlHandle::~CurlHandle() {} // NOLINT
Expand Down
8 changes: 6 additions & 2 deletions src/api/exchanges/src/binancepublicapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,14 @@ BinancePublic::ExchangeInfoFunc::ExchangeInfoDataByMarket BinancePublic::Exchang
}

json BinancePublic::GlobalInfosFunc::operator()() {
string dataStr = _curlHandle.query("", CurlOptions(HttpRequestType::kGet));
string dataStr = _curlHandle.queryRelease("", CurlOptions(HttpRequestType::kGet));
// This json is HUGE and contains numerous amounts of information
static constexpr std::string_view appBegJson = "application/json\">";
string::const_iterator first = dataStr.begin() + dataStr.find(appBegJson) + appBegJson.size();
std::size_t beg = dataStr.find(appBegJson);
if (beg == string::npos) {
throw exception("Unexpected answer from {}", _curlHandle.getNextBaseUrl());
}
string::const_iterator first = dataStr.begin() + beg + appBegJson.size();
std::string_view sv(first, dataStr.end());
std::size_t reduxPos = sv.find("redux\":");
std::size_t ssrStorePos = sv.find("ssrStore\":", reduxPos);
Expand Down
2 changes: 1 addition & 1 deletion src/api/exchanges/src/bithumbpublicapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ WithdrawalFeeMap BithumbPublic::WithdrawalFeesFunc::operator()() {
WithdrawalFeeMap ret;
// This is not a published API and only a "standard" html page. We will capture the text information in it.
// Warning, it's not in json format so we will need manual parsing.
string dataStr = _curlHandle.query("/customer_support/info_fee", CurlOptions(HttpRequestType::kGet));
std::string_view dataStr = _curlHandle.query("/customer_support/info_fee", CurlOptions(HttpRequestType::kGet));
// Now, we have the big string containing the html data. The following should work as long as format is unchanged.
// here is a line containing our coin with its additional withdrawal fees:
//
Expand Down
4 changes: 2 additions & 2 deletions src/api/exchanges/src/krakenpublicapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ KrakenPublic::WithdrawalFeesFunc::WithdrawalFeesFunc(const CoincenterInfo& coinc
coincenterInfo.getRunMode()) {}

KrakenPublic::WithdrawalFeesFunc::WithdrawalInfoMaps KrakenPublic::WithdrawalFeesFunc::updateFromSource1() {
string withdrawalFeesCsv = _curlHandle1.query("", CurlOptions(HttpRequestType::kGet));
std::string_view withdrawalFeesCsv = _curlHandle1.query("", CurlOptions(HttpRequestType::kGet));

static constexpr std::string_view kBeginWithdrawalFeeHtmlTag = "<td class=withdrawalFee>";
static constexpr std::string_view kBeginMinWithdrawalHtmlTag = "<td class=minWithdrawal>";
Expand Down Expand Up @@ -235,7 +235,7 @@ KrakenPublic::WithdrawalFeesFunc::WithdrawalInfoMaps KrakenPublic::WithdrawalFee
}

KrakenPublic::WithdrawalFeesFunc::WithdrawalInfoMaps KrakenPublic::WithdrawalFeesFunc::updateFromSource2() {
string withdrawalFeesCsv = _curlHandle2.query("", CurlOptions(HttpRequestType::kGet));
std::string_view withdrawalFeesCsv = _curlHandle2.query("", CurlOptions(HttpRequestType::kGet));

static constexpr std::string_view kBeginTableTitle = "Kraken Deposit & Withdrawal fees</h2>";

Expand Down
2 changes: 0 additions & 2 deletions src/engine/test/exchangedata_test.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ class ExchangesBaseTest : public ::testing::Test {
EXPECT_CALL(exchangePrivate8, queryAccountBalance(testing::_)).WillRepeatedly(testing::Return(emptyBalance));
}

void TearDown() override {}

LoadConfiguration loadConfiguration{kDefaultDataDir, LoadConfiguration::ExchangeConfigFileType::kTest};
settings::RunMode runMode = settings::RunMode::kTestKeys;
CoincenterInfo coincenterInfo{runMode, loadConfiguration};
Expand Down
2 changes: 2 additions & 0 deletions src/http-request/include/besturlpicker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class BestURLPicker {

int8_t nbBaseURL() const { return static_cast<int8_t>(_responseTimeStatsPerBaseUrl.size()); }

int nbRequestsDone() const;

private:
explicit BestURLPicker(std::span<const std::string_view> baseUrls)
: _pBaseUrls(baseUrls.data()), _responseTimeStatsPerBaseUrl(baseUrls.size()) {}
Expand Down
18 changes: 16 additions & 2 deletions src/http-request/include/curlhandle.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <map>
#include <string_view>
#include <type_traits>
#include <utility>
Expand Down Expand Up @@ -34,23 +35,35 @@ class CurlHandle {
Duration minDurationBetweenQueries = Duration::zero(),
settings::RunMode runMode = settings::RunMode::kProd);

// Move operations are deleted but could be implemented if needed. It's just to avoid useless code.
CurlHandle(const CurlHandle &) = delete;
CurlHandle &operator=(const CurlHandle &) = delete;

// Move operations are deleted but could be implemented if needed. It's just to avoid useless code.
CurlHandle(CurlHandle &&) = delete;
CurlHandle &operator=(CurlHandle &&) = delete;

~CurlHandle();

/// Launch a query on the given endpoint, it should start with a '/' and not contain the base URLs given at
/// creation of this object.
string query(std::string_view endpoint, const CurlOptions &opts);
/// Response is returned as a std::string_view to a memory hold in cache by this CurlHandle.
/// The pointed memory is valid until a next call to 'query'.
std::string_view query(std::string_view endpoint, const CurlOptions &opts);

/// Same as 'query' except that internal memory buffer is immediately freed after the query.
/// This can be useful for rare queries with very large responses for instance.
string queryRelease(std::string_view endpoint, const CurlOptions &opts);

std::string_view getNextBaseUrl() const { return _bestUrlPicker.getNextBaseURL(); }

Duration minDurationBetweenQueries() const { return _minDurationBetweenQueries; }

/// Instead of actually performing real calls, instructs this CurlHandle to
/// return hardcoded responses (in values of given map) based on query endpoints with appended options (in key of
/// given map) This should be used only for tests purposes, as the search for the matching query is of linear
/// complexity in a flat key value string.
void setOverridenQueryResponses(const std::map<string, string> &queryResponsesMap);

// CurlHandle is trivially relocatable
using trivially_relocatable = std::true_type;

Expand All @@ -64,6 +77,7 @@ class CurlHandle {
Duration _minDurationBetweenQueries;
TimePoint _lastQueryTime{};
BestURLPicker _bestUrlPicker;
string _queryData;
};

// Simple RAII class managing global init and clean up of Curl library.
Expand Down
10 changes: 7 additions & 3 deletions src/http-request/src/besturlpicker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ int8_t BestURLPicker::nextBaseURLPos() const {

// We favor the URL that has the least score for 90 % of the requests, and give a chance to the one with the least
// number of requests 10 % of the time, not counting the one with the best score.
int totalNbRequestsDone =
std::accumulate(_responseTimeStatsPerBaseUrl.begin(), _responseTimeStatsPerBaseUrl.end(), 0,
[](int sum, ResponseTimeStats stats) { return sum + stats.nbRequestsDone; });
int totalNbRequestsDone = nbRequestsDone();
if ((totalNbRequestsDone % 10) == 9) {
ResponseTimeStats minScoreResponseTimeStats = *nextBaseURLIt;

Expand Down Expand Up @@ -96,4 +94,10 @@ void BestURLPicker::storeResponseTimePerBaseURL(int8_t baseUrlPos, uint32_t resp
log::debug("Response time stats for '{}': Avg: {} ms, Dev: {} ms, Nb: {} (last: {} ms)", _pBaseUrls[baseUrlPos],
stats.avgResponseTime, stats.avgDeviation, stats.nbRequestsDone, responseTimeInMs);
}

int BestURLPicker::nbRequestsDone() const {
return std::accumulate(_responseTimeStatsPerBaseUrl.begin(), _responseTimeStatsPerBaseUrl.end(), 0,
[](int sum, ResponseTimeStats stats) { return sum + stats.nbRequestsDone; });
}

} // namespace cct
Loading

0 comments on commit 6b267ea

Please sign in to comment.