diff --git a/.gitignore b/.gitignore index c86b8aa29..8b48317a2 100644 --- a/.gitignore +++ b/.gitignore @@ -332,7 +332,10 @@ ASALocalRun/ # CMake build/ /cmake-build-debug -.vscode/settings.json #Doxygen [Dd]ocs/ + +# vscode +.vscode/ +.factorypath diff --git a/CMakeLists.txt b/CMakeLists.txt index 79fc7dab3..5a84f3a9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake-modules") # Compile Options option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" ON) option(BUILD_CURL_TRANSPORT "Build internal http transport implementation with CURL for HTTP Pipeline" OFF) +option(BUILD_TESTING "Build test cases" OFF) # VCPKG Integration if(DEFINED ENV{VCPKG_ROOT} AND NOT DEFINED CMAKE_TOOLCHAIN_FILE) @@ -22,10 +23,16 @@ endif() # Project definition project(az LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED True) -# compiler warning flags +if(BUILD_TESTING) + # tests + enable_testing () + add_subdirectory(sdk/core/azure-core/test) +endif() + +# compiler warning flags globally include(global_compile_options) # sub-projects diff --git a/CMakeSettings.json b/CMakeSettings.json index f3edd5b32..26d779428 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -1,16 +1,16 @@ -{ - "configurations": [ - { - "name": "x64-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "inheritEnvironments": [ "msvc_x64_x64" ], - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "-v", - "ctestCommandArgs": "", - "variables": [] - } - ] +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "-DINSTALL_GTEST=OFF -DBUILD_TESTING=ON", + "buildCommandArgs": "-v", + "ctestCommandArgs": "", + "variables": [] + } + ] } \ No newline at end of file diff --git a/cmake-modules/AddGoogleTest.cmake b/cmake-modules/AddGoogleTest.cmake new file mode 100644 index 000000000..85d077de9 --- /dev/null +++ b/cmake-modules/AddGoogleTest.cmake @@ -0,0 +1,98 @@ +# +# +# Downloads GTest and provides a helper macro to add tests. Add make check, as well, which +# gives output on failed tests without having to set an environment variable. +# +# +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +if(CMAKE_VERSION VERSION_LESS 3.11) + set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") + + include(DownloadProject) + download_project(PROJ googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + UPDATE_DISCONNECTED 1 + QUIET + ) + + # CMake warning suppression will not be needed in version 1.9 + set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE BOOL "") + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_SOURCE_DIR} EXCLUDE_FROM_ALL) + unset(CMAKE_SUPPRESS_DEVELOPER_WARNINGS) +else() + include(FetchContent) + FetchContent_Declare(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master) + FetchContent_GetProperties(googletest) + if(NOT googletest_POPULATED) + FetchContent_Populate(googletest) + set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE BOOL "") + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL) + unset(CMAKE_SUPPRESS_DEVELOPER_WARNINGS) + endif() +endif() + + +if(CMAKE_CONFIGURATION_TYPES) + add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} + --force-new-ctest-process --output-on-failure + --build-config "$") +else() + add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} + --force-new-ctest-process --output-on-failure) +endif() +set_target_properties(check PROPERTIES FOLDER "Scripts") + +#include_directories(${gtest_SOURCE_DIR}/include) + +# More modern way to do the last line, less messy but needs newish CMake: +#target_include_directories(gtest INTERFACE ${gtest_SOURCE_DIR}/include) + + +if(GOOGLE_TEST_INDIVIDUAL) + if(NOT CMAKE_VERSION VERSION_LESS 3.9) + include(GoogleTest) + else() + set(GOOGLE_TEST_INDIVIDUAL OFF) + endif() +endif() + +# Target must already exist +macro(add_gtest TESTNAME) + target_link_libraries(${TESTNAME} PUBLIC gtest gmock gtest_main) + + if(GOOGLE_TEST_INDIVIDUAL) + if(CMAKE_VERSION VERSION_LESS 3.10) + gtest_add_tests(TARGET ${TESTNAME} + TEST_PREFIX "${TESTNAME}." + TEST_LIST TmpTestList) + set_tests_properties(${TmpTestList} PROPERTIES FOLDER "Tests") + else() + gtest_discover_tests(${TESTNAME} + TEST_PREFIX "${TESTNAME}." + PROPERTIES FOLDER "Tests") + endif() + else() + add_test(${TESTNAME} ${TESTNAME}) + set_target_properties(${TESTNAME} PROPERTIES FOLDER "Tests") + endif() + +endmacro() + +mark_as_advanced( +gmock_build_tests +gtest_build_samples +gtest_build_tests +gtest_disable_pthreads +gtest_force_shared_crt +gtest_hide_internal_symbols +BUILD_GMOCK +BUILD_GTEST +) + +set_target_properties(gtest gtest_main gmock gmock_main + PROPERTIES FOLDER "Extern") + \ No newline at end of file diff --git a/cmake-modules/global_compile_options.cmake b/cmake-modules/global_compile_options.cmake index a40bc9782..c46faed32 100644 --- a/cmake-modules/global_compile_options.cmake +++ b/cmake-modules/global_compile_options.cmake @@ -3,7 +3,8 @@ if(MSVC) set(WARNINGS_AS_ERRORS_FLAG "/WX") endif() - add_compile_options(/Wall ${WARNINGS_AS_ERRORS_FLAG} /wd5031 /wd4668 /wd4820 /wd4255 /wd4710 /analyze) + #https://stackoverflow.com/questions/37527946/warning-unreferenced-inline-function-has-been-removed + add_compile_options(/W4 ${WARNINGS_AS_ERRORS_FLAG} /wd5031 /wd4668 /wd4820 /wd4255 /wd4710 /analyze) elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") if(WARNINGS_AS_ERRORS) set(WARNINGS_AS_ERRORS_FLAG "-Werror") diff --git a/eng/pipelines/templates/jobs/archetype-sdk-client.yml b/eng/pipelines/templates/jobs/archetype-sdk-client.yml index 844c9290c..5a54af7d5 100644 --- a/eng/pipelines/templates/jobs/archetype-sdk-client.yml +++ b/eng/pipelines/templates/jobs/archetype-sdk-client.yml @@ -28,6 +28,31 @@ jobs: vcpkg.deps: '' VCPKG_DEFAULT_TRIPLET: 'x64-osx' + # Unit testing ON + Linux_x64_with_unit_test: + vm.image: 'ubuntu-18.04' + vcpkg.deps: '' + VCPKG_DEFAULT_TRIPLET: 'x64-linux' + build.args: ' -DBUILD_TESTING=ON' + Win_x86_with_unit_test: + vm.image: 'windows-2019' + vcpkg.deps: '' + VCPKG_DEFAULT_TRIPLET: 'x86-windows-static' + CMAKE_GENERATOR: 'Visual Studio 16 2019' + CMAKE_GENERATOR_PLATFORM: Win32 + build.args: ' -DBUILD_TESTING=ON' + Win_x64_with_unit_test: + vm.image: 'windows-2019' + vcpkg.deps: '' + VCPKG_DEFAULT_TRIPLET: 'x64-windows-static' + CMAKE_GENERATOR: 'Visual Studio 16 2019' + CMAKE_GENERATOR_PLATFORM: x64 + build.args: ' -DBUILD_TESTING=ON' + MacOS_x64_with_unit_test: + vm.image: 'macOS-10.14' + vcpkg.deps: '' + VCPKG_DEFAULT_TRIPLET: 'x64-osx' + build.args: ' -DBUILD_TESTING=ON' pool: vmImage: $(vm.image) variables: diff --git a/sdk/core/azure-core/CMakeLists.txt b/sdk/core/azure-core/CMakeLists.txt index 414aa0946..d30470842 100644 --- a/sdk/core/azure-core/CMakeLists.txt +++ b/sdk/core/azure-core/CMakeLists.txt @@ -4,17 +4,15 @@ cmake_minimum_required (VERSION 3.12) project (azure-core LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED True) add_library ( azure-core - src/http/http_request + src/http/http + src/http/request ) -# compiler warning flags -include(global_compile_options) - target_include_directories (azure-core PUBLIC $ $) # make sure that users can consume the project as a library. diff --git a/sdk/core/azure-core/inc/http/http.hpp b/sdk/core/azure-core/inc/http/http.hpp index 2f71aa275..1e9871736 100644 --- a/sdk/core/azure-core/inc/http/http.hpp +++ b/sdk/core/azure-core/inc/http/http.hpp @@ -3,6 +3,10 @@ #pragma once +#include +#include +#include + #include namespace azure @@ -12,18 +16,52 @@ namespace core namespace http { -class http_request +// BodyStream is used to read data to/from a service +class BodyStream { - -private: - /* data */ public: - http_request(); - ~http_request(); + static BodyStream* null; + + // Returns the length of the data; used with the HTTP Content-Length header + virtual uint64_t Length() = 0; + + // Resets the stream back to the beginning (for retries) + // Derived classes that send data in an HTTP request MUST override this and implement it properly. + virtual void Rewind() + { + throw "Not Implemented"; // TODO: Replace with best practice as defined by guideline + }; + + // Reads more data; EOF if return < count; throws if error/canceled + virtual uint64_t Read(/*Context& context, */ uint8_t* buffer, uint64_t offset, uint64_t count) + = 0; + + // Closes the stream; typically called after all data read or if an error occurs. + virtual void Close() = 0; }; -http_request::http_request() {} -http_request::~http_request() {} +class BodyBuffer +{ +public: + static BodyBuffer* null; + + uint8_t const* _bodyBuffer; + uint64_t _bodyBufferSize; + BodyBuffer(uint8_t const* bodyBuffer, uint64_t bodyBufferSize) + : _bodyBuffer(bodyBuffer), _bodyBufferSize(bodyBufferSize) + { + } +}; + +enum class HttpMethod +{ + GET, + HEAD, + POST, + PUT, + DELETE, + PATCH, +}; } // namespace http } // namespace core diff --git a/sdk/core/azure-core/inc/http/request.hpp b/sdk/core/azure-core/inc/http/request.hpp new file mode 100644 index 000000000..760684a37 --- /dev/null +++ b/sdk/core/azure-core/inc/http/request.hpp @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +namespace azure +{ +namespace core +{ +namespace http +{ + +class Request +{ + +private: + Request(Request const&) = delete; + void operator=(Request const&) = delete; + + // query needs to be first or at least before url, since url might update it + std::map _queryParameters; + + HttpMethod _method; + std::string _url; + std::map _headers; + std::map _retryHeaders; + std::map _retryQueryParameters; + // Request can contain no body, or either of next bodies (_bodyBuffer plus size or bodyStream) + BodyStream* _bodyStream; + BodyBuffer* _bodyBuffer; + + // flag to know where to insert header + bool _retryModeEnabled; + + // returns left map plus all items in right + // when duplicates, left items are preferred + static std::map mergeMaps( + std::map const& left, + std::map const& right) + { + auto result = std::map(left); + std::for_each(right.begin(), right.end(), [&](auto const& pair) { result.insert(pair); }); + return result; + } + + /** + * Create an stream from source string. Then use getline and separator to get as many tokens as + * possible and insert each one to result vector + */ + static std::vector split(std::string source, char separator) + { + auto result = std::vector(); + + auto stringAsStream = std::stringstream(source); + auto token = std::string(); + + while (std::getline(stringAsStream, token, separator)) + { + result.push_back(token); + } + return result; + } + + /** + * Will check if there are any query parameter in url looking for symbol '?' + * If it is found, it will insert query parameters to _queryParameters internal field + * and remove it from url + */ + std::string parseUrl(std::string url) + { + auto position = url.find('?'); + + if (position == std::string::npos) + { + return url; // no query parameters. Nothing else to do + } + + // Get query parameters string and update + auto queryParameters = url.substr(position + 1); + url = url.substr(0, position); + + // Split all query parameters (should be separated by &) + auto queryParametersVector = split(queryParameters, '&'); + + // insert each query parameter to internal field + std::for_each(queryParametersVector.begin(), queryParametersVector.end(), [&](auto query) { + auto parameter = split(query, '='); + // Will throw if parameter in query is not valid (i.e. arg:1) + _queryParameters.insert(std::pair(parameter[0], parameter[1])); + }); + + return url; + } + + Request( + HttpMethod httpMethod, + std::string const& url, + BodyStream* bodyStream, + BodyBuffer* bodyBuffer) + : _method(std::move(httpMethod)), _url(parseUrl(std::move(url))), _bodyStream(bodyStream), + _bodyBuffer(bodyBuffer), _retryModeEnabled(false) + { + // TODO: parse url + } + +public: + Request(HttpMethod httpMethod, std::string const& url) + : Request(httpMethod, url, BodyStream::null, BodyBuffer::null) + { + } + + Request(HttpMethod httpMethod, std::string const& url, BodyBuffer* bodyBuffer) + : Request(httpMethod, url, BodyStream::null, bodyBuffer) + { + } + + Request(HttpMethod httpMethod, std::string const& url, BodyStream* bodyStream) + : Request(httpMethod, url, bodyStream, BodyBuffer::null) + { + } + + // Methods used to build HTTP request + void addPath(std::string const& path); + void addQueryParameter(std::string const& name, std::string const& value); + void addHeader(std::string const& name, std::string const& value); + void startRetry(); // only called by retry policy + + // Methods used by transport layer (and logger) to send request + HttpMethod getMethod(); + std::string getEncodedUrl(); // should return encoded url + std::map getHeaders(); + BodyStream* getBodyStream(); + BodyBuffer* getBodyBuffer(); +}; + +} // namespace http +} // namespace core +} // namespace azure diff --git a/sdk/core/azure-core/inc/http/response.hpp b/sdk/core/azure-core/inc/http/response.hpp new file mode 100644 index 000000000..91577bc3a --- /dev/null +++ b/sdk/core/azure-core/inc/http/response.hpp @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include + +#include + +namespace azure +{ +namespace core +{ +namespace http +{ + +class Response +{ + +private: + Response(Response const&) = delete; + void operator=(Response const&) = delete; + + uint16_t _statusCode; + std::string _reasonPhrase; + std::map _headers; + + // Response can contain no body, or either of next bodies (_bodyBuffer plus size or bodyStream) + BodyBuffer& _bodyBuffer; + BodyStream& _bodyStream; + + Response( + uint16_t statusCode, + std::string reasonPhrase, + BodyBuffer& const bodyBuffer, + BodyStream& const BodyStream) + : _statusCode(statusCode), _reasonPhrase(reasonPhrase), _bodyBuffer(bodyBuffer), + _bodyStream(BodyStream) + { + } + +public: + Response(uint16_t statusCode, std::string reasonPhrase) + : Response(statusCode, reasonPhrase, BodyBuffer::null, BodyStream::null) + { + } + + // Methods used to build HTTP response + void addHeader(std::string const& name, std::string const& value); + void setBody(BodyBuffer& const bodyBuffer); + void setBody(BodyStream& const bodyStream); + + // Methods used by transport layer (and logger) to send response + uint16_t const& getStatusCode(); + std::string const& getReasonPhrase(); + std::map const& getHeaders(); + http::BodyStream& getBodyStream(); + http::BodyBuffer& getBodyBuffer(); +}; + +} // namespace http +} // namespace core +} // namespace azure diff --git a/sdk/core/azure-core/src/http/az_http_request.cpp b/sdk/core/azure-core/src/http/az_http_request.cpp deleted file mode 100644 index 08afdef1c..000000000 --- a/sdk/core/azure-core/src/http/az_http_request.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// SPDX-License-Identifier: MIT - -#include - -namespace azure -{ -namespace core -{ -namespace http -{ -class az_http_request -{ - -private: - /* data */ -public: - az_http_request(); - ~az_http_request(); -}; - -az_http_request::az_http_request() {} - -az_http_request::~az_http_request() {} - -} // namespace http -} // namespace core -} // namespace azure diff --git a/sdk/core/azure-core/src/http/http_request.cpp b/sdk/core/azure-core/src/http/http.cpp similarity index 51% rename from sdk/core/azure-core/src/http/http_request.cpp rename to sdk/core/azure-core/src/http/http.cpp index a315c06d1..34667abca 100644 --- a/sdk/core/azure-core/src/http/http_request.cpp +++ b/sdk/core/azure-core/src/http/http.cpp @@ -3,13 +3,7 @@ #include -namespace azure -{ -namespace core -{ -namespace http -{ +using namespace azure::core::http; -} // namespace http -} // namespace core -} // namespace azure +BodyStream* BodyStream::null = nullptr; +BodyBuffer* BodyBuffer::null = nullptr; diff --git a/sdk/core/azure-core/src/http/request.cpp b/sdk/core/azure-core/src/http/request.cpp new file mode 100644 index 000000000..8a607813a --- /dev/null +++ b/sdk/core/azure-core/src/http/request.cpp @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include +#include +#include + +#include +#include + +using namespace azure::core::http; + +void Request::addPath(std::string const& path) { this->_url += "/" + path; } + +void Request::addQueryParameter(std::string const& name, std::string const& value) +{ + if (this->_retryModeEnabled) + { + // When retry mode is ON, any new value must override previous + this->_retryQueryParameters[name] = value; + } + else + { + this->_queryParameters.insert(std::pair(name, value)); + } +} + +void Request::addHeader(std::string const& name, std::string const& value) +{ + if (this->_retryModeEnabled) + { + // When retry mode is ON, any new value must override previous + this->_retryHeaders[name] = value; + } + else + { + this->_headers.insert(std::pair(name, value)); + } +} + +void Request::startRetry() +{ + this->_retryModeEnabled = true; + this->_retryHeaders.clear(); +} + +HttpMethod Request::getMethod() { return this->_method; } + +std::string Request::getEncodedUrl() +{ + if (this->_queryParameters.size() == 0 && this->_retryQueryParameters.size() == 0) + { + return _url; // no query parameters to add + } + + // remove query duplicates + auto queryParameters = Request::mergeMaps(this->_retryQueryParameters, this->_queryParameters); + // build url + auto queryString = std::string(""); + for (auto pair : queryParameters) + { + queryString += (queryString.empty() ? "?" : "&") + pair.first + "=" + pair.second; + } + + return _url + queryString; +} + +std::map Request::getHeaders() +{ + // create map with retry headers witch are the most important and we don't want + // to override them with any duplicate header + return Request::mergeMaps(this->_retryHeaders, this->_headers); +} + +BodyStream* Request::getBodyStream() { return _bodyStream; } + +BodyBuffer* Request::getBodyBuffer() { return _bodyBuffer; } diff --git a/sdk/core/azure-core/src/http/response.cpp b/sdk/core/azure-core/src/http/response.cpp new file mode 100644 index 000000000..d26ca5522 --- /dev/null +++ b/sdk/core/azure-core/src/http/response.cpp @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include +#include +#include + +#include +#include + +using namespace azure::core::http; + +BodyStream& BodyStream::null = *(BodyStream*)NULL; +BodyBuffer& BodyBuffer::null = *(BodyBuffer*)NULL; + +uint16_t const& Response::getStatusCode() { return _statusCode; } + +std::string const& Response::getReasonPhrase() { return _reasonPhrase; } + +std::map const& Response::getHeaders() { return this->_headers; } + +BodyStream& Response::getBodyStream() { return _bodyStream; } + +BodyBuffer& Response::getBodyBuffer() { return _bodyBuffer; } diff --git a/sdk/core/azure-core/test/CMakeLists.txt b/sdk/core/azure-core/test/CMakeLists.txt new file mode 100644 index 000000000..ec93504ac --- /dev/null +++ b/sdk/core/azure-core/test/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: MIT + +cmake_minimum_required (VERSION 3.12) + +include(AddGoogleTest) + +project (azure-core-test LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +add_executable ( + azure-core-test + main.cpp +) + +target_link_libraries(azure-core-test PRIVATE azure-core) +add_gtest(azure-core-test) + diff --git a/sdk/core/azure-core/test/main.cpp b/sdk/core/azure-core/test/main.cpp index 4e6ac64cc..c79bb7469 100644 --- a/sdk/core/azure-core/test/main.cpp +++ b/sdk/core/azure-core/test/main.cpp @@ -1,16 +1,151 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT -#include +#include +#include -#include +#include "gtest/gtest.h" -namespace azure +#include +#include + +using namespace azure::core; + +TEST(Http_Request, getters) { -namespace core + http::HttpMethod httpMethod = http::HttpMethod::GET; + std::string url = "http://test.url.com"; + http::Request req(httpMethod, url); + + // EXPECT_PRED works better than just EQ because it will print values in log + EXPECT_PRED2( + [](http::HttpMethod a, http::HttpMethod b) { return a == b; }, req.getMethod(), httpMethod); + EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, req.getEncodedUrl(), url); + /* EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, + req.getBodyStream(), + http::BodyStream::null); + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, + req.getBodyBuffer(), + http::BodyBuffer::null); */ + + uint8_t buffer[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + auto bufferBody = http::BodyBuffer(buffer, sizeof(buffer)); + http::Request requestWithBody(httpMethod, url, &bufferBody); + + EXPECT_PRED2( + [](http::HttpMethod a, http::HttpMethod b) { return a == b; }, + requestWithBody.getMethod(), + httpMethod); + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, requestWithBody.getEncodedUrl(), url); + /* EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, + requestWithBody.getBodyStream(), + http::BodyStream::null); */ + + // body with buffer + auto body = requestWithBody.getBodyBuffer(); + ASSERT_EQ(body->_bodyBufferSize, 10); + for (auto i = 0; i < 10; i++) + { + ASSERT_EQ(body->_bodyBuffer[i], i); + } + + EXPECT_NO_THROW(req.addHeader("name", "value")); + EXPECT_NO_THROW(req.addHeader("name2", "value2")); + + auto headers = req.getHeaders(); + + EXPECT_TRUE(headers.count("name")); + EXPECT_TRUE(headers.count("name2")); + EXPECT_FALSE(headers.count("newHeader")); + + auto value = headers.find("name"); + EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value->second, "value"); + auto value2 = headers.find("name2"); + EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value2->second, "value2"); + + // now add to retry headers + req.startRetry(); + // same headers first, then one new + EXPECT_NO_THROW(req.addHeader("name", "retryValue")); + EXPECT_NO_THROW(req.addHeader("name2", "retryValue2")); + EXPECT_NO_THROW(req.addHeader("newHeader", "new")); + + headers = req.getHeaders(); + + EXPECT_TRUE(headers.count("name")); + EXPECT_TRUE(headers.count("name2")); + EXPECT_TRUE(headers.count("newHeader")); + + value = headers.find("name"); + EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value->second, "retryValue"); + value2 = headers.find("name2"); + EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value2->second, "retryValue2"); + auto value3 = headers.find("newHeader"); + EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value3->second, "new"); +} + +TEST(Http_Request, query_parameter) { + http::HttpMethod httpMethod = http::HttpMethod::PUT; + std::string url = "http://test.com"; + http::Request req(httpMethod, url); -int main() { return 0; } + EXPECT_NO_THROW(req.addQueryParameter("query", "value")); + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, + req.getEncodedUrl(), + url + "?query=value"); -} // namespace core -} // namespace azure + std::string url_with_query = "http://test.com?query=1"; + http::Request req_with_query(httpMethod, url_with_query); + + // ignore if adding same query parameter key that is already in url + EXPECT_NO_THROW(req_with_query.addQueryParameter("query", "value")); + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, + req_with_query.getEncodedUrl(), + url_with_query); + + // retry query params testing + req.startRetry(); + // same query parameter should override previous + EXPECT_NO_THROW(req.addQueryParameter("query", "retryValue")); + + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, + req.getEncodedUrl(), + url + "?query=retryValue"); +} + +TEST(Http_Request, add_path) +{ + http::HttpMethod httpMethod = http::HttpMethod::POST; + std::string url = "http://test.com"; + http::Request req(httpMethod, url); + + EXPECT_NO_THROW(req.addPath("path")); + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, req.getEncodedUrl(), url + "/path"); + + EXPECT_NO_THROW(req.addQueryParameter("query", "value")); + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, + req.getEncodedUrl(), + url + "/path?query=value"); + + EXPECT_NO_THROW(req.addPath("path2")); + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, + req.getEncodedUrl(), + url + "/path/path2?query=value"); + + EXPECT_NO_THROW(req.addPath("path3")); + EXPECT_PRED2( + [](std::string a, std::string b) { return a == b; }, + req.getEncodedUrl(), + url + "/path/path2/path3?query=value"); +} \ No newline at end of file