From 4f1f45ed5cf440cea25de2a6afb5be7e00970678 Mon Sep 17 00:00:00 2001 From: offa Date: Sun, 22 Jan 2023 14:07:19 +0100 Subject: [PATCH] Replace curl with cpr (#93) --- CMakeLists.txt | 2 +- README.md | 4 +- cmake/InfluxDBConfig.cmake.in | 2 +- conanfile.py | 8 +- script/ci_setup.sh | 2 +- script/ci_testdeploy.sh | 5 +- src/CMakeLists.txt | 28 +- src/HTTP.cxx | 247 +++-------- src/HTTP.h | 38 +- test/CMakeLists.txt | 29 +- test/HttpTest.cxx | 649 ++++++---------------------- test/mock/CMakeLists.txt | 20 +- test/mock/CprMock.cxx | 165 +++++++ test/mock/{CurlMock.h => CprMock.h} | 39 +- test/mock/CurlMock.cxx | 127 ------ 15 files changed, 440 insertions(+), 925 deletions(-) create mode 100644 test/mock/CprMock.cxx rename test/mock/{CurlMock.h => CprMock.h} (54%) delete mode 100644 test/mock/CurlMock.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e8a435..e172f91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") find_package(Threads REQUIRED) -find_package(CURL REQUIRED MODULE) +find_package(cpr REQUIRED) if (INFLUXCXX_WITH_BOOST) # Fixes warning when using boost from brew diff --git a/README.md b/README.md index b573103..ce0e1b8 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ InfluxDB C++ client library - C++17 compiler __Dependencies__ - - CURL (required) + - [cpr](https://github.com/libcpr/cpr) (required) - boost 1.66+ (optional – see [Transports](#transports)) ### Generic @@ -124,7 +124,7 @@ List of supported transport is following: | Name | Dependency | URI protocol | Sample URI | | ----------- |:-----------:|:--------------:| -------------------------------------:| -| HTTP | cURLi) | `http`/`https` | `http://localhost:8086?db=` | +| HTTP | cpri) | `http`/`https` | `http://localhost:8086?db=` | | TCP | boost | `tcp` | `tcp://localhost:8094` | | UDP | boost | `udp` | `udp://localhost:8094` | | Unix socket | boost | `unix` | `unix:///tmp/telegraf.sock` | diff --git a/cmake/InfluxDBConfig.cmake.in b/cmake/InfluxDBConfig.cmake.in index 32f661d..fb2aca4 100644 --- a/cmake/InfluxDBConfig.cmake.in +++ b/cmake/InfluxDBConfig.cmake.in @@ -9,7 +9,7 @@ include(CMakeFindDependencyMacro) if(InfluxDB_WITH_BOOST) find_dependency(Boost COMPONENTS system REQUIRED) endif() -find_dependency(CURL REQUIRED) +find_dependency(cpr REQUIRED) find_dependency(Threads REQUIRED) if(NOT TARGET InfluxData::InfluxDB) diff --git a/conanfile.py b/conanfile.py index 99769ff..adef370 100644 --- a/conanfile.py +++ b/conanfile.py @@ -26,7 +26,6 @@ class InfluxdbCxxConan(ConanFile): "system": False, "boost": True, "boost:shared": True, - "libcurl:shared": True } exports = ["LICENSE"] exports_sources = ("CMakeLists.txt", "src/*", "include/*", "test/*", @@ -54,10 +53,9 @@ def set_version(self): f"Project version from CMakeLists.txt: '{self.version}'") def requirements(self): - if not self.options.system: - self.requires("libcurl/7.84.0") - if self.options.boost: - self.requires("boost/1.79.0") + self.requires("cpr/1.9.3") + if not self.options.system and self.options.boost: + self.requires("boost/1.81.0") if self.options.tests: self.requires("catch2/3.3.0") self.requires("trompeloeil/43") diff --git a/script/ci_setup.sh b/script/ci_setup.sh index 494c0ea..cbd02a0 100755 --- a/script/ci_setup.sh +++ b/script/ci_setup.sh @@ -3,7 +3,7 @@ set -ex apt-get update -apt-get install -y python3-pip libcurl4-openssl-dev +apt-get install -y python3-pip pip3 install -U conan mkdir -p build && cd build diff --git a/script/ci_testdeploy.sh b/script/ci_testdeploy.sh index 26370a8..37e4fd0 100755 --- a/script/ci_testdeploy.sh +++ b/script/ci_testdeploy.sh @@ -12,7 +12,10 @@ cp -r script/include_library/* ${BASEPATH}/ cd /tmp/influx-test-${PID}/ mkdir build && cd build -cmake "$@" .. + +conan install -g cmake_paths -g cmake_find_package cpr/1.9.3@ + +cmake -DCMAKE_TOOLCHAIN_FILE=./conan_paths.cmake "$@" .. cmake --build . -j #cleanup diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bc4f87c..231f960 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,10 +4,6 @@ set(INTERNAL_INCLUDE_DIRS ${PROJECT_BINARY_DIR}/src ) -add_library(InfluxDB-Http OBJECT HTTP.cxx) -target_include_directories(InfluxDB-Http PRIVATE ${INTERNAL_INCLUDE_DIRS}) -target_include_directories(InfluxDB-Http SYSTEM PUBLIC $) - add_library(InfluxDB-BoostSupport OBJECT $<$>:NoBoostSupport.cxx> @@ -30,17 +26,27 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_ set_source_files_properties(UDP.cxx TCP.cxx UnixSocket.cxx PROPERTIES COMPILE_OPTIONS "-Wno-null-dereference") endif() -add_library(InfluxDB-Internal OBJECT LineProtocol.cxx) +add_library(InfluxDB-Internal OBJECT LineProtocol.cxx HTTP.cxx) target_include_directories(InfluxDB-Internal PRIVATE ${INTERNAL_INCLUDE_DIRS}) +target_link_libraries(InfluxDB-Internal PUBLIC cpr::cpr) + + +add_library(InfluxDB-Core OBJECT + InfluxDB.cxx + Point.cxx + InfluxDBFactory.cxx + Proxy.cxx + ) +target_include_directories(InfluxDB-Core PUBLIC + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/src +) +target_link_libraries(InfluxDB-Core PRIVATE cpr::cpr) add_library(InfluxDB - InfluxDB.cxx - Point.cxx - InfluxDBFactory.cxx - Proxy.cxx + $ $ - $ $ ) add_library(InfluxData::InfluxDB ALIAS InfluxDB) @@ -76,7 +82,7 @@ target_include_directories(InfluxDB # Link targets target_link_libraries(InfluxDB PRIVATE - CURL::libcurl + cpr::cpr Threads::Threads $<$:Boost::boost> $<$:Boost::system> diff --git a/src/HTTP.cxx b/src/HTTP.cxx index 2899f0a..51af056 100644 --- a/src/HTTP.cxx +++ b/src/HTTP.cxx @@ -28,187 +28,95 @@ #include "HTTP.h" #include "InfluxDBException.h" - namespace influxdb::transports { namespace { - size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) + void checkResponse(const cpr::Response& resp) { - static_cast(userp)->append(static_cast(contents), size * nmemb); - return size * nmemb; - } - - size_t noopWriteCallBack([[maybe_unused]] char* ptr, size_t size, - size_t nmemb, [[maybe_unused]] void* userdata) - { - return size * nmemb; + if (resp.error) + { + throw ConnectionError(__func__, resp.error.message); + } + if (resp.status_code == 404) + { + throw NonExistentDatabase(__func__, "Nonexistent database: " + std::to_string(resp.status_code)); + } + if (cpr::status::is_client_error(resp.status_code)) + { + throw BadRequest(__func__, "Bad request: " + std::to_string(resp.status_code)); + } + if (cpr::status::is_server_error(resp.status_code)) + { + throw ServerError(__func__, "Influx server error: " + std::to_string(resp.status_code)); + } + if (!cpr::status::is_success(resp.status_code)) + { + throw ConnectionError(__func__, "(" + std::to_string(resp.status_code) + ") " + resp.reason); + } } - void setConnectionOptions(CURL* handle) + std::string parseUrl(const std::string& url) { - curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, 10); - curl_easy_setopt(handle, CURLOPT_TIMEOUT, 10); - curl_easy_setopt(handle, CURLOPT_TCP_KEEPIDLE, 120L); - curl_easy_setopt(handle, CURLOPT_TCP_KEEPINTVL, 60L); - curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, noopWriteCallBack); - } + const auto questionMarkPosition = url.find('?'); - CURL* createReadHandle() - { - if (CURL* readHandle = curl_easy_init(); readHandle != nullptr) + if (questionMarkPosition == std::string::npos) { - setConnectionOptions(readHandle); - curl_easy_setopt(readHandle, CURLOPT_WRITEFUNCTION, WriteCallback); - return readHandle; + return url; } - - throw InfluxDBException{__func__, "Failed to initialize write handle"}; + if (url.at(questionMarkPosition - 1) == '/') + { + return url.substr(0, questionMarkPosition - 1); + } + return url.substr(0, questionMarkPosition); } - CURL* createWriteHandle(const std::string& url) + std::string parseDatabaseName(const std::string& url) { - if (CURL* writeHandle = curl_easy_init(); writeHandle != nullptr) + const auto dbParameterPosition = url.find("?db="); + + if (dbParameterPosition == std::string::npos) { - setConnectionOptions(writeHandle); - curl_easy_setopt(writeHandle, CURLOPT_URL, url.c_str()); - curl_easy_setopt(writeHandle, CURLOPT_POST, 1); - return writeHandle; + throw InfluxDBException{__func__, "Database not specified"}; } - - throw InfluxDBException{__func__, "Failed to initialize write handle"}; + return url.substr(dbParameterPosition + 4); } } - HTTP::HTTP(const std::string& url) - { - initCurl(url); - initCurlRead(url); - obtainInfluxServiceUrl(url); - obtainDatabaseName(url); - } - - HTTP::~HTTP() - { - curl_easy_cleanup(writeHandle); - curl_easy_cleanup(readHandle); - curl_global_cleanup(); - } - void HTTP::initCurl(const std::string& url) + HTTP::HTTP(const std::string& url) + : mInfluxDbServiceUrl(parseUrl(url)), mDatabaseName(parseDatabaseName(url)) { - if (const CURLcode globalInitResult = curl_global_init(CURL_GLOBAL_ALL); globalInitResult != CURLE_OK) - { - throw InfluxDBException(__func__, curl_easy_strerror(globalInitResult)); - } - - std::string writeUrl = url; - auto position = writeUrl.find('?'); - if (position == std::string::npos) - { - throw InfluxDBException(__func__, "Database not specified"); - } - if (writeUrl.at(position - 1) != '/') - { - writeUrl.insert(position, "/write"); - } - else - { - writeUrl.insert(position, "write"); - } - writeHandle = createWriteHandle(writeUrl); + session.SetTimeout(cpr::Timeout{std::chrono::seconds{10}}); + session.SetConnectTimeout(cpr::ConnectTimeout{std::chrono::seconds{10}}); } - void HTTP::initCurlRead(const std::string& url) + std::string HTTP::query(const std::string& query) { - mReadUrl = url + "&q="; - const auto pos = mReadUrl.find('?'); - std::string cmd{"query"}; + session.SetUrl(cpr::Url{mInfluxDbServiceUrl + "/query"}); + session.SetParameters(cpr::Parameters{{"db", mDatabaseName}, {"q", query}}); - if (mReadUrl[pos - 1] != '/') - { - cmd.insert(0, 1, '/'); - } + const auto response = session.Get(); + checkResponse(response); - mReadUrl.insert(pos, cmd); - readHandle = createReadHandle(); - } - - std::string HTTP::query(const std::string& query) - { - std::string buffer; - char* encodedQuery = curl_easy_escape(readHandle, query.c_str(), static_cast(query.size())); - auto fullUrl = mReadUrl + std::string(encodedQuery); - curl_free(encodedQuery); - curl_easy_setopt(readHandle, CURLOPT_URL, fullUrl.c_str()); - curl_easy_setopt(readHandle, CURLOPT_WRITEDATA, &buffer); - const CURLcode response = curl_easy_perform(readHandle); - long responseCode{0}; - curl_easy_getinfo(readHandle, CURLINFO_RESPONSE_CODE, &responseCode); - treatCurlResponse(response, responseCode); - return buffer; + return response.text; } void HTTP::enableBasicAuth(const std::string& auth) { - curl_easy_setopt(writeHandle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); - curl_easy_setopt(writeHandle, CURLOPT_USERPWD, auth.c_str()); - curl_easy_setopt(readHandle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); - curl_easy_setopt(readHandle, CURLOPT_USERPWD, auth.c_str()); + const auto delim = auth.find(':'); + session.SetAuth(cpr::Authentication{auth.substr(0, delim), auth.substr(delim + 1), cpr::AuthMode::BASIC}); } void HTTP::send(std::string&& lineprotocol) { - curl_easy_setopt(writeHandle, CURLOPT_POSTFIELDS, lineprotocol.c_str()); - curl_easy_setopt(writeHandle, CURLOPT_POSTFIELDSIZE, static_cast(lineprotocol.length())); - const CURLcode response = curl_easy_perform(writeHandle); - long responseCode{0}; - curl_easy_getinfo(writeHandle, CURLINFO_RESPONSE_CODE, &responseCode); - treatCurlResponse(response, responseCode); - } + session.SetUrl(cpr::Url{mInfluxDbServiceUrl + "/write"}); + session.SetHeader(cpr::Header{{"Content-Type", "application/json"}}); + session.SetParameters(cpr::Parameters{{"db", mDatabaseName}}); + session.SetBody(cpr::Body{lineprotocol}); - void HTTP::treatCurlResponse(const CURLcode& response, long responseCode) const - { - if (response != CURLE_OK) - { - throw ConnectionError(__func__, curl_easy_strerror(response)); - } - // - // Influx API response codes: - // https://docs.influxdata.com/influxdb/v1.7/tools/api/#status-codes-and-responses-2 - // - if (responseCode == 404) - { - throw NonExistentDatabase(__func__, "Nonexistent database: " + std::to_string(responseCode)); - } - if ((responseCode >= 400) && (responseCode < 500)) - { - throw BadRequest(__func__, "Bad request: " + std::to_string(responseCode)); - } - if (responseCode >= 500) - { - throw ServerError(__func__, "Influx server error: " + std::to_string(responseCode)); - } - } - - void HTTP::obtainInfluxServiceUrl(const std::string& url) - { - const auto questionMarkPosition = url.find('?'); - - if (url.at(questionMarkPosition - 1) == '/') - { - mInfluxDbServiceUrl = url.substr(0, questionMarkPosition - 1); - } - else - { - mInfluxDbServiceUrl = url.substr(0, questionMarkPosition); - } - } - - void HTTP::obtainDatabaseName(const std::string& url) - { - const auto dbParameterPosition = url.find("db="); - mDatabaseName = url.substr(dbParameterPosition + 3); + const auto response = session.Post(); + checkResponse(response); } std::string HTTP::databaseName() const @@ -223,50 +131,33 @@ namespace influxdb::transports void HTTP::setProxy(const Proxy& proxy) { - auto proxyServer = proxy.getProxy(); - curl_easy_setopt(writeHandle, CURLOPT_PROXY, proxyServer.c_str()); - curl_easy_setopt(readHandle, CURLOPT_PROXY, proxyServer.c_str()); + session.SetProxies(cpr::Proxies{{"http", proxy.getProxy()}, {"https", proxy.getProxy()}}); - if (auto auth = proxy.getAuthentication(); auth.has_value()) + if (const auto& auth = proxy.getAuthentication(); auth.has_value()) { - curl_easy_setopt(writeHandle, CURLOPT_PROXYUSERNAME, auth->user.c_str()); - curl_easy_setopt(writeHandle, CURLOPT_PROXYPASSWORD, auth->password.c_str()); - - curl_easy_setopt(readHandle, CURLOPT_PROXYUSERNAME, auth->user.c_str()); - curl_easy_setopt(readHandle, CURLOPT_PROXYPASSWORD, auth->password.c_str()); + session.SetProxyAuth(cpr::ProxyAuthentication{{"http", cpr::EncodedAuthentication{auth->user, auth->password}}, + {"https", cpr::EncodedAuthentication{auth->user, auth->password}}}); } } std::string HTTP::execute(const std::string& cmd) { - std::string buffer; - char* encodedQuery = curl_easy_escape(readHandle, cmd.c_str(), static_cast(cmd.size())); - auto fullUrl = mInfluxDbServiceUrl + "/query?q=" + std::string(encodedQuery); - curl_free(encodedQuery); - curl_easy_setopt(readHandle, CURLOPT_URL, fullUrl.c_str()); - curl_easy_setopt(readHandle, CURLOPT_WRITEDATA, &buffer); - const CURLcode response = curl_easy_perform(readHandle); - long responseCode{0}; - curl_easy_getinfo(readHandle, CURLINFO_RESPONSE_CODE, &responseCode); - treatCurlResponse(response, responseCode); - return buffer; + session.SetUrl(cpr::Url{mInfluxDbServiceUrl + "/query"}); + session.SetParameters(cpr::Parameters{{"db", mDatabaseName}, {"q", cmd}}); + + const auto response = session.Get(); + checkResponse(response); + + return response.text; } void HTTP::createDatabase() { - const std::string createUrl = mInfluxDbServiceUrl + "/query"; - const std::string postFields = "q=CREATE DATABASE " + mDatabaseName; - - CURL* createHandle = createWriteHandle(createUrl); - - curl_easy_setopt(createHandle, CURLOPT_POSTFIELDS, postFields.c_str()); - curl_easy_setopt(createHandle, CURLOPT_POSTFIELDSIZE, static_cast(postFields.length())); + session.SetUrl(cpr::Url{mInfluxDbServiceUrl + "/query"}); + session.SetParameters(cpr::Parameters{{"q", "CREATE DATABASE " + mDatabaseName}}); - const CURLcode response = curl_easy_perform(createHandle); - long responseCode; - curl_easy_getinfo(createHandle, CURLINFO_RESPONSE_CODE, &responseCode); - treatCurlResponse(response, responseCode); - curl_easy_cleanup(createHandle); + const auto response = session.Post(); + checkResponse(response); } } // namespace influxdb diff --git a/src/HTTP.h b/src/HTTP.h index 6d7bb64..6b5c03f 100644 --- a/src/HTTP.h +++ b/src/HTTP.h @@ -29,9 +29,9 @@ #define INFLUXDATA_TRANSPORTS_HTTP_H #include "Transport.h" -#include #include #include +#include namespace influxdb::transports { @@ -43,15 +43,12 @@ namespace influxdb::transports /// Constructor explicit HTTP(const std::string& url); - /// Default destructor - ~HTTP() override; - /// Sends point via HTTP POST - /// \throw InfluxDBException when CURL fails on POSTing or response code != 200 + /// \throw InfluxDBException when send fails void send(std::string&& lineprotocol) override; /// Queries database - /// \throw InfluxDBException when CURL GET fails + /// \throw InfluxDBException when query fails std::string query(const std::string& query) override; /// Execute command @@ -59,7 +56,7 @@ namespace influxdb::transports std::string execute(const std::string& cmd) override; /// Creates database used at url if it does not exists - /// \throw InfluxDBException when CURL POST fails + /// \throw InfluxDBException when HTTP POST fails void createDatabase() override; /// Enable Basic Auth @@ -76,36 +73,13 @@ namespace influxdb::transports void setProxy(const Proxy& proxy) override; private: - /// Obtain InfluxDB service url from the url passed - void obtainInfluxServiceUrl(const std::string& url); - - /// Obtain database name from the url passed - void obtainDatabaseName(const std::string& url); - - /// Initializes CURL for writing and common options - /// \throw InfluxDBException if database (?db=) not specified - void initCurl(const std::string& url); - - /// Initializes CURL for reading - void initCurlRead(const std::string& url); - - /// treats responses of CURL requests - void treatCurlResponse(const CURLcode& response, long responseCode) const; - - /// CURL pointer configured for writing points - CURL* writeHandle; - - /// CURL pointer configured for querying - CURL* readHandle; - - /// InfluxDB read URL - std::string mReadUrl; - /// InfluxDB service URL std::string mInfluxDbServiceUrl; /// Database name used std::string mDatabaseName; + + cpr::Session session; }; } // namespace influxdb diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dee52b5..9e7a451 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -11,8 +11,15 @@ add_subdirectory("mock") function(add_unittest name) + set(multiValueArgs DEPENDS) + cmake_parse_arguments(TEST_OPTION "" "" ${multiValueArgs} ${ARGN}) + add_executable(${name} ${name}.cxx) - target_link_libraries(${name} PRIVATE InfluxDB Catch2::Catch2WithMain trompeloeil::trompeloeil) + target_link_libraries(${name} PRIVATE + ${TEST_OPTION_DEPENDS} + Catch2::Catch2WithMain + trompeloeil::trompeloeil + ) target_include_directories(${name} PRIVATE ${CMAKE_SOURCE_DIR}/src @@ -21,25 +28,21 @@ function(add_unittest name) add_test(NAME ${name} COMMAND ${name}) endfunction() -add_unittest(PointTest) +add_unittest(PointTest DEPENDS InfluxDB) target_compile_options(PointTest PRIVATE $<$>:-Wno-deprecated-declarations>) -add_unittest(LineProtocolTest) -target_link_libraries(LineProtocolTest PRIVATE InfluxDB-Internal) - -add_unittest(InfluxDBTest) -add_unittest(InfluxDBFactoryTest) -add_unittest(ProxyTest) - -add_unittest(HttpTest) -target_link_libraries(HttpTest PRIVATE InfluxDB-Http CurlMock) +add_unittest(LineProtocolTest DEPENDS InfluxDB InfluxDB-Internal) +add_unittest(InfluxDBTest DEPENDS InfluxDB) +add_unittest(InfluxDBFactoryTest DEPENDS InfluxDB) +add_unittest(ProxyTest DEPENDS InfluxDB) +add_unittest(HttpTest DEPENDS InfluxDB-Core InfluxDB-Internal InfluxDB-BoostSupport CprMock) add_unittest(NoBoostSupportTest) target_sources(NoBoostSupportTest PRIVATE ${PROJECT_SOURCE_DIR}/src/NoBoostSupport.cxx) +target_link_libraries(NoBoostSupportTest PRIVATE InfluxDB) if (INFLUXCXX_WITH_BOOST) - add_unittest(BoostSupportTest) - target_link_libraries(BoostSupportTest PRIVATE InfluxDB-BoostSupport Threads::Threads Boost::system date) + add_unittest(BoostSupportTest DEPENDS InfluxDB-BoostSupport InfluxDB Threads::Threads Boost::system date) endif() diff --git a/test/HttpTest.cxx b/test/HttpTest.cxx index 20fd91c..8362051 100644 --- a/test/HttpTest.cxx +++ b/test/HttpTest.cxx @@ -22,480 +22,213 @@ #include "HTTP.h" #include "InfluxDBException.h" -#include "mock/CurlMock.h" +#include "mock/CprMock.h" #include #include - namespace influxdb::test { - namespace - { - CurlHandleDummy dummy; - CURL* handle = &dummy; - } - - CurlMock curlMock; + SessionMock sessionMock; using influxdb::transports::HTTP; using trompeloeil::_; + using trompeloeil::eq; - TEST_CASE("Construction initializes curl", "[HttpTest]") - { - REQUIRE_CALL(curlMock, curl_global_init(CURL_GLOBAL_ALL)).RETURN(CURLE_OK); - - REQUIRE_CALL(curlMock, curl_easy_init()).RETURN(handle); - REQUIRE_CALL(curlMock, curl_easy_init()).RETURN(handle); - - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_CONNECTTIMEOUT, long{10})).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_TIMEOUT, long{10})).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_TCP_KEEPIDLE, long{120})).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_TCP_KEEPINTVL, long{60})).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_WRITEFUNCTION, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_WRITEFUNCTION, ANY(WriteCallbackFn))).RETURN(CURLE_OK); + using ParamMap = std::map; - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_CONNECTTIMEOUT, long{10})).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_TIMEOUT, long{10})).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_TCP_KEEPIDLE, long{120})).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_TCP_KEEPINTVL, long{60})).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_WRITEFUNCTION, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_URL, "http://localhost:8086/write?db=test")).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_POST, long{1})).RETURN(CURLE_OK); - - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); - - HTTP http{"http://localhost:8086?db=test"}; - } - - TEST_CASE("Construction throws if curl init fails", "[HttpTest]") + cpr::Response createResponse(const cpr::ErrorCode& code, std::int32_t statusCode, const std::string& text = "") { - ALLOW_CALL(curlMock, curl_global_init(CURL_GLOBAL_ALL)).RETURN(CURLE_FAILED_INIT); - - CHECK_THROWS_AS(HTTP{"http://localhost:8086?db=test"}, InfluxDBException); + cpr::Error error{}; + error.code = code; + error.message = "(data.size()))).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_perform(handle)).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 200) - .RETURN(CURLE_OK); + REQUIRE_CALL(sessionMock, Post()).RETURN(createResponse(cpr::ErrorCode::OK, cpr::status::HTTP_OK)); + REQUIRE_CALL(sessionMock, SetUrl(eq("http://localhost:8086/write"))); + REQUIRE_CALL(sessionMock, SetHeader(_)).WITH(_1.at("Content-Type") == "application/json"); + REQUIRE_CALL(sessionMock, SetBody(_)).WITH(_1.str() == data); + REQUIRE_CALL(sessionMock, SetParameters(ParamMap{{"db", "test"}})); http.send(std::string{data}); } TEST_CASE("Send fails on unsuccessful execution", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); + auto http = createHttp(); - HTTP http{"http://localhost:8086?db=test"}; - - REQUIRE_CALL(curlMock, curl_easy_perform(handle)).RETURN(CURLE_FAILED_INIT); - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 99) - .RETURN(CURLE_OK); + REQUIRE_CALL(sessionMock, Post()).RETURN(createResponse(cpr::ErrorCode::INTERNAL_ERROR, cpr::status::HTTP_OK)); + ALLOW_CALL(sessionMock, SetUrl(_)); + ALLOW_CALL(sessionMock, SetHeader(_)); + ALLOW_CALL(sessionMock, SetBody(_)); + ALLOW_CALL(sessionMock, SetParameters(_)); REQUIRE_THROWS_AS(http.send("content"), ConnectionError); } TEST_CASE("Send accepts successful response", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); - - HTTP http{"http://localhost:8086?db=test"}; + auto http = createHttp(); - ALLOW_CALL(curlMock, curl_easy_perform(_)).RETURN(CURLE_OK); + REQUIRE_CALL(sessionMock, Post()).RETURN(createResponse(cpr::ErrorCode::OK, cpr::status::HTTP_OK)); + ALLOW_CALL(sessionMock, SetUrl(_)); + ALLOW_CALL(sessionMock, SetHeader(_)); + ALLOW_CALL(sessionMock, SetBody(_)); + ALLOW_CALL(sessionMock, SetParameters(_)); - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 200) - .RETURN(CURLE_OK); - http.send("content"); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 204) - .RETURN(CURLE_OK); http.send("content"); } TEST_CASE("Send throws on unsuccessful response", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); - - HTTP http{"http://localhost:8086?db=test"}; + auto http = createHttp(); - ALLOW_CALL(curlMock, curl_easy_perform(_)).RETURN(CURLE_OK); + REQUIRE_CALL(sessionMock, Post()).RETURN(createResponse(cpr::ErrorCode::OK, cpr::status::HTTP_NOT_FOUND)); + ALLOW_CALL(sessionMock, SetUrl(_)); + ALLOW_CALL(sessionMock, SetHeader(_)); + ALLOW_CALL(sessionMock, SetBody(_)); + ALLOW_CALL(sessionMock, SetParameters(_)); - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 404) - .RETURN(CURLE_OK); REQUIRE_THROWS_AS(http.send("content"), NonExistentDatabase); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 400) - .RETURN(CURLE_OK); - REQUIRE_THROWS_AS(http.send("content"), BadRequest); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 500) - .RETURN(CURLE_OK); - REQUIRE_THROWS_AS(http.send("content"), ServerError); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 503) - .RETURN(CURLE_OK); - REQUIRE_THROWS_AS(http.send("content"), ServerError); } - TEST_CASE("Query configures curl", "[HttpTest]") + TEST_CASE("Query sets parameters", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); + auto http = createHttp(); + const std::string query{"/12?ab=cd"}; - HTTP http{"http://localhost:8086?db=test"}; + REQUIRE_CALL(sessionMock, Get()).RETURN(createResponse(cpr::ErrorCode::OK, cpr::status::HTTP_OK, "query-result")); + REQUIRE_CALL(sessionMock, SetUrl(eq("http://localhost:8086/query"))); + REQUIRE_CALL(sessionMock, SetParameters(ParamMap{{"db", "test"}, {"q", query}})); - const std::string query{"/12?ab=cd"}; - std::string returnValue = query; - char* ptr = &returnValue[0]; - REQUIRE_CALL(curlMock, curl_easy_escape(handle, query.c_str(), static_cast(query.size()))).RETURN(ptr); - ALLOW_CALL(curlMock, curl_free(ptr)); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_URL, "http://localhost:8086/query?db=test&q=/12?ab=cd")).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_WRITEDATA, ANY(void*))) - .LR_SIDE_EFFECT(*static_cast(_3) = "query-result") - .RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_perform(handle)).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 200) - .RETURN(CURLE_OK); - - const auto result = http.query(query); - CHECK(result == "query-result"); + CHECK(http.query(query) == "query-result"); } TEST_CASE("Query fails on unsuccessful execution", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); + auto http = createHttp(); - HTTP http{"http://localhost:8086?db=test"}; + REQUIRE_CALL(sessionMock, Get()).RETURN(createResponse(cpr::ErrorCode::CONNECTION_FAILURE, cpr::status::HTTP_OK)); + ALLOW_CALL(sessionMock, SetUrl(_)); + ALLOW_CALL(sessionMock, SetParameters(_)); - const std::string query{"/x?shouldfail=true"}; - std::string returnValue = query; - char* ptr = &returnValue[0]; - ALLOW_CALL(curlMock, curl_easy_escape(handle, query.c_str(), static_cast(query.size()))).RETURN(ptr); - ALLOW_CALL(curlMock, curl_free(_)); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_WRITEDATA, ANY(void*))) - .LR_SIDE_EFFECT(*static_cast(_3) = "query-result") - .RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_perform(handle)).RETURN(CURLE_FAILED_INIT); - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 99) - .RETURN(CURLE_OK); - - REQUIRE_THROWS_AS(http.query(query), ConnectionError); + REQUIRE_THROWS_AS(http.query("/12?ab=cd"), ConnectionError); } TEST_CASE("Query accepts successful response", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); + auto http = createHttp(); - HTTP http{"http://localhost:8086?db=test"}; + REQUIRE_CALL(sessionMock, Get()).RETURN(createResponse(cpr::ErrorCode::OK, cpr::status::HTTP_OK, "query-result")); + ALLOW_CALL(sessionMock, SetUrl(_)); + ALLOW_CALL(sessionMock, SetParameters(_)); - const std::string query{"/x?shouldfail=true"}; - std::string returnValue = query; - char* ptr = &returnValue[0]; - ALLOW_CALL(curlMock, curl_easy_escape(handle, query.c_str(), static_cast(query.size()))).RETURN(ptr); - ALLOW_CALL(curlMock, curl_free(_)); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_WRITEDATA, ANY(void*))) - .LR_SIDE_EFFECT(*static_cast(_3) = "query-result") - .RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_perform(handle)).RETURN(CURLE_OK); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 200) - .RETURN(CURLE_OK); - http.query(query); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 204) - .RETURN(CURLE_OK); - - const auto result = http.query(query); - CHECK(result == "query-result"); + CHECK(http.query("/12?ab=cd") == "query-result"); } TEST_CASE("Query throws on unsuccessful response", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_URL, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); + auto http = createHttp(); - HTTP http{"http://localhost:8086?db=test"}; + REQUIRE_CALL(sessionMock, Get()).RETURN(createResponse(cpr::ErrorCode::OK, cpr::status::HTTP_BAD_GATEWAY)); + ALLOW_CALL(sessionMock, SetUrl(_)); + ALLOW_CALL(sessionMock, SetParameters(_)); - const std::string query{"/x?shouldfail=true"}; - std::string returnValue = query; - char* ptr = &returnValue[0]; - ALLOW_CALL(curlMock, curl_easy_escape(handle, query.c_str(), static_cast(query.size()))).RETURN(ptr); - ALLOW_CALL(curlMock, curl_free(_)); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_WRITEDATA, ANY(void*))) - .LR_SIDE_EFFECT(*static_cast(_3) = "query-result") - .RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_perform(handle)).RETURN(CURLE_OK); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 404) - .RETURN(CURLE_OK); - REQUIRE_THROWS_AS(http.query(query), NonExistentDatabase); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 400) - .RETURN(CURLE_OK); - REQUIRE_THROWS_AS(http.query(query), BadRequest); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 500) - .RETURN(CURLE_OK); - REQUIRE_THROWS_AS(http.query(query), ServerError); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 503) - .RETURN(CURLE_OK); - REQUIRE_THROWS_AS(http.query(query), ServerError); + REQUIRE_THROWS_AS(http.query("/12?ab=cd"), ServerError); } - TEST_CASE("Create database configures curl", "[HttpTest]") + TEST_CASE("Create database sets parameters", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); - - HTTP http{"http://localhost:8086?db=example-to-create"}; - REQUIRE_CALL(curlMock, curl_easy_setopt_(handle, CURLOPT_URL, "http://localhost:8086/query")).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_setopt_(handle, CURLOPT_POST, long{1})).RETURN(CURLE_OK); - - const std::string data = "q=CREATE DATABASE example-to-create"; - REQUIRE_CALL(curlMock, curl_easy_setopt_(handle, CURLOPT_POSTFIELDS, data)).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_setopt_(handle, CURLOPT_POSTFIELDSIZE, static_cast(data.size()))).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_perform(handle)).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 200) - .RETURN(CURLE_OK); + auto http = createHttp(); + + REQUIRE_CALL(sessionMock, Post()).RETURN(createResponse(cpr::ErrorCode::OK, cpr::status::HTTP_OK)); + REQUIRE_CALL(sessionMock, SetUrl(eq("http://localhost:8086/query"))); + REQUIRE_CALL(sessionMock, SetParameters(ParamMap{{"q", "CREATE DATABASE test"}})); http.createDatabase(); } TEST_CASE("Create database fails on unsuccessful execution", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); - - HTTP http{"http://localhost:8086?db=example-to-create"}; - - const std::string data = "q=CREATE DATABASE example-to-create"; - REQUIRE_CALL(curlMock, curl_easy_perform(handle)).RETURN(CURLE_FAILED_INIT); - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 200) - .RETURN(CURLE_OK); + auto http = createHttp(); + + REQUIRE_CALL(sessionMock, Post()).RETURN(createResponse(cpr::ErrorCode::INTERNAL_ERROR, cpr::status::HTTP_OK)); + ALLOW_CALL(sessionMock, SetUrl(_)); + ALLOW_CALL(sessionMock, SetParameters(_)); REQUIRE_THROWS_AS(http.createDatabase(), ConnectionError); } TEST_CASE("Create database accepts successful response", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); - - HTTP http{"http://localhost:8086?db=example-to-create"}; - - const std::string data = "q=CREATE DATABASE example-to-create"; - ALLOW_CALL(curlMock, curl_easy_perform(handle)).RETURN(CURLE_OK); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 200) - .RETURN(CURLE_OK); - http.createDatabase(); + auto http = createHttp(); + + REQUIRE_CALL(sessionMock, Post()).RETURN(createResponse(cpr::ErrorCode::OK, cpr::status::HTTP_OK)); + ALLOW_CALL(sessionMock, SetUrl(_)); + ALLOW_CALL(sessionMock, SetParameters(_)); - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 204) - .RETURN(CURLE_OK); http.createDatabase(); } TEST_CASE("Create database throws on unsuccessful response", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); - - HTTP http{"http://localhost:8086?db=example-to-create"}; - - const std::string data = "q=CREATE DATABASE example-to-create"; - ALLOW_CALL(curlMock, curl_easy_perform(handle)).RETURN(CURLE_OK); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 404) - .RETURN(CURLE_OK); - REQUIRE_THROWS_AS(http.createDatabase(), NonExistentDatabase); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 400) - .RETURN(CURLE_OK); - REQUIRE_THROWS_AS(http.createDatabase(), BadRequest); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 500) - .RETURN(CURLE_OK); - REQUIRE_THROWS_AS(http.createDatabase(), ServerError); + auto http = createHttp(); + + REQUIRE_CALL(sessionMock, Post()).RETURN(createResponse(cpr::ErrorCode::OK, cpr::status::HTTP_BAD_GATEWAY)); + ALLOW_CALL(sessionMock, SetUrl(_)); + ALLOW_CALL(sessionMock, SetParameters(_)); - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 503) - .RETURN(CURLE_OK); REQUIRE_THROWS_AS(http.createDatabase(), ServerError); } - TEST_CASE("Enabling basic auth sets curl options", "[HttpTest]") + TEST_CASE("Enable basic auth sets parameters", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); - - HTTP http{"http://localhost:8086?db=example-database-0"}; - - REQUIRE_CALL(curlMock, curl_easy_setopt_(handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC)).RETURN(CURLE_OK).TIMES(2); - REQUIRE_CALL(curlMock, curl_easy_setopt_(handle, CURLOPT_USERPWD, "user0:pass0")).RETURN(CURLE_OK).TIMES(2); + auto http = createHttp(); + + REQUIRE_CALL(sessionMock, SetAuth(_)).WITH(_1.GetAuthString() == std::string{"user0:pass0"}); http.enableBasicAuth("user0:pass0"); } TEST_CASE("Database name is returned if valid", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); + ALLOW_CALL(sessionMock, SetTimeout(_)); + ALLOW_CALL(sessionMock, SetConnectTimeout(_)); const HTTP http{"http://localhost:8086?db=example-database-0"}; CHECK(http.databaseName() == "example-database-0"); @@ -503,13 +236,8 @@ namespace influxdb::test TEST_CASE("Database service url is returned if valid", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); + ALLOW_CALL(sessionMock, SetTimeout(_)); + ALLOW_CALL(sessionMock, SetConnectTimeout(_)); const HTTP http{"http://localhost:8086?db=example-database-1"}; CHECK(http.influxDbServiceUrl() == "http://localhost:8086"); @@ -517,172 +245,65 @@ namespace influxdb::test TEST_CASE("Set proxy without authentication", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); - - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_PROXY, "https://proxy-server:1234")).RETURN(CURLE_OK).TIMES(2); + auto http = createHttp(); - HTTP http{"http://localhost:8086?db=test"}; + REQUIRE_CALL(sessionMock, SetProxies(_)).WITH(_1["http"] == std::string{"https://proxy-server:1234"} && _1["https"] == std::string{"https://proxy-server:1234"}); http.setProxy(Proxy{"https://proxy-server:1234"}); } TEST_CASE("Set proxy with authentication", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); - - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_PROXY, "https://proxy-server:1234")).RETURN(CURLE_OK).TIMES(2); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_PROXYUSERNAME, "abc")).RETURN(CURLE_OK).TIMES(2); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_PROXYPASSWORD, "def")).RETURN(CURLE_OK).TIMES(2); + auto http = createHttp(); - HTTP http{"http://localhost:8086?db=test"}; - http.setProxy(Proxy{"https://proxy-server:1234", Proxy::Auth{"abc", "def"}}); + REQUIRE_CALL(sessionMock, SetProxies(_)).WITH(_1["http"] == std::string{"https://auth-proxy-server:1234"} && _1["https"] == std::string{"https://auth-proxy-server:1234"}); + REQUIRE_CALL(sessionMock, SetProxyAuth(_)).WITH(_1["http"] == std::string{"abc:def"} && _1["https"] == std::string{"abc:def"}); + + http.setProxy(Proxy{"https://auth-proxy-server:1234", Proxy::Auth{"abc", "def"}}); } - TEST_CASE("Execute configures curl", "[HttpTest]") + TEST_CASE("Execute sets parameters", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); + auto http = createHttp(); + const std::string cmd{"show databases"}; - HTTP http{"http://localhost:8086?db=test"}; + REQUIRE_CALL(sessionMock, Get()).RETURN(createResponse(cpr::ErrorCode::OK, cpr::status::HTTP_OK, "response-of-execute")); + REQUIRE_CALL(sessionMock, SetUrl(eq("http://localhost:8086/query"))); + REQUIRE_CALL(sessionMock, SetParameters(ParamMap{{"db", "test"}, {"q", cmd}})); - const std::string cmd{"show databases"}; - std::string returnValue = cmd; - char* ptr = &returnValue[0]; - REQUIRE_CALL(curlMock, curl_easy_escape(handle, cmd.c_str(), static_cast(cmd.size()))).RETURN(ptr); - ALLOW_CALL(curlMock, curl_free(ptr)); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_URL, "http://localhost:8086/query?q=show databases")).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_WRITEDATA, ANY(void*))) - .LR_SIDE_EFFECT(*static_cast(_3) = "show-database-result") - .RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_perform(handle)).RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 200) - .RETURN(CURLE_OK); - - const auto result = http.execute(cmd); - CHECK(result == "show-database-result"); + CHECK(http.execute(cmd) == "response-of-execute"); } TEST_CASE("Execute fails on unsuccessful execution", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); + auto http = createHttp(); - HTTP http{"http://localhost:8086?db=test"}; + REQUIRE_CALL(sessionMock, Get()).RETURN(createResponse(cpr::ErrorCode::CONNECTION_FAILURE, cpr::status::HTTP_OK)); + ALLOW_CALL(sessionMock, SetUrl(_)); + ALLOW_CALL(sessionMock, SetParameters(_)); - const std::string cmd{"this is an invalid query"}; - std::string returnValue = cmd; - char* ptr = &returnValue[0]; - ALLOW_CALL(curlMock, curl_easy_escape(handle, cmd.c_str(), static_cast(cmd.size()))).RETURN(ptr); - ALLOW_CALL(curlMock, curl_free(_)); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_WRITEDATA, ANY(void*))) - .LR_SIDE_EFFECT(*static_cast(_3) = "query-result") - .RETURN(CURLE_OK); - REQUIRE_CALL(curlMock, curl_easy_perform(handle)).RETURN(CURLE_FAILED_INIT); - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 99) - .RETURN(CURLE_OK); - - REQUIRE_THROWS_AS(http.execute(cmd), ConnectionError); + REQUIRE_THROWS_AS(http.execute("fail-execution"), ConnectionError); } TEST_CASE("Execute accepts successful response", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); + auto http = createHttp(); - HTTP http{"http://localhost:8086?db=test"}; + REQUIRE_CALL(sessionMock, Get()).RETURN(createResponse(cpr::ErrorCode::OK, cpr::status::HTTP_OK, "response-of-execute")); + ALLOW_CALL(sessionMock, SetUrl(_)); + ALLOW_CALL(sessionMock, SetParameters(_)); - const std::string cmd{"show databases"}; - std::string returnValue = cmd; - char* ptr = &returnValue[0]; - ALLOW_CALL(curlMock, curl_easy_escape(handle, cmd.c_str(), static_cast(cmd.size()))).RETURN(ptr); - ALLOW_CALL(curlMock, curl_free(_)); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_WRITEDATA, ANY(void*))) - .LR_SIDE_EFFECT(*static_cast(_3) = "query-result") - .RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_perform(handle)).RETURN(CURLE_OK); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 200) - .RETURN(CURLE_OK); - http.execute(cmd); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 204) - .RETURN(CURLE_OK); - - const auto result = http.execute(cmd); - CHECK(result == "query-result"); + CHECK(http.execute("show databases") == "response-of-execute"); } TEST_CASE("Execute throws on unsuccessful response", "[HttpTest]") { - ALLOW_CALL(curlMock, curl_global_init(_)).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_init()).RETURN(handle); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_URL, ANY(std::string))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(long))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, _, ANY(WriteCallbackFn))).RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_cleanup(_)); - ALLOW_CALL(curlMock, curl_global_cleanup()); + auto http = createHttp(); - HTTP http{"http://localhost:8086?db=test"}; + REQUIRE_CALL(sessionMock, Get()).RETURN(createResponse(cpr::ErrorCode::OK, cpr::status::HTTP_NOT_FOUND)); + ALLOW_CALL(sessionMock, SetUrl(_)); + ALLOW_CALL(sessionMock, SetParameters(_)); - const std::string cmd{"query should fail"}; - std::string returnValue = cmd; - char* ptr = &returnValue[0]; - ALLOW_CALL(curlMock, curl_easy_escape(handle, cmd.c_str(), static_cast(cmd.size()))).RETURN(ptr); - ALLOW_CALL(curlMock, curl_free(_)); - ALLOW_CALL(curlMock, curl_easy_setopt_(_, CURLOPT_WRITEDATA, ANY(void*))) - .LR_SIDE_EFFECT(*static_cast(_3) = "query-result") - .RETURN(CURLE_OK); - ALLOW_CALL(curlMock, curl_easy_perform(handle)).RETURN(CURLE_OK); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 404) - .RETURN(CURLE_OK); - REQUIRE_THROWS_AS(http.execute(cmd), NonExistentDatabase); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 400) - .RETURN(CURLE_OK); - REQUIRE_THROWS_AS(http.execute(cmd), BadRequest); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 500) - .RETURN(CURLE_OK); - REQUIRE_THROWS_AS(http.execute(cmd), ServerError); - - REQUIRE_CALL(curlMock, curl_easy_getinfo_(handle, CURLINFO_RESPONSE_CODE, _)) - .LR_SIDE_EFFECT(*static_cast(_3) = 503) - .RETURN(CURLE_OK); - REQUIRE_THROWS_AS(http.execute(cmd), ServerError); + REQUIRE_THROWS_AS(http.execute("fail-execution"), NonExistentDatabase); } } diff --git a/test/mock/CMakeLists.txt b/test/mock/CMakeLists.txt index bc6e531..2ef72f9 100644 --- a/test/mock/CMakeLists.txt +++ b/test/mock/CMakeLists.txt @@ -1,16 +1,4 @@ -add_library(CurlMock STATIC CurlMock.cxx) -target_include_directories(CurlMock PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(CurlMock PRIVATE Catch2::Catch2 trompeloeil::trompeloeil) - -target_include_directories(CurlMock SYSTEM PUBLIC $) -target_compile_definitions(CurlMock - PRIVATE - BUILDING_LIBCURL - PUBLIC - $ - ) - - -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") - target_compile_options(CurlMock PRIVATE -Wno-varargs) -endif() +add_library(CprMock STATIC CprMock.cxx) +target_include_directories(CprMock PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(CprMock PRIVATE Catch2::Catch2 trompeloeil::trompeloeil) +target_include_directories(CprMock SYSTEM PUBLIC $) diff --git a/test/mock/CprMock.cxx b/test/mock/CprMock.cxx new file mode 100644 index 0000000..fc25801 --- /dev/null +++ b/test/mock/CprMock.cxx @@ -0,0 +1,165 @@ +// MIT License +// +// Copyright (c) 2020-2023 offa +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "CprMock.h" +#include + +namespace cpr +{ + namespace + { + class ParametersSpy : public Parameters + { + public: + static std::map toMap(const Parameters& p) + { + auto& entries = static_cast(p).containerList_; + std::map params{}; + + std::transform(entries.cbegin(), entries.cend(), std::inserter(params, params.end()), [](const auto& e) + { return std::pair{e.key, e.value}; }); + return params; + } + }; + } + + + template + CurlContainer::CurlContainer(const std::initializer_list& containerList) + : containerList_(containerList) + { + } + + Session::Session() = default; + + void Session::SetTimeout(const Timeout& timeout) + { + influxdb::test::sessionMock.SetTimeout(timeout); + } + + void Session::SetConnectTimeout(const ConnectTimeout& timeout) + { + influxdb::test::sessionMock.SetConnectTimeout(timeout); + } + + void Session::SetBody(Body&& body) + { + influxdb::test::sessionMock.SetBody(std::move(body)); + } + + Response Session::Get() + { + return influxdb::test::sessionMock.Get(); + } + Response Session::Post() + { + return influxdb::test::sessionMock.Post(); + } + + void Session::SetAuth(const Authentication& auth) + { + influxdb::test::sessionMock.SetAuth(auth); + } + + void Session::SetProxyAuth(ProxyAuthentication&& proxy_auth) + { + influxdb::test::sessionMock.SetProxyAuth(std::move(proxy_auth)); + } + + void Session::SetParameters(Parameters&& parameters) + { + influxdb::test::sessionMock.SetParameters(ParametersSpy::toMap(parameters)); + } + + void Session::SetProxies(Proxies&& proxies) + { + influxdb::test::sessionMock.SetProxies(std::move(proxies)); + } + + void Session::SetUrl(const Url& url) + { + influxdb::test::sessionMock.SetUrl(url); + } + + void Session::SetHeader(const Header& header) + { + influxdb::test::sessionMock.SetHeader(header); + } + + + Parameters::Parameters(const std::initializer_list& parameters) + : CurlContainer(parameters) + { + } + + Proxies::Proxies(const std::initializer_list>& hosts) + : hosts_{hosts} + { + } + + Authentication::~Authentication() noexcept = default; + + + bool CaseInsensitiveCompare::operator()([[maybe_unused]] const std::string& a, [[maybe_unused]] const std::string& b) const noexcept + { + return false; + } + + std::string util::urlEncode(const std::string& s) + { + return s; + } + + CurlHolder::CurlHolder() = default; + CurlHolder::~CurlHolder() = default; + + const char* Authentication::GetAuthString() const noexcept + { + return auth_string_.c_str(); + } + + const std::string& Proxies::operator[](const std::string& protocol) + { + if (hosts_.count(protocol) == 0) + { + FAIL("Proxies: No entry '" << protocol << "' available"); + } + return hosts_[protocol]; + } + + const char* ProxyAuthentication::operator[](const std::string& protocol) + { + if (proxyAuth_.count(protocol) == 0) + { + FAIL("ProxyAuthentication: No entry '" << protocol << "' available"); + } + return proxyAuth_[protocol].GetAuthString(); + } + + EncodedAuthentication::~EncodedAuthentication() noexcept = default; + + const char* EncodedAuthentication::GetAuthString() const noexcept + { + return auth_string_.c_str(); + } + +} diff --git a/test/mock/CurlMock.h b/test/mock/CprMock.h similarity index 54% rename from test/mock/CurlMock.h rename to test/mock/CprMock.h index fd0ec7b..400a9b7 100644 --- a/test/mock/CurlMock.h +++ b/test/mock/CprMock.h @@ -22,36 +22,29 @@ #pragma once -#include +#include #include #include - +#include namespace influxdb::test { - struct CurlHandleDummy - { - }; - - using WriteCallbackFn = size_t (*)(void*, size_t, size_t, void*); - - struct CurlMock + class SessionMock { - MAKE_MOCK1(curl_global_init, CURLcode(long)); - MAKE_MOCK0(curl_easy_init, CURL*()); - MAKE_MOCK3(curl_easy_setopt_, CURLcode(CURL*, CURLoption, long)); - MAKE_MOCK3(curl_easy_setopt_, CURLcode(CURL*, CURLoption, unsigned long)); - MAKE_MOCK3(curl_easy_setopt_, CURLcode(CURL*, CURLoption, std::string)); - MAKE_MOCK3(curl_easy_setopt_, CURLcode(CURL*, CURLoption, void*)); - MAKE_MOCK3(curl_easy_setopt_, CURLcode(CURL*, CURLoption, WriteCallbackFn)); - MAKE_MOCK1(curl_easy_cleanup, void(CURL*)); - MAKE_MOCK0(curl_global_cleanup, void()); - MAKE_MOCK1(curl_easy_perform, CURLcode(CURL* easy_handle)); - MAKE_MOCK3(curl_easy_getinfo_, CURLcode(CURL*, CURLINFO, long*)); - MAKE_MOCK3(curl_easy_escape, char*(CURL*, const char*, int) ); - MAKE_MOCK1(curl_free, void(void*)); + public: + MAKE_MOCK1(SetTimeout, void(const cpr::Timeout&)); + MAKE_MOCK1(SetConnectTimeout, void(const cpr::ConnectTimeout&)); + MAKE_MOCK0(Get, cpr::Response()); + MAKE_MOCK0(Post, cpr::Response()); + MAKE_MOCK1(SetUrl, void(const cpr::Url&)); + MAKE_MOCK1(SetHeader, void(const cpr::Header&)); + MAKE_MOCK1(SetBody, void(cpr::Body&&)); + MAKE_MOCK1(SetParameters, void(std::map)); + MAKE_MOCK1(SetAuth, void(const cpr::Authentication&)); + MAKE_MOCK1(SetProxies, void(cpr::Proxies&&)); + MAKE_MOCK1(SetProxyAuth, void(cpr::ProxyAuthentication&&)); }; - extern CurlMock curlMock; + extern SessionMock sessionMock; } diff --git a/test/mock/CurlMock.cxx b/test/mock/CurlMock.cxx deleted file mode 100644 index dbb95a5..0000000 --- a/test/mock/CurlMock.cxx +++ /dev/null @@ -1,127 +0,0 @@ -// MIT License -// -// Copyright (c) 2020-2023 offa -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#include "CurlMock.h" -#include -#include - -void curl_easy_cleanup(CURL* handle) -{ - influxdb::test::curlMock.curl_easy_cleanup(handle); -} - -char* curl_easy_escape(CURL* curl, const char* string, int length) -{ - return influxdb::test::curlMock.curl_easy_escape(curl, string, length); -} - -const char* curl_easy_strerror([[maybe_unused]] CURLcode errornum) -{ - return "Mocked error message"; -} - -void curl_global_cleanup() -{ - influxdb::test::curlMock.curl_global_cleanup(); -} - -CURL* curl_easy_init() -{ - return influxdb::test::curlMock.curl_easy_init(); -} - -CURLcode curl_easy_setopt(CURL* handle, CURLoption option, ...) -{ - using namespace influxdb::test; - - va_list argp; - va_start(argp, option); - std::variant value; - - switch (option) - { - case CURLOPT_CONNECTTIMEOUT: - case CURLOPT_TIMEOUT: - case CURLOPT_TCP_KEEPIDLE: - case CURLOPT_TCP_KEEPINTVL: - case CURLOPT_POST: - case CURLOPT_POSTFIELDSIZE: - value = va_arg(argp, long); - break; - case CURLOPT_HTTPAUTH: - value = va_arg(argp, unsigned long); - break; - case CURLOPT_WRITEDATA: - value = va_arg(argp, void*); - break; - case CURLOPT_URL: - case CURLOPT_POSTFIELDS: - case CURLOPT_USERPWD: - case CURLOPT_PROXY: - case CURLOPT_PROXYUSERNAME: - case CURLOPT_PROXYPASSWORD: - value = va_arg(argp, const char*); - break; - case CURLOPT_WRITEFUNCTION: - value = va_arg(argp, WriteCallbackFn); - break; - default: - FAIL("Option unsupported by mock: " + std::to_string(option)); - return CURLE_UNKNOWN_OPTION; - } - - va_end(argp); - - return std::visit([&](auto& v) - { return curlMock.curl_easy_setopt_(handle, option, v); }, - value); -} - -CURLcode curl_easy_perform(CURL* easy_handle) -{ - return influxdb::test::curlMock.curl_easy_perform(easy_handle); -} - -void curl_free(void* ptr) -{ - influxdb::test::curlMock.curl_free(ptr); -} - -CURLcode curl_global_init(long flags) -{ - return influxdb::test::curlMock.curl_global_init(flags); -} - -CURLcode curl_easy_getinfo(CURL* curl, CURLINFO info, ...) -{ - if (info == CURLINFO_RESPONSE_CODE) - { - va_list argp; - va_start(argp, info); - long* outValue = va_arg(argp, long*); - const auto result = influxdb::test::curlMock.curl_easy_getinfo_(curl, info, outValue); - va_end(argp); - return result; - } - FAIL("Option unsupported by mock: " + std::to_string(info)); - return CURLE_UNKNOWN_OPTION; -}