//////////////////////////////////////////////////////////////////////////////// // (c) Copyright, Ubisoft Entertainment Inc. || Created by Harbour // //////////////////////////////////////////////////////////////////////////////// #include "tests/config.h" #include "tests/fixture/authenticationFixture.h" #include "tests/fixture/memoryCheckFixture.h" #include "tests/fixture/paramFixture.h" #include "tests/common/helpers.h" #include "tests/common/restStatusCode.h" #include "tests/mock/mockLogDevice.h" #include "testsUnit/helpers/asyncResultBaseProxy.h" #include #include #include <../private/ubiservices/core/extensions/staticAssert.h> #include #include #include #include #include #include #include #include <../private/ubiservices/core/chrono/chronometer.h> #include <../private/ubiservices/core/chrono/clockSystem.h> #include <../private/ubiservices/core/configs/environmentHelper.h> #include <../private/ubiservices/core/debug/systemChecker.h> #include <../private/ubiservices/core/encoding/hmac.hpp> #include <../private/ubiservices/core/encoding/base64.h> #include <../private/ubiservices/core/http/httpObfuscatorHelper.h> #include <../private/ubiservices/services/http/httpMultipartHelper.h> #include <../private/ubiservices/services/http/request/httpRequestProxy.h> #if defined(US_SDK_S2S) #include <../private/ubiservices/services/parameters/gatewayResources.h> #endif #if !defined(US_DLL) #include <../private/ubiservices/core/encoding/sha1.h> #endif #include <../private/ubiservices/core/helpers/streamOperators.h> #include #include #include #include #include #include #include namespace US_NS {; US_DEFINE_TEST(TestHttpClient); // 'min' is defined as preprocessor in WinDef.h (and perhaps in other places) // 'min' in '::std::min' is therefore replaced by the macro. // Using the expression "(::std::min)" allows to prevent the macro replacement. #if defined(US_SDK_S2S) #define TEST_SERVICE_EVENTS_VERSION 4 #endif #if !defined(US_RETAIL) extern bool US_DLL_API s_forceNonSecure; #endif typedef LeakCheckerHttpClient HTTP_CLIENT; typedef LeakCheckerHttpClientProxy HTTP_CLIENT_PROXY; typedef LeakCheckerHttpClientNoInit HTTP_CLIENT_NO_INIT; #if defined(US_TARGET_WINDOWS) #pragma warning(disable:4068) // unknown pragma #pragma BullseyeCoverage save off #pragma warning(default:4068) #endif #if !defined(US_DLL) #if defined(US_USE_OPENSSL) namespace TestSslCert_BF { SSLPinningKeyList obfuscateSSLPinningKeys(const Vector& sslPinningKeys, const SSLPinningKeyObfuscator& pinningKeysObfuscator); extern const char* UBISERVICES_DEV_SSL_URL; extern const char* UBISERVICES_UAT_PUBLIC_SSL_URL; } #endif // defined(US_USE_OPENSSL) #if defined(US_SDK_S2S) namespace TestExtendSession_BF { AuthenticationNotificationType waitForNotification(ListenerHandler& listener, DurationMicro waitNano); } #if !defined(DECLARE_TEST_EXTEND_SESSION_HELPER) #define DECLARE_TEST_EXTEND_SESSION_HELPER struct TestExtendSessionHelper { US_FRIEND_TEST(HTTP_CLIENT, HTTPBOX_TestSignatureSuccessAfterExtendSession); static void reduceSessionDuration(Facade& facade, uint64 expirationTimeMs); // For test purposes ONLY }; #endif // !defined(DECLARE_TEST_EXTEND_SESSION_HELPER) #endif // defined(US_SDK_S2S) #endif // !defined(US_DLL) namespace InstancesManager_BF { // Decode Base64 string to obfuscated pinning key void setObfuscatedSSLPinningKeys(SSLPinningKeyList& sslPinningObfuscatedKeys); } namespace TestHttpClient_BF { const char* const URL_INTERNET = "http://www.wolframalpha.com/"; #if defined(US_TARGET_WINDOWS) && !defined(US_DLL) bool setRequestWithEmptyKeysAndObfuscator(HttpRequest& request, bool keyEmpty, bool obfuscatorEmpty) { // By default we have empty keys and obfuscator SSLPinningKeyList keyList; SSLPinningKeyObfuscator obfuscator; // Later we change if required if (!keyEmpty) { InstancesManager_BF::setObfuscatedSSLPinningKeys(keyList); if (keyList.size() > 1) { // Keep only first item keyList.erase(keyList.begin() + 1, keyList.end()); } } if (!obfuscatorEmpty) { obfuscator = HttpObfuscatorHelper::getObfuscator(); } return request.setSSLPinningKeysAndObfuscator(keyList, obfuscator);; } bool setRequestWithCustomKeysAndObfuscator(HttpRequest& request, bool keyOK, bool obfuscatorOK, const uint32 selectedKeyIndex = 0) // 0 is default pinning key { size_t validKeyLength = HttpObfuscatorHelper::getObfuscatorLength(); // By default we have invalid keys and obfuscator SSLPinningKeyList keyList; Vector key(validKeyLength - 10, 4); // shorter than valid key SSLPinningKeyObfuscator obfuscator(validKeyLength - 10, 4); // shorter than valid obfuscator // Later we change if required if (keyOK) { SSLPinningKeyList validKeyList; InstancesManager_BF::setObfuscatedSSLPinningKeys(validKeyList); // Is index out of range ? if (validKeyList.size() < selectedKeyIndex + 1) { return false; } keyList.push_back(validKeyList[selectedKeyIndex]); } else { keyList.push_back(key); } if (obfuscatorOK) { obfuscator = HttpObfuscatorHelper::getObfuscator(); } return request.setSSLPinningKeysAndObfuscator(keyList, obfuscator); } #endif void writeToFile(const String& fileName, const char* data, uint32 size) { std::ofstream file; file.open(fileName.getBufferT().c_str(), std::ios::out | std::ios::binary); file.write(data, size); if ((file.rdstate() & std::ofstream::failbit) != 0) { std::cerr << _T("Error writing!!!"); } file.close(); } String readTextFile(const String& fileName) { std::ifstream file; file.open(fileName.getBufferT().c_str()); StringStream retVal; if (file.is_open()) { while(file.good()) { std::string line; getline(file, line); retVal << line.c_str(); } } file.close(); return retVal.getContent(); } Vector createUploadData(uint32 iteration) { EXPECT_GT(iteration, 0u); Vector data; data.reserve(4*iteration); for (; iteration > 0; iteration--) { data.push_back('t'); data.push_back('e'); data.push_back(0); data.push_back('s'); data.push_back('t'); } return data; } Vector getHeaderJsonFromHttpBoxBody(const String& body, const Vector &headersToBeFound = Vector()) { Vector headersFound; if (!body.isEmpty()) { const Json reader(body); if (reader.isValid() && reader.isTypeObject()) { const Vector& items = reader["headers"].getItems(); headersFound.reserve(items.size()); if (!headersToBeFound.empty()) { for (const auto& headerToBeFound : headersToBeFound) { const auto& it = find_if(items.begin(), items.end(), [&headerToBeFound](const Json& item) { return headerToBeFound == item.getKey(); }); if (it != items.end()) { headersFound.push_back(*it); } } } else { headersFound = items; } } } return headersFound; } ::testing::AssertionResult validateHttpBoxHeadersInBodyResponse(const String& requestBody, const Vector& headersToValidate) { const Vector& jsonHeaders = getHeaderJsonFromHttpBoxBody(requestBody); if(!jsonHeaders.empty()) { for (const auto& headerToValidate : headersToValidate) { String errorString; if (find_if(jsonHeaders.begin(), jsonHeaders.end(), [&headerToValidate, &errorString](const Json& item) { if (item.getKey() == headerToValidate) { if (item.getValueString().isEmpty()) { errorString = headerToValidate + " was found on the HttpBox response, but it does not have a value set."; return false; } return true; } errorString = headerToValidate + " was not found on the httpBox response."; return false; }) == jsonHeaders.end()) { return ::testing::AssertionFailure() << headerToValidate << " was not found on the HttpBox response."; } } return ::testing::AssertionSuccess(); } return ::testing::AssertionFailure() << "requestBody does not contain any header returned by HTTPBOX"; } Vector createMultipartData() { const Vector image = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x07, 0x08, 0x02, 0x00, 0x00, 0x00, 0xBE, 0xCE, 0x4B, 0x34, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, 0xC7, 0x6F, 0xA8, 0x64, 0x00, 0x00, 0x00, 0x12, 0x49, 0x44, 0x41, 0x54, 0x18, 0x57, 0x63, 0xE8, 0x60, 0x10, 0xC5, 0x83, 0x86, 0xAA, 0x34, 0x83, 0x28, 0x00, 0x53, 0x38, 0x2A, 0xEF, 0x36, 0xB0, 0x43, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; const StringA text("This is a multipart test string"); Vector multiparts; multiparts.reserve(2); multiparts.push_back(HttpMultipartData("image_data", image, "image/png", "test.png")); multiparts.push_back(HttpMultipartData("text_data", Vector(text.begin(), text.end()))); return multiparts; } #if defined(US_SDK_S2S) enum class ParamsHmacState { Valid, Invalid, Empty, ENUM_END, ENUM_COUNT = ENUM_END }; US_STATIC_ASSERT(static_cast(TestHttpClient_BF::ParamsHmacState::ENUM_COUNT) == 3, "TestHttpClient_BF::getPrivateCertificateValue needs to be updated"); const String getPrivateCertificateValue(ParamsHmacState hmacState) { switch (hmacState) { case ubiservices::TestHttpClient_BF::ParamsHmacState::Valid: return HmacHelper_BF::deobfuscateKey(S2S_OBFUSCATED_CERT_PRIVATE); case ubiservices::TestHttpClient_BF::ParamsHmacState::Invalid: return String("InvalidPrivateKey"); case ubiservices::TestHttpClient_BF::ParamsHmacState::Empty: default: return String(); } } #endif // defined(US_SDK_S2S) } namespace TestParametersHttpMultipart_BF { struct ParamsHttpRequest { HttpMethod method; const char* stringMethod; }; } // Test that the header Content-Length must be specified by UbiServices internal implementation TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpGETWithContentLength) { const StringUrl url = TestHelpers_BF::getHttpTestUrl("get"); HttpHeader header = TestHelpers_BF::getHttpBoxHeader(); header["Content-Length"] = "100"; EXPECT_EQ(100u, header.getContentLength()); Facade facade; EXPECT_TRUE(AsyncHelper::waitFailure(facade.getHttpClient().sendRequest(HttpGet(url, header)), Errors::HTTP_CONTENT_LENGTH_REJECTED)); } /// @test Checks that the header Content-Length must be specified by UbiServices internal implementation TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpGETWithUserAgent) { const StringUrl url = TestHelpers_BF::getHttpTestUrl("get"); HttpHeader header = TestHelpers_BF::getHttpBoxHeader(); header["User-Agent"] = "Philip Bond"; Facade facade; EXPECT_TRUE(AsyncHelper::waitFailure(facade.getHttpClient().sendRequest(HttpGet(url, header)), Errors::HTTP_USER_AGENT_REJECTED)); } TEST_F(HTTP_CLIENT, HTTPBOX_MINRUN_PerformHttpGETSuccess) { Facade facade; const StringUrl url = TestHelpers_BF::getHttpTestUrl("get"); const HttpHeader header = TestHelpers_BF::getHttpBoxHeader(); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, header)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_TRUE(result.getResult().isBodyTypeString()); PRINT_STREAM("GET response body is " << result.getResult().getBodyAsString()); // Validate headers const HttpHeader& respHeader = result.getResult().getAllHeader(); ASSERT_GT(respHeader.getSize(), 0ull); EXPECT_GT(respHeader.getContentLength(), 0); EXPECT_NE(String::npos, result.getResult().getHeader("Content-Type").findSubstringCase("application/json")); } #if defined(US_SDK_S2S) /// @tests Test used for testing a http GET request with signature TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpGETSuccessWithSignedRequest) { Facade facade; const AsyncResult resultLogin = facade.createSession(GET_CREDENTIALS(), {}, {}, GET_AUTHENTICATION_CERTIFICATE_PRIVATE()); ASSERT_TRUE(AsyncHelper::waitSuccess(resultLogin)); SessionInfo sessionInfo = facade.getAuthenticationClient().getSessionInfo(); // we need a valid session so that http signing can work // it is also checked in FacadeInterface::signRequest ASSERT_TRUE(sessionInfo.getSessionId().isValid()); const StringUrl url = TestHelpers_BF::getHttpTestUrl("get"); const HttpHeader header = TestHelpers_BF::getHttpBoxHeader(); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, header), HttpRequestSignType::Hmac); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_TRUE(result.getResult().isBodyTypeString()); ASSERT_TRUE(TestHttpClient_BF::validateHttpBoxHeadersInBodyResponse( result.getResult().getBodyAsString(), { "Ubi-Signature-timestamp", "Ubi-Signature", "x-forwarded-url" }) ); } /// @tests Test used for testing a http GET request with signature when no session is available TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpGETWithFailedSignedRequest) { Facade facade; const StringUrl url = TestHelpers_BF::getHttpTestUrl("get"); const HttpHeader header = TestHelpers_BF::getHttpBoxHeader(); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, header), HttpRequestSignType::Hmac); ASSERT_TRUE(AsyncHelper::waitFailure(result, Errors::HTTP_SIGNING_FAILURE)); } /// @tests Test used for testing a http GET Events request with signature TEST_F(HTTP_CLIENT, HTTP_PerformHttpGETEventsWithSignedRequest) { Facade facade; const AsyncResult resultLogin = facade.createSession(GET_CREDENTIALS(), {}, {}, GET_AUTHENTICATION_CERTIFICATE_PRIVATE()); ASSERT_TRUE(AsyncHelper::waitSuccess(resultLogin)); SessionInfo sessionInfo = facade.getAuthenticationClient().getSessionInfo(); ASSERT_TRUE(sessionInfo.getSessionId().isValid()); StringUrl url = facade.getParametersClient().getResourceUrl(GatewayResource::MAP_S2S.at(GatewayResource::ConfigsEvents).m_name, TEST_SERVICE_EVENTS_VERSION); const String spaceId = facade.getWorkingSpaceId(); url = url.replace("{spaceId}", spaceId); HttpHeader header = TestHelpers_BF::getHttpBoxHeader(); header["Authorization"] = "ubi_s2s_v1 t=" + sessionInfo.getTicket(); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, header), HttpRequestSignType::Hmac); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); ASSERT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); } TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpGETWithBasicAuthenticationConfig) { setupDebugLogging(); Facade facade; const StringUrl url = TestHelpers_BF::getHttpTestUrl("get"); HttpHeader header = TestHelpers_BF::getHttpBoxHeader(); const String email = "myEmail"; const String password = "myPwd"; HttpGet request(url, header); const HttpRequest::HttpRequestBasicAuthenticationConfig httpBasicAuthentication(email, password); request.setHttpBasicAuthenticationConfig(httpBasicAuthentication); const AsyncResult result = facade.getHttpClient().sendRequest(request); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_TRUE(result.getResult().isBodyTypeString()); PRINT_STREAM("GET response body is " << result.getResult().getBodyAsString()); ASSERT_TRUE(TestHttpClient_BF::validateHttpBoxHeadersInBodyResponse(result.getResult().getBodyAsString(), { "Authorization" })); } #endif //!defined(US_SDK_S2S) /// @tests Test used to first request a resource ETag and after to request /// the same resource but on this request it will add the obtained ETag in the /// If-None-Match header, resulting normally in a 304 Not Modified response TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpGetIfNoneMatchEtag) { Facade facade; const StringUrl url = "https://uat-ubiservices.cdn.ubi.com/d0b38ea5-d884-4444-895d-fa99ec47a76a/logo/ubiservices.png"; // Perform a HEAD request in order to obtain the resource Etag const AsyncResult headResult = facade.getHttpClient().sendRequest(HttpHead(url)); ASSERT_TRUE(AsyncHelper::waitSuccess(headResult)); const HttpResponse& headResponse = headResult.getResult(); ASSERT_EQ(RestStatusCode::OK, headResponse.getStatusCode()); const String etag = headResponse.getHeader("ETag").replaceAll("\"", ""); // Perform a second request with the etag added in the If-None-Match header HttpHeader header; header.addHeader("If-None-Match", etag); const AsyncResult secondResult = facade.getHttpClient().sendRequest(HttpGet(url, header)); ASSERT_TRUE(AsyncHelper::waitSuccess(secondResult)); ASSERT_EQ(RestStatusCode::Error304, secondResult.getResult().getStatusCode()); } /// @tests Test used to perform a conditional GET request with a wrong ETag. /// The result should contain the requested resource and a 200 OK status code. TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpGetIfNoneMatchWrongEtag) { Facade facade; const StringUrl url = "https://uat-ubiservices.cdn.ubi.com/d0b38ea5-d884-4444-895d-fa99ec47a76a/logo/ubiservices.png"; const String etag = "781f58e0239a0d4308589b7b74aaee60"; // Perform a second request with the etag added in the If-None-Match header HttpHeader header; header.addHeader("If-None-Match", etag); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, header)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); ASSERT_EQ(RestStatusCode::OK, response.getStatusCode()); ASSERT_GT(response.getBodySize(), 0u); } /// @test Validates that the client can receive binary properly. /// HttpBox server returns random bytes with the ETag header value containing the Sha1 hash. TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpGETSuccessBinary) { Facade facade; const uint32 EXPECTED_SIZE = 1024; const StringUrl url = TestHelpers_BF::getHttpTestUrl(String::formatText("bytes/%i", EXPECTED_SIZE)); const HttpHeader header = TestHelpers_BF::getHttpBoxHeader(); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, header)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); ASSERT_EQ(RestStatusCode::OK, response.getStatusCode()); // Validate headers const HttpHeader& respHeader = response.getAllHeader(); ASSERT_GT(respHeader.getSize(), 0ull); EXPECT_GT(respHeader.getContentLength(), 0); EXPECT_NE(String::npos, response.getHeader("Content-Type").findSubstringCase("application/octet-stream")); // Validate body and body size EXPECT_EQ(EXPECTED_SIZE, response.getBodySize()); const Vector body = response.getBodyAsBinary(); const String etag = response.getHeader("ETag").replaceAll("\"", ""); ASSERT_EQ(40, etag.getLength()) << "ETag must contain 40 hex characters (20 bytes): " << etag.getUtf8(); #if !defined(US_DLL) SHA1 sha1; const std::string hashResult = sha1(&body[0], body.size()); EXPECT_TRUE(etag.isEqualCaseInsensitive(hashResult.c_str())); #endif } // This test use Httpbin because we don't want to receive a Content-Length header and // it's not really possible to do that with HttpBox. TEST_F(HTTP_CLIENT_PROXY, DISABLED_HTTPBIN_PerformHttpGETBinaryNoContentLength) { Facade facade; const uint32 EXPECTED_SIZE = 1024; const StringUrl url = String::formatText("http://httpbin.org/stream-bytes/%i", EXPECTED_SIZE); const HttpHeader header = TestHelpers_BF::getHttpBoxHeader(); // Using a custom request timeout to make sure this test // won't fail if external site is very slow to respond. HttpRetryConfig retryConfig = HttpRetryConfig(0, HttpRetryConfig::RetryParam(), HttpRetryConfig::TimeoutParam(60000)); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, header), retryConfig); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); ASSERT_EQ(RestStatusCode::OK, response.getStatusCode()); // Validate headers const HttpHeader& respHeader = response.getAllHeader(); ASSERT_GT(respHeader.getSize(), 0ull); EXPECT_EQ(-1, respHeader.getContentLength()); EXPECT_NE(String::npos, response.getHeader("Content-Type").findSubstringCase("application/octet-stream")); // Validate body size const Vector body = response.getBodyAsBinary(); EXPECT_EQ(EXPECTED_SIZE, response.getBodySize()); EXPECT_EQ(EXPECTED_SIZE, body.size()); } TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpGETTransferEncodingChunked) { Facade facade; const StringUrl url = TestHelpers_BF::getHttpTestUrl("get", "", false, true); const HttpHeader header = TestHelpers_BF::getHttpBoxHeader(); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, header)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); ASSERT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); // Validate headers const HttpResponse& response = result.getResult(); const HttpHeader& respHeader = response.getAllHeader(); ASSERT_GT(respHeader.getSize(), 0ull); EXPECT_FALSE(respHeader.hasHeader("Content-Length")); EXPECT_TRUE(respHeader.hasHeader("Transfer-Encoding")); EXPECT_NE(String::npos, result.getResult().getHeader("Transfer-Encoding").findSubstringCase("chunked")); EXPECT_NE(String::npos, result.getResult().getHeader("Content-Type").findSubstringCase("application/json")); // Validate body EXPECT_TRUE(response.isBodyTypeString()); EXPECT_TRUE(Json(response.getBodyAsString()).isValid()); } // Validate that the client can receive binary with chunked encoding properly. // HttpBox server returns random bytes with the ETag header value containing the Sha1 hash. // A new chunk is returned by the server at every 1024 bytes. TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpGETTransferEncodingChunkedBinary) { Facade facade; const uint32 EXPECTED_SIZE = 1024 * 5; // 5 chunks const StringUrl url = TestHelpers_BF::getHttpTestUrl(String::formatText("stream/%i", EXPECTED_SIZE)); const HttpHeader header = TestHelpers_BF::getHttpBoxHeader(); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, header)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); ASSERT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); const HttpResponse& response = result.getResult(); // Validate headers const HttpHeader& respHeader = response.getAllHeader(); ASSERT_GT(respHeader.getSize(), 0ull); EXPECT_FALSE(respHeader.hasHeader("Content-Length")); EXPECT_TRUE(respHeader.hasHeader("Transfer-Encoding")); EXPECT_NE(String::npos, result.getResult().getHeader("Transfer-Encoding").findSubstringCase("chunked")); EXPECT_NE(String::npos, respHeader["Content-Type"].findSubstringCase("application/octet-stream")); // Validate body and body size EXPECT_EQ(EXPECTED_SIZE, response.getBodySize()); const Vector body = response.getBodyAsBinary(); const String etag = response.getHeader("ETag").replaceAll("\"", ""); ASSERT_EQ(40, etag.getLength()) << "ETag must contain 40 hex characters (20 bytes): " << etag.getUtf8(); #if !defined(US_DLL) SHA1 sha1; const std::string hashResult = sha1(&body[0], body.size()); EXPECT_TRUE(etag.isEqualCaseInsensitive(hashResult.c_str())); #endif } /// @test Performs an HTTP GET and validate that query params are sent TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpWithQuery) { Facade facade; const ::std::pair queryParam1("param1", "val1"); const ::std::pair queryParam2("param2", "val2"); const String queryString(queryParam1.first + "=" + queryParam1.second + "&" + queryParam2.first + "=" + queryParam2.second); const StringUrl url = TestHelpers_BF::getHttpTestUrl("get", queryString); const HttpHeader header = TestHelpers_BF::getHttpBoxHeader(); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, header)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); ASSERT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); // Validate that query was sent EXPECT_TRUE(result.getResult().isBodyTypeString()); const Json reader(result.getResult().getBodyAsString()); ASSERT_TRUE(reader.isValid()); const Json args = reader["args"]; ASSERT_TRUE(args.isValid()); EXPECT_STREQ(queryParam1.second.getUtf8(), args[queryParam1.first].getValueString().getUtf8()); EXPECT_STREQ(queryParam2.second.getUtf8(), args[queryParam2.first].getValueString().getUtf8()); } /// @test Performs an HTTP POST TEST_F(HTTP_CLIENT, HTTPBOX_ValidatePOSTResponseHeader) { Facade facade; const StringUrl url = TestHelpers_BF::getHttpTestUrl(_T("post")); const HttpHeader header = TestHelpers_BF::getHttpContentHeader(); const AsyncResult result = facade.getHttpClient().sendRequest(HttpPost(url, header, _T("data"))); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); // Validate headers const HttpHeader& respHeader = result.getResult().getAllHeader(); ASSERT_GT(respHeader.getSize(), 0u); EXPECT_GT(respHeader.getContentLength(), 0); EXPECT_NE(String::npos, result.getResult().getHeader("Content-Type").findSubstringCase("application/json")); } TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpHEADSuccess) { Facade facade; const StringUrl url = TestHelpers_BF::getHttpTestUrl(_T("head")); const HttpHeader header = TestHelpers_BF::getHttpBoxHeader(); const AsyncResult result = facade.getHttpClient().sendRequest(HttpHead(url, header)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); // Validate headers const HttpHeader& respHeader = result.getResult().getAllHeader(); ASSERT_GT(respHeader.getSize(), 0u); EXPECT_GT(respHeader.getContentLength(), 0); EXPECT_NE(String::npos, result.getResult().getHeader("Content-Type").findSubstringCase("application/json")); } #if defined(US_SDK_S2S) /// @test Performs an HTTP HEAD with signature TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpHEADWithSignedRequest) { Facade facade; const AsyncResult resultLogin = facade.createSession(GET_CREDENTIALS(), {}, {}, GET_AUTHENTICATION_CERTIFICATE_PRIVATE()); ASSERT_TRUE(AsyncHelper::waitSuccess(resultLogin)); SessionInfo sessionInfo = facade.getAuthenticationClient().getSessionInfo(); ASSERT_TRUE(sessionInfo.getSessionId().isValid()); const StringUrl url = TestHelpers_BF::getHttpTestUrl(_T("head")); const HttpHeader header = TestHelpers_BF::getHttpBoxHeader(); const AsyncResult result = facade.getHttpClient().sendRequest(HttpHead(url, header), HttpRequestSignType::Hmac); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); } #endif /// @test Performs an HTTP GET TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpGETError) { Facade facade; { const StringUrl url = TestHelpers_BF::getHttpTestUrl("post"); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, TestHelpers_BF::getHttpBoxHeader())); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); EXPECT_FALSE(response.isSuccessStatusCode()); EXPECT_EQ(RestStatusCode::Error405, response.getStatusCode()); } { const StringUrl url = TestHelpers_BF::getHttpTestUrl("put"); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, TestHelpers_BF::getHttpBoxHeader())); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); EXPECT_FALSE(response.isSuccessStatusCode()); EXPECT_EQ(RestStatusCode::Error405, response.getStatusCode()); } { const StringUrl url = TestHelpers_BF::getHttpTestUrl("delete"); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, TestHelpers_BF::getHttpBoxHeader())); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); EXPECT_FALSE(response.isSuccessStatusCode()); EXPECT_EQ(RestStatusCode::Error405, response.getStatusCode()); } } /// @test Test to validate http request retry mechanism /// The HttpBox will always return 503, so this request always fails /// We calculate the minimum time it should take for this specific retry config /// then make sure the actual time it took is greater than the minimum time. TEST_F(HTTP_CLIENT, HTTPBOX_ValidateHttpRetryHandler) { Facade facade; { const HttpGet request(TestHelpers_BF::getHttpStatusCodeTestUrl(RestStatusCode::Error503), TestHelpers_BF::getHttpBoxHeader()); // Retry config (time in Msec) const uint32 RETRY_COUNT = 2; const uint32 RETRY_INTERVAL = 2000; const uint32 RETRY_INCREMENT = 1000; const uint32 RETRY_RANDOM_FACTOR = 100; const uint32 TYPICAL_REQUEST_TIME = 200; const uint32 minimumRequestTimeWithRetries = (TYPICAL_REQUEST_TIME)+ (RETRY_INTERVAL + TYPICAL_REQUEST_TIME) + (RETRY_INTERVAL + RETRY_INCREMENT + TYPICAL_REQUEST_TIME); HttpRetryConfig retryConfig = HttpRetryConfig(RETRY_COUNT, HttpRetryConfig::RetryParam(RETRY_INTERVAL, RETRY_INCREMENT, RETRY_RANDOM_FACTOR)); HighResolutionChrono chrono; const AsyncResult result = facade.getHttpClient().sendRequest(request, retryConfig); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); EXPECT_FALSE(response.isSuccessStatusCode()); EXPECT_EQ(RestStatusCode::Error503, response.getStatusCode()); const DurationMilli totalTimeWithRetries = chrono.getElapsed(); EXPECT_LT(minimumRequestTimeWithRetries, totalTimeWithRetries); } } /// @test Performs an HTTP POST TEST_F(HTTP_CLIENT, HTTPBOX_MINRUN_PerformHttpPOST) { Facade facade; JsonWriter writer; writer["post some thing"] = 0; const String body = writer.renderContent(); const HttpHeader header = TestHelpers_BF::getHttpContentHeader(); { const StringUrl url = TestHelpers_BF::getHttpTestUrl("post"); //PRINT_STREAM("Sending post to " << url); const AsyncResult result = facade.getHttpClient().sendRequest(HttpPost(url, header, body)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); //PRINT_STREAM("POST response body is " << result.getResult().getBodyAsString()); } // Errors { const StringUrl url = TestHelpers_BF::getHttpTestUrl("get"); const AsyncResult result = facade.getHttpClient().sendRequest(HttpPost(url, header, body)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); EXPECT_FALSE(response.isSuccessStatusCode()); EXPECT_EQ(RestStatusCode::Error405, response.getStatusCode()); } } typedef ParamFixture PARAMETERS_HTTP_MULTIPART_REQUEST_FIXTURE; INSTANTIATE_TEST_CASE_P(ParamsHttpRequest, PARAMETERS_HTTP_MULTIPART_REQUEST_FIXTURE, ::testing::Values( TestParametersHttpMultipart_BF::ParamsHttpRequest{ HttpMethod::HTTP_POST, "post" }, TestParametersHttpMultipart_BF::ParamsHttpRequest{ HttpMethod::HTTP_PUT, "put" }, TestParametersHttpMultipart_BF::ParamsHttpRequest{ HttpMethod::HTTP_PATCH, "patch" }, TestParametersHttpMultipart_BF::ParamsHttpRequest{ HttpMethod::HTTP_DELETE, "delete" } )); /// @test Performs HTTP POST/PUT/PATCH/DELETE request with multiparts provided at construction TEST_P(PARAMETERS_HTTP_MULTIPART_REQUEST_FIXTURE, HTTPBOX_PerformMultipart) { Facade facade; HttpHeader header = TestHelpers_BF::getHttpContentHeader(); auto param = this->getParam(); const StringUrl url = TestHelpers_BF::getHttpTestUrl(param.stringMethod); const String multipartBoundary = "----USMultipartBoundary"; const auto multipartData = TestHttpClient_BF::createMultipartData(); AsyncResult result; switch (param.method) { case HttpMethod::HTTP_POST: result = facade.getHttpClient().sendRequest(HttpPost(url, header, multipartData, multipartBoundary)); break; case HttpMethod::HTTP_PUT: result = facade.getHttpClient().sendRequest(HttpPut(url, header, multipartData, multipartBoundary)); break; case HttpMethod::HTTP_PATCH: result = facade.getHttpClient().sendRequest(HttpPatch(url, header, multipartData, multipartBoundary)); break; case HttpMethod::HTTP_DELETE: result = facade.getHttpClient().sendRequest(HttpDelete(url, header, multipartData, multipartBoundary)); break; default: ASSERT_TRUE(false); } ASSERT_TRUE(AsyncHelper::waitSuccess(result)); ASSERT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); //Somewhat future proof in case the multipartData changes ASSERT_EQ(2u, multipartData.size()); ASSERT_EQ("image_data", multipartData[0].m_name); ASSERT_EQ("text_data", multipartData[1].m_name); // multipartData[0] contains the image data String stringImageData = "data:image/png;base64,"; stringImageData += Base64::encode(multipartData[0].m_data); // multipartData[1] contains the text data const String stringTextData = String(reinterpret_cast(multipartData[1].m_data.data()), multipartData[1].m_data.size()); Json json(result.getResult().getBodyAsString()); ASSERT_GT(json.getItemsCount(), 0); EXPECT_EQ(stringImageData, json["files"]["image_data"].getValueString()); EXPECT_EQ(stringTextData, json["form"]["text_data"].getValueString()); } /// @test Performs HTTP POST/PUT/PATCH/DELETE request with multiparts provided at construction, but with default boundary TEST_P(PARAMETERS_HTTP_MULTIPART_REQUEST_FIXTURE, HTTPBOX_PerformMultipartDefaultBoundary) { Facade facade; HttpHeader header = TestHelpers_BF::getHttpContentHeader(); auto param = this->getParam(); const StringUrl url = TestHelpers_BF::getHttpTestUrl(param.stringMethod); const auto multipartData = TestHttpClient_BF::createMultipartData(); const String multipartGenerated = HttpMultipartHelper::generateDefaultMultipartBoundary(); AsyncResult result; switch (param.method) { case HttpMethod::HTTP_POST: result = facade.getHttpClient().sendRequest(HttpPost(url, header, multipartData, String())); break; case HttpMethod::HTTP_PUT: result = facade.getHttpClient().sendRequest(HttpPut(url, header, multipartData, String())); break; case HttpMethod::HTTP_PATCH: result = facade.getHttpClient().sendRequest(HttpPatch(url, header, multipartData, String())); break; case HttpMethod::HTTP_DELETE: result = facade.getHttpClient().sendRequest(HttpDelete(url, header, multipartData, String())); break; default: ASSERT_TRUE(false); } ASSERT_TRUE(AsyncHelper::waitSuccess(result)); ASSERT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); //Somewhat future proof in case the multipartData changes ASSERT_EQ(2u, multipartData.size()); ASSERT_EQ("image_data", multipartData[0].m_name); ASSERT_EQ("text_data", multipartData[1].m_name); // multipartData[0] contains the image data String stringImageData = "data:image/png;base64,"; stringImageData += Base64::encode(multipartData[0].m_data); // multipartData[1] contains the text data const String stringTextData = String(reinterpret_cast(multipartData[1].m_data.data()), multipartData[1].m_data.size()); Json json(result.getResult().getBodyAsString()); ASSERT_GT(json.getItemsCount(), 0); EXPECT_EQ(stringImageData, json["files"]["image_data"].getValueString()); EXPECT_EQ(stringTextData, json["form"]["text_data"].getValueString()); EXPECT_NE(String::npos, json["headers"]["Content-Type"].getValueString().findSubstringCase(multipartGenerated)); } /// @test Performs HTTP POST/PUT/PATCH/DELETE request with multiparts provided with setBody TEST_P(PARAMETERS_HTTP_MULTIPART_REQUEST_FIXTURE, HTTPBOX_PerformMultipartWithSetBody) { Facade facade; HttpHeader header = TestHelpers_BF::getHttpContentHeader(); auto param = this->getParam(); const StringUrl url = TestHelpers_BF::getHttpTestUrl(param.stringMethod); const String multipartBoundary = "----USMultipartBoundary"; const auto multipartData = TestHttpClient_BF::createMultipartData(); AsyncResult result; switch (param.method) { case HttpMethod::HTTP_POST: { HttpPost request(url, ""); request.setBody(multipartData, multipartBoundary); result = facade.getHttpClient().sendRequest(request); break; } case HttpMethod::HTTP_PUT: { HttpPut request(url, ""); request.setBody(multipartData, multipartBoundary); result = facade.getHttpClient().sendRequest(request); break; } case HttpMethod::HTTP_PATCH: { HttpPatch request(url, ""); request.setBody(multipartData, multipartBoundary); result = facade.getHttpClient().sendRequest(request); break; } case HttpMethod::HTTP_DELETE: { HttpDelete request(url, ""); request.setBody(multipartData, multipartBoundary); result = facade.getHttpClient().sendRequest(request); break; } default: ASSERT_TRUE(false); } ASSERT_TRUE(AsyncHelper::waitSuccess(result)); ASSERT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); //Somewhat future proof in case the multipartData changes ASSERT_EQ(2u, multipartData.size()); ASSERT_EQ("image_data", multipartData[0].m_name); ASSERT_EQ("text_data", multipartData[1].m_name); // multipartData[0] contains the image data String stringImageData = "data:image/png;base64,"; stringImageData += Base64::encode(multipartData[0].m_data); // multipartData[1] contains the text data const String stringTextData = String(reinterpret_cast(multipartData[1].m_data.data()), multipartData[1].m_data.size()); Json json(result.getResult().getBodyAsString()); ASSERT_GT(json.getItemsCount(), 0); EXPECT_EQ(stringImageData, json["files"]["image_data"].getValueString()); EXPECT_EQ(stringTextData, json["form"]["text_data"].getValueString()); } /// @test Performs HTTP POST/PUT/PATCH/DELETE request with multiparts provided with setBody, but with default boundary TEST_P(PARAMETERS_HTTP_MULTIPART_REQUEST_FIXTURE, HTTPBOX_PerformMultipartWithSetBodyAndDefaultBoundary) { Facade facade; HttpHeader header = TestHelpers_BF::getHttpContentHeader(); auto param = this->getParam(); const StringUrl url = TestHelpers_BF::getHttpTestUrl(param.stringMethod); const auto multipartData = TestHttpClient_BF::createMultipartData(); const String multipartGenerated = HttpMultipartHelper::generateDefaultMultipartBoundary(); AsyncResult result; switch (param.method) { case HttpMethod::HTTP_POST: { HttpPost request(url, ""); request.setBody(multipartData, String()); result = facade.getHttpClient().sendRequest(request); break; } case HttpMethod::HTTP_PUT: { HttpPut request(url, ""); request.setBody(multipartData, String()); result = facade.getHttpClient().sendRequest(request); break; } case HttpMethod::HTTP_PATCH: { HttpPatch request(url, ""); request.setBody(multipartData, String()); result = facade.getHttpClient().sendRequest(request); break; } case HttpMethod::HTTP_DELETE: { HttpDelete request(url, ""); request.setBody(multipartData, String()); result = facade.getHttpClient().sendRequest(request); break; } default: ASSERT_TRUE(false); } ASSERT_TRUE(AsyncHelper::waitSuccess(result)); ASSERT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); //Somewhat future proof in case the multipartData changes ASSERT_EQ(2u, multipartData.size()); ASSERT_EQ("image_data", multipartData[0].m_name); ASSERT_EQ("text_data", multipartData[1].m_name); // multipartData[0] contains the image data String stringImageData = "data:image/png;base64,"; stringImageData += Base64::encode(multipartData[0].m_data); // multipartData[1] contains the text data const String stringTextData = String(reinterpret_cast(multipartData[1].m_data.data()), multipartData[1].m_data.size()); Json json(result.getResult().getBodyAsString()); ASSERT_GT(json.getItemsCount(), 0); EXPECT_EQ(stringImageData, json["files"]["image_data"].getValueString()); EXPECT_EQ(stringTextData, json["form"]["text_data"].getValueString()); EXPECT_NE(String::npos, json["headers"]["Content-Type"].getValueString().findSubstringCase(multipartGenerated)); } /// @test Performs HTTP POST/PUT/PATCH/DELETE request with multiparts provided at construction, with a too big boundary TEST_P(PARAMETERS_HTTP_MULTIPART_REQUEST_FIXTURE, HTTPBOX_PerformMultipartWithBoundaryTooBig) { Facade facade; HttpHeader header = TestHelpers_BF::getHttpContentHeader(); auto param = this->getParam(); const StringUrl url = TestHelpers_BF::getHttpTestUrl(param.stringMethod); const String extraBoundary = "cut"; String multipartBoundary; multipartBoundary.reserve(HttpMultipartHelper::s_MAX_BOUNDARY_LENGTH + extraBoundary.getLength()); for (unsigned int i = 0; i < HttpMultipartHelper::s_MAX_BOUNDARY_LENGTH; ++i) { multipartBoundary += "a"; } multipartBoundary += extraBoundary; const auto multipartData = TestHttpClient_BF::createMultipartData(); AsyncResult result; switch (param.method) { case HttpMethod::HTTP_POST: result = facade.getHttpClient().sendRequest(HttpPost(url, header, multipartData, multipartBoundary)); break; case HttpMethod::HTTP_PUT: result = facade.getHttpClient().sendRequest(HttpPut(url, header, multipartData, multipartBoundary)); break; case HttpMethod::HTTP_PATCH: result = facade.getHttpClient().sendRequest(HttpPatch(url, header, multipartData, multipartBoundary)); break; case HttpMethod::HTTP_DELETE: result = facade.getHttpClient().sendRequest(HttpDelete(url, header, multipartData, multipartBoundary)); break; default: ASSERT_TRUE(false); } ASSERT_TRUE(AsyncHelper::waitSuccess(result)); ASSERT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); //Somewhat future proof in case the multipartData changes ASSERT_EQ(2u, multipartData.size()); ASSERT_EQ("image_data", multipartData[0].m_name); ASSERT_EQ("text_data", multipartData[1].m_name); // multipartData[0] contains the image data String stringImageData = "data:image/png;base64,"; stringImageData += Base64::encode(multipartData[0].m_data); // multipartData[1] contains the text data const String stringTextData = String(reinterpret_cast(multipartData[1].m_data.data()), multipartData[1].m_data.size()); Json json(result.getResult().getBodyAsString()); ASSERT_GT(json.getItemsCount(), 0); EXPECT_EQ(stringImageData, json["files"]["image_data"].getValueString()); EXPECT_EQ(stringTextData, json["form"]["text_data"].getValueString()); String contentTypeBoundary = json["headers"]["Content-Type"].getValueString(); const size_t boundaryIndex = contentTypeBoundary.findSubstringCase("boundary="); ASSERT_NE(String::npos, boundaryIndex); const String boundary = contentTypeBoundary.substr(boundaryIndex + String("boundary=").getLength()); EXPECT_EQ(HttpMultipartHelper::s_MAX_BOUNDARY_LENGTH, boundary.getLength()); EXPECT_EQ(String::npos, boundary.findSubstringCase(extraBoundary)); } /// @test Performs HTTP POST/PUT/PATCH/DELETE request with multiparts provided with setBody, with a too big boundary TEST_P(PARAMETERS_HTTP_MULTIPART_REQUEST_FIXTURE, HTTPBOX_PerformMultipartSetBodyWithBoundaryTooBig) { Facade facade; HttpHeader header = TestHelpers_BF::getHttpContentHeader(); auto param = this->getParam(); const StringUrl url = TestHelpers_BF::getHttpTestUrl(param.stringMethod); const String extraBoundary = "cut"; String multipartBoundary; multipartBoundary.reserve(HttpMultipartHelper::s_MAX_BOUNDARY_LENGTH + extraBoundary.getLength()); for (unsigned int i = 0; i < HttpMultipartHelper::s_MAX_BOUNDARY_LENGTH; ++i) { multipartBoundary += "a"; } multipartBoundary += extraBoundary; const auto multipartData = TestHttpClient_BF::createMultipartData(); AsyncResult result; switch (param.method) { case HttpMethod::HTTP_POST: { HttpPost request(url, ""); request.setBody(multipartData, multipartBoundary); result = facade.getHttpClient().sendRequest(request); break; } case HttpMethod::HTTP_PUT: { HttpPut request(url, ""); request.setBody(multipartData, multipartBoundary); result = facade.getHttpClient().sendRequest(request); break; } case HttpMethod::HTTP_PATCH: { HttpPatch request(url, ""); request.setBody(multipartData, multipartBoundary); result = facade.getHttpClient().sendRequest(request); break; } case HttpMethod::HTTP_DELETE: { HttpDelete request(url, ""); request.setBody(multipartData, multipartBoundary); result = facade.getHttpClient().sendRequest(request); break; } default: ASSERT_TRUE(false); } ASSERT_TRUE(AsyncHelper::waitSuccess(result)); ASSERT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); //Somewhat future proof in case the multipartData changes ASSERT_EQ(2u, multipartData.size()); ASSERT_EQ("image_data", multipartData[0].m_name); ASSERT_EQ("text_data", multipartData[1].m_name); // multipartData[0] contains the image data String stringImageData = "data:image/png;base64,"; stringImageData += Base64::encode(multipartData[0].m_data); // multipartData[1] contains the text data const String stringTextData = String(reinterpret_cast(multipartData[1].m_data.data()), multipartData[1].m_data.size()); Json json(result.getResult().getBodyAsString()); ASSERT_GT(json.getItemsCount(), 0); EXPECT_EQ(stringImageData, json["files"]["image_data"].getValueString()); EXPECT_EQ(stringTextData, json["form"]["text_data"].getValueString()); String contentTypeBoundary = json["headers"]["Content-Type"].getValueString(); const size_t boundaryIndex = contentTypeBoundary.findSubstringCase("boundary="); ASSERT_NE(boundaryIndex, static_cast(-1)); const String boundary = contentTypeBoundary.substr(boundaryIndex + String("boundary=").getLength()); EXPECT_EQ(HttpMultipartHelper::s_MAX_BOUNDARY_LENGTH, boundary.getLength()); EXPECT_EQ(String::npos, boundary.findSubstringCase(extraBoundary)); } /// @test Performs HTTP POST/PUT/PATCH/DELETE standard request, but with multiparts provided at construction and body overwritten TEST_P(PARAMETERS_HTTP_MULTIPART_REQUEST_FIXTURE, HTTPBOX_PerformStandardWithMultipartOverwritten) { Facade facade; HttpHeader header = TestHelpers_BF::getHttpContentHeader(); auto param = this->getParam(); const String url = TestHelpers_BF::getHttpTestUrl(param.stringMethod); const String multipartBoundary = "----USMultipartBoundary"; const auto multipartData = TestHttpClient_BF::createMultipartData(); JsonWriter writer; writer["This is a standard HTTP Request"] = 0; AsyncResult result; switch (param.method) { case HttpMethod::HTTP_POST: { HttpPost request(url, header, multipartData, multipartBoundary); request.setBody(writer.renderContent()); result = facade.getHttpClient().sendRequest(request); break; } case HttpMethod::HTTP_PUT: { HttpPut request(url, header, multipartData, multipartBoundary); request.setBody(writer.renderContent()); result = facade.getHttpClient().sendRequest(request); break; } case HttpMethod::HTTP_PATCH: { HttpPatch request(url, header, multipartData, multipartBoundary); request.setBody(writer.renderContent()); result = facade.getHttpClient().sendRequest(request); break; } case HttpMethod::HTTP_DELETE: { HttpDelete request(url, header, multipartData, multipartBoundary); request.setBody(writer.renderContent()); result = facade.getHttpClient().sendRequest(request); break; } default: ASSERT_TRUE(false); } ASSERT_TRUE(AsyncHelper::waitSuccess(result)); ASSERT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); Json json(result.getResult().getBodyAsString()); ASSERT_GT(json.getItemsCount(), 0); EXPECT_EQ(0, json["files"].getItemsCount()); EXPECT_EQ(0, json["form"].getItemsCount()); EXPECT_EQ(String::npos, json["headers"]["Content-Type"].getValueString().findSubstringCase(multipartBoundary)); } #if defined(US_SDK_S2S) /// @test Performs an HTTP POST with signature TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpPOSTWithSignedRequest) { Facade facade; const AsyncResult resultLogin = facade.createSession(GET_CREDENTIALS(), {}, {}, GET_AUTHENTICATION_CERTIFICATE_PRIVATE()); ASSERT_TRUE(AsyncHelper::waitSuccess(resultLogin)); SessionInfo sessionInfo = facade.getAuthenticationClient().getSessionInfo(); // we need a valid session so that http signing can work // it is also checked in FacadeInterface::signRequest ASSERT_TRUE(sessionInfo.getSessionId().isValid()); JsonWriter writer; writer["post some thing"] = 0; const String body = writer.renderContent(); const HttpHeader header = TestHelpers_BF::getHttpContentHeader(); { const StringUrl url = TestHelpers_BF::getHttpTestUrl("post"); //PRINT_STREAM("Sending post to " << url); const AsyncResult result = facade.getHttpClient().sendRequest(HttpPost(url, header, body), HttpRequestSignType::Hmac); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); EXPECT_TRUE(result.getResult().isBodyTypeString()); ASSERT_TRUE(TestHttpClient_BF::validateHttpBoxHeadersInBodyResponse( result.getResult().getBodyAsString(), { "Ubi-Signature-timestamp", "Ubi-Signature", "x-forwarded-url" }) ); } } #endif TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpPOSTBinary) { Facade facade; const Vector data = TestHttpClient_BF::createUploadData(500u); const StringUrl url = TestHelpers_BF::getHttpTestUrl("bytes"); const HttpHeader header = TestHelpers_BF::getHttpContentHeader(); //PRINT_STREAM("Sending post to " << url); const AsyncResult result = facade.getHttpClient().sendRequest(HttpPost(url, header, data)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); EXPECT_EQ(RestStatusCode::OK, response.getStatusCode()); // Validate data sent const String& etag = response.getHeader("ETag").replaceAll("\"", ""); ASSERT_EQ(40, etag.getLength()) << "ETag must contain 40 hex characters (20 bytes): " << etag.getUtf8(); #if !defined(US_DLL) SHA1 sha1; const std::string hashResult = sha1(&data[0], data.size()); EXPECT_TRUE(etag.isEqualCaseInsensitive(hashResult.c_str())); #endif } TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpPUTBinary) { Facade facade; const Vector data = TestHttpClient_BF::createUploadData(500u); const StringUrl url = TestHelpers_BF::getHttpTestUrl("bytes"); const HttpHeader header = TestHelpers_BF::getHttpContentHeader(); //PRINT_STREAM("Sending post to " << url); const AsyncResult result = facade.getHttpClient().sendRequest(HttpPut(url, header, data)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); EXPECT_EQ(RestStatusCode::OK, response.getStatusCode()); // Validate data sent const String& etag = response.getHeader("ETag").replaceAll("\"", ""); ASSERT_EQ(40, etag.getLength()) << "ETag must contain 40 hex characters (20 bytes): " << etag.getUtf8(); #if !defined(US_DLL) SHA1 sha1; const std::string hashResult = sha1(&data[0], data.size()); EXPECT_TRUE(etag.isEqualCaseInsensitive(hashResult.c_str())); #endif } TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpPostSuccessNoBody) { Facade facade; const StringUrl url = TestHelpers_BF::getHttpTestUrl("post"); const HttpHeader header = TestHelpers_BF::getHttpContentHeader(); //PRINT_STREAM("Sending post to " << url); const AsyncResult result = facade.getHttpClient().sendRequest(HttpPost(url, header, "")); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); } /// @test Performs an HTTP PUT TEST_F(HTTP_CLIENT, HTTPBOX_MINRUN_PerformHttpPUT) { Facade facade; JsonWriter writer; writer["put some thing"] = 0; const HttpHeader header = TestHelpers_BF::getHttpContentHeader(); const String body = writer.renderContent(); { const StringUrl url(TestHelpers_BF::getHttpTestUrl("put")); const AsyncResult result = facade.getHttpClient().sendRequest(HttpPut(url, header, body)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); //PRINT_STREAM("PUT response body is " << result.getResult().getBodyAsString()); } // Errors { const StringUrl url = TestHelpers_BF::getHttpTestUrl("get"); const AsyncResult result = facade.getHttpClient().sendRequest(HttpPut(url, header, body)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); EXPECT_FALSE(response.isSuccessStatusCode()); EXPECT_EQ(RestStatusCode::Error405, response.getStatusCode()); } } #if defined(US_SDK_S2S) /// @test Performs an HTTP PUT with signature TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpPUTWithSignedRequest) { Facade facade; const AsyncResult resultLogin = facade.createSession(GET_CREDENTIALS(), {}, {}, GET_AUTHENTICATION_CERTIFICATE_PRIVATE()); ASSERT_TRUE(AsyncHelper::waitSuccess(resultLogin)); const SessionInfo sessionInfo = facade.getAuthenticationClient().getSessionInfo(); ASSERT_TRUE(sessionInfo.getSessionId().isValid()); JsonWriter writer; writer["put some thing"] = 0; const HttpHeader header = TestHelpers_BF::getHttpContentHeader(); const String body = writer.renderContent(); { const StringUrl url(TestHelpers_BF::getHttpTestUrl("put")); const AsyncResult result = facade.getHttpClient().sendRequest(HttpPut(url, header, body), HttpRequestSignType::Hmac); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); EXPECT_TRUE(result.getResult().isBodyTypeString()); ASSERT_TRUE(TestHttpClient_BF::validateHttpBoxHeadersInBodyResponse( result.getResult().getBodyAsString(), { "Ubi-Signature-timestamp", "Ubi-Signature", "x-forwarded-url" }) ); } } #endif /// @test Performs an HTTP PATCH TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpPatch) { Facade facade; JsonWriter writer; writer["patch this bug"] = 0; const HttpHeader header = TestHelpers_BF::getHttpContentHeader(); const String body = writer.renderContent(); { const StringUrl url(TestHelpers_BF::getHttpTestUrl("patch")); const AsyncResult result = facade.getHttpClient().sendRequest(HttpPatch(url, header, body)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); PRINT_STREAM("PATCH response body is " << result.getResult().getBodyAsString()); } // Errors { const StringUrl url(TestHelpers_BF::getHttpTestUrl("get")); const AsyncResult result = facade.getHttpClient().sendRequest(HttpPatch(url, header, body)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); EXPECT_FALSE(response.isSuccessStatusCode()); EXPECT_EQ(RestStatusCode::Error405, response.getStatusCode()); } } #if defined(US_SDK_S2S) /// @test Performs an HTTP PATCH with signature TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpPATCHWithSignedRequest) { Facade facade; const AsyncResult resultLogin = facade.createSession(GET_CREDENTIALS(), {}, {}, GET_AUTHENTICATION_CERTIFICATE_PRIVATE()); ASSERT_TRUE(AsyncHelper::waitSuccess(resultLogin)); SessionInfo sessionInfo = facade.getAuthenticationClient().getSessionInfo(); ASSERT_TRUE(sessionInfo.getSessionId().isValid()); JsonWriter writer; writer["patch this bug"] = 0; const HttpHeader header = TestHelpers_BF::getHttpContentHeader(); const String body = writer.renderContent(); { const StringUrl url(TestHelpers_BF::getHttpTestUrl("patch")); const AsyncResult result = facade.getHttpClient().sendRequest(HttpPatch(url, header, body), HttpRequestSignType::Hmac); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); EXPECT_TRUE(result.getResult().isBodyTypeString()); ASSERT_TRUE(TestHttpClient_BF::validateHttpBoxHeadersInBodyResponse( result.getResult().getBodyAsString(), { "Ubi-Signature-timestamp", "Ubi-Signature", "x-forwarded-url" }) ); } } US_STATIC_ASSERT(static_cast(TestHttpClient_BF::ParamsHmacState::ENUM_COUNT) == 3, "HTTP_CLIENT_NO_INIT_HMAC_FIXTURE needs to be updated"); typedef ParamFixture HTTP_CLIENT_NO_INIT_HMAC_FIXTURE; INSTANTIATE_TEST_CASE_P(ParamsHmacState, HTTP_CLIENT_NO_INIT_HMAC_FIXTURE, ::testing::Values( TestHttpClient_BF::ParamsHmacState{ TestHttpClient_BF::ParamsHmacState::Valid }, // valid private key TestHttpClient_BF::ParamsHmacState{ TestHttpClient_BF::ParamsHmacState::Invalid }, // invalid private key TestHttpClient_BF::ParamsHmacState{ TestHttpClient_BF::ParamsHmacState::Empty })); // empty private key // Test must run in dev environment, (HMAC check is not done on UAT server side). TEST_P(HTTP_CLIENT_NO_INIT_HMAC_FIXTURE, TestHmacSignature) { const US_TEST_NS::ScopedInit autoInit; { HttpConfig httpConfig; httpConfig.m_proxy = HttpProxyConfig(US_TEST_NS::PROXY_NAME, US_TEST_NS::PROXY_PORT); const GameConfig gameConfig( ApplicationId(NS_COMMON::Options::getApplicationId().c_str()), NS_COMMON::Options::getApplicationBuildId().c_str(), Environment::Dev); const SystemConfig systemConfig(ThreadingConfig(), httpConfig); configureSdk(gameConfig, systemConfig); } const TestHttpClient_BF::ParamsHmacState& hmacState = this->getParam(); Facade facade; const AsyncResult resultLogin = facade.createSession(GET_CREDENTIALS(), {}, {}, TestHttpClient_BF::getPrivateCertificateValue(hmacState)); ASSERT_TRUE(AsyncHelper::waitSuccess(resultLogin)); SessionInfo sessionInfo = facade.getAuthenticationClient().getSessionInfo(); ASSERT_TRUE(sessionInfo.getSessionId().isValid()); JsonWriter writer; writer["communicationOptIn"] = false; writer["communicationThirdPartyOptIn"] = false; writer["country"] = "US"; writer["dateOfBirth"] = "1982-02-20"; writer["email"] = "ussdk032@ubisoft.com"; writer["firstName"] = ""; writer["gender"] = "M"; writer["isDateOfBirthApprox"] = false; writer["lastName"] = ""; writer["password"] = "a1234567"; writer["nameOnPlatform"] = "ussdk032"; writer["preferredLanguage"] = "en"; writer["accountType"] = "Ubisoft"; HttpHeader header; header["Ubi-AppId"] = facade.getParametersClient().getUsApplicationId(); header["Ubi-SessionId"] = facade.getAuthenticationClient().getSessionInfo().getSessionId(); header["Authorization"] = "ubi_s2s_v1 t=" + facade.getAuthenticationClient().getSessionInfo().getTicket(); header["Content-Type"] = "application/json"; const StringUrl url("https://msr-dev-s2s-ubiservices.ubi.com/v3/users/validateCreation"); const AsyncResult result = facade.getHttpClient().sendRequest( HttpPost(url, header, writer.renderContent()), HttpRequestSignType::Hmac); if (hmacState == TestHttpClient_BF::ParamsHmacState::Valid) { ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); } else { ASSERT_TRUE(AsyncHelper::waitFailure(result, Errors::HTTP_SIGNING_FAILURE)); } } TEST_F(HTTP_CLIENT_NO_INIT, TestHmacSignatureSuccessWithUnicode) { const US_TEST_NS::ScopedInit autoInit; { HttpConfig httpConfig; httpConfig.m_proxy = HttpProxyConfig(US_TEST_NS::PROXY_NAME, US_TEST_NS::PROXY_PORT); const GameConfig gameConfig( ApplicationId(NS_COMMON::Options::getApplicationId().c_str()), NS_COMMON::Options::getApplicationBuildId().c_str(), Environment::Dev); const SystemConfig systemConfig(ThreadingConfig(), httpConfig); configureSdk(gameConfig, systemConfig); } Facade facade; const AsyncResult resultLogin = facade.createSession(GET_CREDENTIALS(), {}, {}, GET_AUTHENTICATION_CERTIFICATE_PRIVATE()); ASSERT_TRUE(AsyncHelper::waitSuccess(resultLogin)); SessionInfo sessionInfo = facade.getAuthenticationClient().getSessionInfo(); ASSERT_TRUE(sessionInfo.getSessionId().isValid()); HttpHeader header; header["Ubi-AppId"] = facade.getParametersClient().getUsApplicationId(); header["Ubi-SessionId"] = facade.getAuthenticationClient().getSessionInfo().getSessionId(); header["Authorization"] = "ubi_s2s_v1 t=" + facade.getAuthenticationClient().getSessionInfo().getTicket(); header["Content-Type"] = "application/json"; String body("{\"firstName\":\"My\u000BFirst\u000bName\"}"); const StringUrl url("https://msr-dev-s2s-ubiservices.ubi.com/v3/users/d27c61c4-7129-445f-9f5b-3bb7221a10e5"); // ussdk032 const AsyncResult result = facade.getHttpClient().sendRequest( HttpPatch(url, header, body), HttpRequestSignType::Hmac); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); } #endif /// @test Performs an HTTP Delete TEST_F(HTTP_CLIENT, HTTPBOX_MINRUN_PerformHttpDELETE) { Facade facade; JsonWriter writer; writer["delete some thing"] = 0; const HttpHeader header = TestHelpers_BF::getHttpContentHeader(); const String body = writer.renderContent(); { const StringUrl url = TestHelpers_BF::getHttpTestUrl("delete"); const AsyncResult result = facade.getHttpClient().sendRequest(HttpDelete(url, header, body)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); } // Errors { const StringUrl url = TestHelpers_BF::getHttpTestUrl("get"); const AsyncResult result = facade.getHttpClient().sendRequest(HttpDelete(url, header, body)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); EXPECT_FALSE(response.isSuccessStatusCode()); EXPECT_EQ(RestStatusCode::Error405, response.getStatusCode()); } } #if defined(US_SDK_S2S) /// @test Performs an HTTP DELETE with signature TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpDELETEWithSignedRequest) { Facade facade; const AsyncResult resultLogin = facade.createSession(GET_CREDENTIALS(), {}, {}, GET_AUTHENTICATION_CERTIFICATE_PRIVATE()); ASSERT_TRUE(AsyncHelper::waitSuccess(resultLogin)); SessionInfo sessionInfo = facade.getAuthenticationClient().getSessionInfo(); ASSERT_TRUE(sessionInfo.getSessionId().isValid()); JsonWriter writer; writer["delete some thing"] = 0; const HttpHeader header = TestHelpers_BF::getHttpContentHeader(); const String body = writer.renderContent(); { const StringUrl url = TestHelpers_BF::getHttpTestUrl("delete"); const AsyncResult result = facade.getHttpClient().sendRequest(HttpDelete(url, header, body), HttpRequestSignType::Hmac); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); EXPECT_TRUE(result.getResult().isBodyTypeString()); ASSERT_TRUE(TestHttpClient_BF::validateHttpBoxHeadersInBodyResponse( result.getResult().getBodyAsString(), { "Ubi-Signature-timestamp", "Ubi-Signature", "x-forwarded-url" }) ); } } #if !defined(US_DLL) TEST_F(HTTP_CLIENT, HTTPBOX_TestSignatureSuccessAfterExtendSession) { Facade facade; const AsyncResult resultLogin = facade.createSession(GET_CREDENTIALS(), {}, {}, GET_AUTHENTICATION_CERTIFICATE_PRIVATE()); ASSERT_TRUE(AsyncHelper::waitSuccess(resultLogin)); SessionInfo sessionInfo = facade.getAuthenticationClient().getSessionInfo(); ASSERT_TRUE(sessionInfo.hasValidSession()); JsonWriter writer; writer["delete some thing"] = 0; const HttpHeader header = TestHelpers_BF::getHttpContentHeader(); const String body = writer.renderContent(); const StringUrl url = TestHelpers_BF::getHttpTestUrl("delete"); const AsyncResult result = facade.getHttpClient().sendRequest(HttpDelete(url, header, body), HttpRequestSignType::Hmac); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); // Create listener before triggering state change, so we can catch notification ListenerHandler notificationListener = facade.getAuthenticationClient().createNotificationListener(); TestExtendSessionHelper::reduceSessionDuration(facade, ClockSteady::getTimeMilli() + 500); // Setup expiration ASSERT_EQ(AuthenticationNotificationType::SessionInfoChanged, TestExtendSession_BF::waitForNotification(notificationListener, DurationMicro(Duration<>(5)))); sessionInfo = facade.getAuthenticationClient().getSessionInfo(); ASSERT_TRUE(sessionInfo.hasValidSession()); const AsyncResult result2 = facade.getHttpClient().sendRequest(HttpDelete(url, header, body), HttpRequestSignType::Hmac); ASSERT_TRUE(AsyncHelper::waitSuccess(result2)); EXPECT_EQ(RestStatusCode::OK, result2.getResult().getStatusCode()); } #endif // !defined(US_DLL) #endif // defined(US_SDK_S2S) // // /// @test Validates that the number of calls to *ubiservices.ubi.com is not limited. // /// @note It's not really reliable to test that some asyncs will be delayed since it highly // /// depends on the timing. Therefore, the test only checks that the calls were successful. // // TODO: Review this test as per US_HttpClientRequestLimitCustom implementation // TEST_F(HTTP_CLIENT, US_HttpClientRequestLimit) // { // const uint32 REQUEST_COUNT = 11; // Vector allRequests; // // Facade facade; // // const StringUrl url("https://public-ubiservices.ubi.com/v1/status"); // // EXPECT_TRUE(AsyncHelper::waitSuccess(facade.getHttpClient().sendRequest(HttpGet(url, TestHelpers_BF::getHttpBoxHeader())))); // // for (uint32 i = 0; i <= REQUEST_COUNT; ++i) // { // allRequests.push_back(facade.getHttpClient().sendRequest(HttpGet(url, TestHelpers_BF::getHttpBoxHeader()))); // } // for (Vector::const_iterator it = allRequests.begin(); it != allRequests.end(); ++it) // { // EXPECT_TRUE(AsyncHelper::waitSuccess(*it)); // } // } #if !defined(US_DLL) namespace HttpClientImpl_BF { extern uint32 s_MAX_CONCURRENT_US_CALLS; } TEST_F(HTTP_CLIENT, US_HttpClientRequestLimitUbiServices) { const uint32 DELAY = 4; // This value must be below 10 seconds (until timeout value is specified) const uint32 REQUEST_COUNT = 20; ASSERT_GT(REQUEST_COUNT, HttpClientImpl_BF::s_MAX_CONCURRENT_US_CALLS) << "Sanity check for the test configuration"; Vector,char>> allRequests; Facade facade; const StringUrl url = TestHelpers_BF::getHttpTestUrl("get", String::formatText("delay=%d", DELAY), false, false); ASSERT_TRUE(url.findSubstringCase("ubiservices.ubi.com") != String::npos) << "The test requirement is missing."; for (uint32 i = 0; i < REQUEST_COUNT; ++i) { allRequests.push_back(std::pair, char>(facade.getHttpClient().sendRequest(HttpGet(url, TestHelpers_BF::getHttpBoxHeader())), '_')); US_TEST_NS::Helper::sleep((DELAY/20)*1000); } US_TEST_NS::Helper::sleep((DELAY/2)*1000); // The following loop is not required for the test, but it is disabled for Linux, // because tests run in a VM with limited resources and exposes the weakness of this test: // The async result states may change while iterating, leading to invalid counters values! #if !defined(US_TARGET_LINUX) { uint32 delayed = 0; String states; for (auto& request : allRequests) { request.second = AsyncResultBaseProxy::isCurrentlyDelayed(request.first)? 'D' : '.'; states += String::formatText("%c", request.second); if (AsyncResultBaseProxy::isCurrentlyDelayed(request.first)) { delayed++; } } //PRINT_LINE("STATES [%s]", states.getUtf8()); EXPECT_EQ(REQUEST_COUNT - HttpClientImpl_BF::s_MAX_CONCURRENT_US_CALLS, delayed) << "The SDK limits the number of concurrent requests with URL containing 'ubiservices.ubi.com'."; } #endif HighResolutionChrono chrono; uint32 done = 0; const uint32 MAX_TIME_FOR_ALL_REQUESTS_SEC = 30; while (done < allRequests.size() && chrono.getElapsed >() < MAX_TIME_FOR_ALL_REQUESTS_SEC) { done = 0; String statesLog; // This implementation is not perfect. While iterating // through the vector, processed async objects state could change in the meantime! // Minimizing the processing in this loop is a best effort... // (copying allRequests would have no benefit as the AsyncResult instances would perform shallow copies) for (auto& request : allRequests) { if (request.second == 'F') // Request already finished and checked { continue; } // Update status request.second = AsyncResultBaseProxy::isCurrentlyDelayed(request.first) ? 'D' : (request.first.hasFailed() || request.first.hasSucceeded()) ? 'X' : '.'; } uint32 processingCount = 0; bool hasNewRequestFinished = false; for (auto& request : allRequests) { processingCount = processingCount + (request.second == '.'); if (request.second == 'F') { done++; } if (request.second == 'X') { done++; EXPECT_TRUE(AsyncHelper::waitSuccess(request.first)); EXPECT_EQ(200, reinterpret_cast&>(request.first).getResult().getStatusCode()); request.second = 'F'; hasNewRequestFinished = true; } statesLog = statesLog + String::formatText("%c", request.second); } EXPECT_LE(processingCount, HttpClientImpl_BF::s_MAX_CONCURRENT_US_CALLS) << "States: " << statesLog.getUtf8() << " | Done = " << done; if (hasNewRequestFinished) { //PRINT_LINE("States [%s], done = %d", statesLog.getUtf8(), done); } } EXPECT_EQ(allRequests.size(), done) << MAX_TIME_FOR_ALL_REQUESTS_SEC << " seconds timeout reached"; } // This test is used to verify that there is no limit on the number // of paralel HTTP requests done to hosts other than ubiservices.ubi.com TEST_F(HTTP_CLIENT, US_HttpClientRequestLimitCustom) { const uint32 DELAY = 2; const uint32 REQUEST_COUNT = 10; Vector > allRequests; Facade facade; // This URL should work over HTTP from any office. // Note: It might stop working someday as the server was moved in the cloud, but at the moment using our HttpBox URL is the best solution. const StringUrl url = "http://msr-d-us-iis01.ubisoft.onbe/UbiServices.HttpBox.Services/v1/httpbox/get?" + String::formatText("delay=%d", DELAY); HttpRetryConfig config(0, HttpRetryConfig::RetryParam(), HttpRetryConfig::TimeoutParam(30000ul)); for (uint32 i = 0; i < REQUEST_COUNT; ++i) { allRequests.push_back(facade.getHttpClient().sendRequest(HttpGet(url, TestHelpers_BF::getHttpBoxHeader()), config)); } US_TEST_NS::Helper::sleep((DELAY/2)*1000); uint32 delayed = 0; for (Vector >::const_iterator it = allRequests.begin(); it != allRequests.end(); ++it) { if (AsyncResultBaseProxy::isCurrentlyDelayed(*it)) delayed++; } //PRINT_LINE("BEFORE"); #if defined(US_TARGET_SWITCH) // Only SWITCH console requires to limit the number of concurrent HTTPS requests (custom or not). EXPECT_EQ(REQUEST_COUNT - HttpClientImpl_BF::s_MAX_CONCURRENT_US_CALLS, delayed) << "The SWITCH requires to limit the number of concurrent HTTPS requests (custom or not)."; #else EXPECT_EQ(0, delayed); #endif //PRINT_LINE("AFTER"); for (Vector >::const_iterator it = allRequests.begin(); it != allRequests.end(); ++it) { EXPECT_TRUE(AsyncHelper::waitSuccess(*it)); EXPECT_EQ(200, reinterpret_cast&>(*it).getResult().getStatusCode()); } } #endif #if defined(US_TARGET_WINDOWS) && !defined(US_DLL) TEST_F(HTTP_CLIENT, SSL_ValidateCertWithoutKeysAndObfuscator) { // If public-UbiServices, verification is automatic HttpGet requestUbiservices(TestSslCert_BF::UBISERVICES_UAT_PUBLIC_SSL_URL, TestHelpers_BF::getHttpBoxHeader()); ASSERT_TRUE(AsyncHelper::waitSuccess(Facade().getHttpClient().sendRequest(requestUbiservices))); // For non public-UbiServices URL, if both keys and obfuscator are not specified, no verification is required (see testSslCertificate.cpp) // By default if there aren't any verifications, the request will always be sent with success. HttpGet requestAny(TestSslCert_BF::UBISERVICES_DEV_SSL_URL, TestHelpers_BF::getHttpBoxHeader()); ASSERT_TRUE(AsyncHelper::waitSuccess(Facade().getHttpClient().sendRequest(requestAny))); } TEST_F(HTTP_CLIENT, SSL_ValidateRequestWithEmptyKeysAndObfuscator) { // Any URL will work since the request is not sent, we only want to verify empty request settings HttpGet requestAny(TestSslCert_BF::UBISERVICES_DEV_SSL_URL, TestHelpers_BF::getHttpBoxHeader()); // A request setting is valid only if both keys and obfuscator are not empty ASSERT_FALSE(TestHttpClient_BF::setRequestWithEmptyKeysAndObfuscator(requestAny, true, true)); ASSERT_FALSE(TestHttpClient_BF::setRequestWithEmptyKeysAndObfuscator(requestAny, false, true)); ASSERT_FALSE(TestHttpClient_BF::setRequestWithEmptyKeysAndObfuscator(requestAny, true, false)); ASSERT_TRUE(TestHttpClient_BF::setRequestWithEmptyKeysAndObfuscator(requestAny, false, false)); } TEST_F(HTTP_CLIENT, SSL_ValidateCertWithEmptyKeysAndObfuscator) { // If UbiServices, verification is automatic HttpGet requestUbiservices(TestSslCert_BF::UBISERVICES_UAT_PUBLIC_SSL_URL, TestHelpers_BF::getHttpBoxHeader()); ASSERT_FALSE(TestHttpClient_BF::setRequestWithEmptyKeysAndObfuscator(requestUbiservices, true, true)); ASSERT_TRUE(AsyncHelper::waitSuccess(Facade().getHttpClient().sendRequest(requestUbiservices))); // For non public-UbiServices URL, if both keys and obfuscator are empty, verification is not possible // By default if there aren't any verifications, the request will always be sent with success. HttpGet requestAny(TestSslCert_BF::UBISERVICES_DEV_SSL_URL, TestHelpers_BF::getHttpBoxHeader()); ASSERT_FALSE(TestHttpClient_BF::setRequestWithEmptyKeysAndObfuscator(requestAny, true, true)); ASSERT_TRUE(AsyncHelper::waitSuccess(Facade().getHttpClient().sendRequest(requestAny))); } TEST_F(HTTP_CLIENT, SSL_ValidateCertWithInvalidKeysAndObfuscator) { // These cases only apply to custom URL, since with UbiServices, verification is automatic HttpGet requestAny("https://google.com", TestHelpers_BF::getHttpBoxHeader()); // setRequestWithCustomKeysAndObfuscator() will always return true // since both keys and obfuscator are not empty, they're just invalid. // invalid key, invalid obfuscator ASSERT_TRUE(TestHttpClient_BF::setRequestWithCustomKeysAndObfuscator(requestAny, false, false)); ASSERT_FALSE(AsyncHelper::waitSuccess(Facade().getHttpClient().sendRequest(requestAny))); // valid key, invalid obfuscator ASSERT_TRUE(TestHttpClient_BF::setRequestWithCustomKeysAndObfuscator(requestAny, true, false)); ASSERT_FALSE(AsyncHelper::waitSuccess(Facade().getHttpClient().sendRequest(requestAny))); // invalid key, valid obfuscator ASSERT_TRUE(TestHttpClient_BF::setRequestWithCustomKeysAndObfuscator(requestAny, false, true)); ASSERT_FALSE(AsyncHelper::waitSuccess(Facade().getHttpClient().sendRequest(requestAny))); // valid key, valid obfuscator -> This does not work with non-ubiservices url since the keys are ubiservices only. //ASSERT_TRUE(TestHttpClient_BF::setRequestWithCustomKeysAndObfuscator(requestAny, true)); //ASSERT_TRUE(AsyncHelper::waitSuccess(Facade().getHttpClient().sendRequest(requestAny))); } // Validation only works on PC because ussdkcertvalidation server // certificate is not signed by a trusted CA on consoles. TEST_F(HTTP_CLIENT, SSL_ValidateCertChainBadIntermediate) { HttpGet request("https://ussdkcertvalidation.ubi.com:8443", TestHelpers_BF::getHttpBoxHeader()); // Need to provide at least one pinning key to activate pinning // because url is not pointing to our default url, and our keys // won't be added automatically ! TestHttpClient_BF::setRequestWithCustomKeysAndObfuscator(request, true, true); ASSERT_TRUE(AsyncHelper::waitFailure(Facade().getHttpClient().sendRequest(request), Errors::SSL_CERTIFICATE_INVALID_SIGNATURE)); } TEST_F(HTTP_CLIENT, SSL_ValidateCertChainBadIssuer) { HttpGet request("https://ussdkcertvalidation.ubi.com:8444", TestHelpers_BF::getHttpBoxHeader()); // Need to provide at least one pinning key to activate pinning // because url is not pointing to our default url, and our keys // won't be added automatically ! TestHttpClient_BF::setRequestWithCustomKeysAndObfuscator(request, true, true); ASSERT_TRUE(AsyncHelper::waitFailure(Facade().getHttpClient().sendRequest(request), Errors::SSL_CERTIFICATE_INVALID_SIGNATURE)); } TEST_F(HTTP_CLIENT, SSL_ValidateCertLeafCN) { HttpGet request("https://ussdkcertvalidation.ubi.com:8445", TestHelpers_BF::getHttpBoxHeader()); // Need to provide at least one pinning key to activate pinning // because url is not pointing to our default url, and our keys // won't be added automatically ! TestHttpClient_BF::setRequestWithCustomKeysAndObfuscator(request, true, true); ASSERT_TRUE(AsyncHelper::waitFailure(Facade().getHttpClient().sendRequest(request), Errors::SSL_CERTIFICATE_INVALID)); } TEST_F(HTTP_CLIENT, SSL_ValidateCertDateNotYetValid) { HttpGet request("https://ussdkcertvalidation.ubi.com:8447", TestHelpers_BF::getHttpBoxHeader()); // Need to provide at least one pinning key to activate pinning // because url is not pointing to our default url, and our keys // won't be added automatically ! TestHttpClient_BF::setRequestWithCustomKeysAndObfuscator(request, false, true, true); ASSERT_TRUE(AsyncHelper::waitFailure(Facade().getHttpClient().sendRequest(request), Errors::SSL_CERTIFICATE_ULTERIOR)); } TEST_F(HTTP_CLIENT, SSL_ValidateCertDateExpired) { HttpGet request("https://ussdkcertvalidation.ubi.com:8448", TestHelpers_BF::getHttpBoxHeader()); // Need to provide at least one pinning key to activate pinning // because url is not pointing to our default url, and our keys // won't be added automatically ! TestHttpClient_BF::setRequestWithCustomKeysAndObfuscator(request, true, true); ASSERT_TRUE(AsyncHelper::waitFailure(Facade().getHttpClient().sendRequest(request), Errors::SSL_CERTIFICATE_EXPIRED)); } /// @test Checks that an error is returned when the certificate does not contain the expected public key. TEST_F(HTTP_CLIENT, SSL_ValidateCertPinningWrongPublicKey) { HttpGet request("https://ussdkcertvalidation.ubi.com:8449", TestHelpers_BF::getHttpBoxHeader()); // Need to provide at least one pinning key to activate pinning // because url is not pointing to our default url, and our keys // won't be added automatically ! TestHttpClient_BF::setRequestWithCustomKeysAndObfuscator(request, true, true); ASSERT_TRUE(AsyncHelper::waitFailure(Facade().getHttpClient().sendRequest(request), Errors::SSL_CERTIFICATE_INVALID)); } /// @test Checks that the hardcoded obfuscated public keys are valid and functional /// Return an error when the certificate does not contain the expected public key. /// --- Important Note --- /// Key indexes are taken from UBISERVICES_SSL_ALLOWED_PUBLIC_KEY_OBFUSCATED_BASE64 in InstanceManager.cpp /// /// @disabled certificates must be renewed for this test to work. See JIRA US-15709 to reenable this test TEST_F(HTTP_CLIENT, SSL_ValidateLiveAndBackupCertificatePinningKeys) { struct pinningTestData { String url; uint32 keyIndex; OnlineAccessContext context; bool testEnabled; }; // *.ubi.com backup keys tests are disabled // Certificates need to be updated !!! Vector testUrls = { { "https://public-ubiservices.ubi.com/v1/status", 0, OnlineAccessContext::Standard, true }, // Live *.ubi.com { "https://ussdkcertvalidation.ubisoft.cn:8474", 1, OnlineAccessContext::China, true }, // Live *.ubisoft.cn { "https://ussdkcertvalidation.ubi.com:8471", 2, OnlineAccessContext::Standard, false },// Backup 1 *.ubi.com { "https://ussdkcertvalidation.ubisoft.cn:8475", 3, OnlineAccessContext::China, true }, // Backup 1 *.ubisoft.cn { "https://ussdkcertvalidation.ubi.com:8472", 4, OnlineAccessContext::Standard, false },// Backup 2 *.ubi.com { "https://ussdkcertvalidation.ubisoft.cn:8476", 5, OnlineAccessContext::China, true }, // Backup 2 *.ubisoft.cn { "https://ussdkcertvalidation.ubi.com:8473", 6, OnlineAccessContext::Standard, false },// Backup 3 *.ubi.com { "https://ussdkcertvalidation.ubisoft.cn:8477", 7, OnlineAccessContext::China, true } // Backup 3 *.ubisoft.cn }; for (pinningTestData testData : testUrls) { if (!testData.testEnabled) { PRINT_LINE("%s test is disabled", testData.url.getUtf8()); continue; } PRINT_LINE("Testing %s in pinning key index %d in OnlineAccedContext %s", testData.url.getUtf8(), testData.keyIndex, testData.context == OnlineAccessContext::Standard ? "Standard" : "China"); HttpGet request(testData.url, TestHelpers_BF::getHttpBoxHeader()); TestHttpClient_BF::setRequestWithCustomKeysAndObfuscator(request, true, true, testData.keyIndex); EXPECT_TRUE(AsyncHelper::waitSuccess(Facade().getHttpClient().sendRequest(request))) << "Pinning failed for URL: " << testData.url.getUtf8(); } } #endif // US_TARGET_WINDOWS /// @test Validates the SSL certificate over the managed UbiServices URL and for each environment. /// @note For these URLs, it's irrelevant what the requests return (forbidden code for most of them). /// We just want to make sure SSL can connect to the server. /// @disabled SSL Tests are useless for Xbox One and Xbox 360 since we do not have control over the enforcement. #if !defined(US_TARGET_XONE) TEST_F(HTTP_CLIENT_NO_INIT, US_HttpsValidateCertificate) { const US_TEST_NS::ScopedInit autoInit; // This test requires HTTPS and SSL validation #if !defined(US_RETAIL) s_forceNonSecure = false; #endif { HttpConfig httpConfig; #if defined(US_TARGET_WINDOWS) || defined(US_TARGET_OSX) || defined(US_TARGET_LINUX) || defined(US_TARGET_GGP) httpConfig.m_proxy = HttpProxyConfig(US_TEST_NS::PROXY_NAME, US_TEST_NS::PROXY_PORT); #endif const GameConfig gameConfig( ApplicationId(NS_COMMON::Options::getApplicationId().c_str()), NS_COMMON::Options::getApplicationBuildId().c_str() #if defined(US_SDK_S2S) , NS_COMMON::Options::environment() #endif , OnlineAccessContext::Standard #if !defined(US_SDK_S2S) , GameConfigEvent(US_SDK_VERSION_CONST_CHAR, US_TEST_NS::US_SDK_DUMMY_GAME_SKU, Vector()) #endif #if defined(US_TARGET_PS4) || defined(US_TARGET_SWITCH) || defined(US_TARGET_XONE) || defined(US_TARGET_GGP) || defined(US_TARGET_KILI) , GameConfigConsole(NS_COMMON::FIRSTPARTY_APP_ID) #endif ); const SystemConfig systemConfig(ThreadingConfig(), httpConfig); configureSdk(gameConfig, systemConfig); } { Facade facade; const Vector urls { //UBISERVICES.ubi.com "https://dev-public-ubiservices.ubi.com", "https://uat-public-ubiservices.ubi.com", "https://cert2-public-ubiservices.ubi.com", "https://public-ubiservices.ubi.com", //prod //UPLAY.ubi.com "https://dev-wspuplay-ext.ubi.com", "https://uat-uplay-ext.ubi.com", "https://cert2-wspuplay-ext.ubi.com", "https://wspuplay-ext.ubi.com" //prod }; for (const auto& urlIt : urls) { const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(urlIt, TestHelpers_BF::getHttpBoxHeader())); EXPECT_TRUE(result.wait(NS_COMMON::DEFAULT_WAIT_TIME)); // If there's an error, it shouldn't be a SSL certificate one. if(result.hasFailed()) { const uint32 code = static_cast(result.getError().m_code); EXPECT_FALSE(code >= Errors::SSL_GENERIC_FAILURE && code < Errors::SSL_END_SECTION_GUARD); } } } } #endif /// @test Performs an HTTP request retrieving a gzip body. TEST_F(HTTP_CLIENT, HTTPBOX_HttpGetEncodingGzip) { setupDebugLogging(); const String typeOfEncoding = "gzip"; Facade facade; const StringUrl url = TestHelpers_BF::getHttpTestUrl("get", "", true); HttpHeader headers = TestHelpers_BF::getHttpBoxHeader(); headers["Accept-Encoding"] = typeOfEncoding; const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, headers)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); EXPECT_EQ(RestStatusCode::OK, response.getStatusCode()); EXPECT_GT(response.getBodySize(), 0u); //PRINT_STREAM("Response: " << response); const String& contentEncoding = response.getHeader("Content-Encoding"); const String& contentLength = response.getHeader("Content-Length"); // Note: XB1/PS4/WINAPPSTORE automatically decompresses gzipped body // The Content-Length and the Content-Encoding should reflect this according to RFC 2616. #if defined(US_TARGET_PS4) || defined(US_TARGET_XONE) || defined(US_TARGET_UWP) || defined(US_TARGET_KILI) // Note : Checking the exact size of what is received is useful to ensure the uncompressed gzip received // has the expected size. However, there is currently no known way to get a response with a body with a constant length // Current test returns the headers sent, which depends on the platform, the firmware version, and others... // JIRA US-8156 to reenable this line // EXPECT_EQ(331u, response.getBodySize()); // Note : In case of bug, HttpResponse::getBodySize() could differ from HttpResponse::getBodyAsString().getLength() // e.g. JIRA US-7419 const String body = response.getBodyAsString(); EXPECT_EQ(response.getBodySize(), body.getLength()); EXPECT_EQ("", contentEncoding); EXPECT_EQ("", contentLength); EXPECT_TRUE(Json(body).isValid()); #else // The other platforms do not uncompress the zipped body. // http://stackoverflow.com/questions/9487752/decoding-compressed-response-libcurl EXPECT_EQ(typeOfEncoding, contentEncoding); EXPECT_NE(contentLength, ""); #endif } /// @test Performs an HTTP request retrieving a deflate body. TEST_F(HTTP_CLIENT, HTTPBOX_HttpGetEncodingDeflate) { setupDebugLogging(); const String typeOfEncoding = "deflate"; Facade facade; const StringUrl url = TestHelpers_BF::getHttpTestUrl("get", "", true); HttpHeader headers = TestHelpers_BF::getHttpBoxHeader(); headers["Accept-Encoding"] = typeOfEncoding; const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, headers)); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); EXPECT_EQ(RestStatusCode::OK, response.getStatusCode()); EXPECT_GT(response.getBodySize(), 0u); //PRINT_STREAM("Response: " << response); const String& contentEncoding = response.getHeader("Content-Encoding"); const String& contentLength = response.getHeader("Content-Length"); // Note: XB1/WINAPPSTORE automatically decompresses gzipped body // The Content-Length and the Content-Encoding should reflect this according to RFC 2616. #if defined(US_TARGET_XONE) || defined(US_TARGET_UWP) // Note : Checking the exact size of what is received is useful to ensure the uncompressed gzip received // has the expected size. However, there is currently no known way to get a response with a body with a constant length // Current test returns the headers sent, which depends on the platform, the firmware version, and others... // JIRA US-8156 to reenable this line // EXPECT_EQ(331u, response.getBodySize()); // Note : In case of bug, HttpResponse::getBodySize() could differ from HttpResponse::getBodyAsString().getLength() // e.g. JIRA US-7419 const String body = response.getBodyAsString(); EXPECT_EQ(response.getBodySize(), body.getLength()); EXPECT_EQ("", contentEncoding); EXPECT_EQ("", contentLength); EXPECT_TRUE(Json(body).isValid()); #else // The other platforms do not uncompress the zipped body. // http://stackoverflow.com/questions/9487752/decoding-compressed-response-libcurl EXPECT_EQ(typeOfEncoding, contentEncoding); EXPECT_NE(contentLength, ""); #endif } /// @test Verifies the maximum URL length the client can process on the platform. /// HttpBin returns a 404 code (not found) if the URL is treated by the server, /// else it returns a 400 bad request (Request Line is too large). TEST_F(HTTP_CLIENT, DISABLED_MANUAL_URLMaximumLength) { char const URL_BASE[] = "http://httpbin.org/"; const size_t URL_LENGTH_MINIMUM = 2048u; const size_t URL_STEPPING_LENGTH = 64u; const std::string filler(URL_LENGTH_MINIMUM + (URL_STEPPING_LENGTH - (sizeof(URL_BASE) - 1)), 'a'); StringUrl url = StringUrl(URL_BASE) + filler.c_str(); EXPECT_EQ(URL_LENGTH_MINIMUM + URL_STEPPING_LENGTH, url.getLength()); Facade facade; { const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, TestHelpers_BF::getHttpBoxHeader())); EXPECT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::Error404, result.getResult().getStatusCode()) << "The client should support URL's length of at least " << URL_LENGTH_MINIMUM; } while (SystemChecker::GetTrue()) { url += std::string(URL_STEPPING_LENGTH, 'a').c_str(); AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, TestHelpers_BF::getHttpBoxHeader())); EXPECT_TRUE(AsyncHelper::waitSuccess(result)); if (result.getResult().getStatusCode() == RestStatusCode::Error404) { if (url.getLength() > 0xFFFF) { PRINT_LINE("Processed size can be greater than %d. Validation ending.", url.getLength()); return; } else { PRINT_LINE("Succesfully Processed size: %d", url.getLength()); } } else { while (result.getResult().getStatusCode() != RestStatusCode::Error404) { url = url.truncateTo(url.getLength() - 1); result = facade.getHttpClient().sendRequest(HttpGet(url, TestHelpers_BF::getHttpBoxHeader())); EXPECT_TRUE(AsyncHelper::waitSuccess(result)); } PRINT_LINE("Error code returned: %d", result.getResult().getStatusCode()); PRINT_LINE("URL Maximum size processed: %d", (url.getLength() - 1)); #if defined(US_TARGET_WINDOWS) EXPECT_EQ(8210u, url.getLength()); #else // TODO #endif return; } } } /// @test Checks the support for the HTTP status codes TEST_F(HTTP_CLIENT, DISABLED_MANUAL_HttpStatusCodes) { Facade facade; const Vector statusCodes { RestStatusCode::Error304, RestStatusCode::Error406, RestStatusCode::Error416, RestStatusCode::Error501, RestStatusCode::Error505 }; for (const auto& code : statusCodes) { { StringUrl url = StringUrl("http://httpbin.org/status/" + String::getText(code)); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, TestHelpers_BF::getHttpBoxHeader())); EXPECT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(code, result.getResult().getStatusCode()) << "The HTTP client should support status code " << code; } } } #if defined(US_TARGET_PS4) || defined(US_TARGET_KILI) TEST_F(HTTP_CLIENT, HTTPBOX_HttpSslPoolsUsage) { Facade facade; const StringUrl url = TestHelpers_BF::getHttpTestUrl("post"); const HttpHeader header = TestHelpers_BF::getHttpContentHeader(); const AsyncResult result = facade.getHttpClient().sendRequest(HttpPost(url, header, "")); while (result.isProcessing()) { HttpSslPoolsUsage memoryPools = facade.getHttpClient().getHttpSslPoolsUsage(); ASSERT_TRUE(memoryPools.m_httpPoolSizeInBytes != 0); ASSERT_TRUE(memoryPools.m_sslPoolSizeInBytes != 0); PRINT_STREAM("HTTP Memory pool usage : " << memoryPools.m_httpBytesInUse << "/" << memoryPools.m_httpPoolSizeInBytes); PRINT_STREAM("SSL Memory pool usage : " << memoryPools.m_sslBytesInUse << "/" << memoryPools.m_sslPoolSizeInBytes); US_NS::Helper::sleep(10); } ASSERT_TRUE(AsyncHelper::waitSuccess(result)); } #endif #if defined(US_TARGET_XONE) /// @test Checks that changing the host header in a xml request works correctly TEST_F(HTTP_CLIENT, HTTPBOX_PerformHttpGETWithDifferentHost) { Facade facade; const StringUrl url = TestHelpers_BF::getHttpTestUrl("get"); HttpHeader header = TestHelpers_BF::getHttpBoxHeader(); // Because we have the property "XHR_PROP_NO_DEFAULT_HEADERS", the request will use the Host header and fail. // Without that property the request would use the destination Host and succeed. header.addHeader("Host", "randomtesthost"); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url, header)); ASSERT_TRUE(AsyncHelper::waitFailure(result,Errors::HTTP_REQUEST_TRANSFER_FAILURE)); } #endif #if defined(US_TARGET_OSX) || defined(US_TARGET_IOS) // Note: The test was disabled as it fails most of the time on the build farm. // Could it may be a wrong(network) configuration on some agents? [US-15052] TEST_F(HTTP_CLIENT_NO_INIT, DISABLED_USNOT_SendRequestUsingGlobalAutoDiscovery) #else TEST_F(HTTP_CLIENT_NO_INIT, USNOT_SendRequestUsingGlobalAutoDiscovery) #endif { const US_TEST_NS::ScopedInit autoInit; { HttpConfig httpConfig; httpConfig.m_proxy.m_proxyType = HttpProxyConfig::Type::AutoProxy; // This code sets the environment variable all_proxy so that libcurl can detect it automatically. #if defined(US_TARGET_WINDOWS) { _putenv_s("all_proxy", US_TEST_NS::ProxyHelper::findAutomaticProxySettings(TestHttpClient_BF::URL_INTERNET).getUtf8()); } #endif const GameConfig gameConfig( ApplicationId(NS_COMMON::Options::getApplicationId().c_str()), NS_COMMON::Options::getApplicationBuildId().c_str() #if defined(US_SDK_S2S) , NS_COMMON::Options::environment() #endif , OnlineAccessContext::Standard #if !defined(US_SDK_S2S) , GameConfigEvent(US_SDK_VERSION_CONST_CHAR, US_TEST_NS::US_SDK_DUMMY_GAME_SKU, Vector()) #endif #if defined(US_TARGET_PS4) || defined(US_TARGET_SWITCH) || defined(US_TARGET_XONE) || defined(US_TARGET_GGP) || defined(US_TARGET_KILI) , GameConfigConsole(NS_COMMON::FIRSTPARTY_APP_ID) #endif ); configureSdk(gameConfig, SystemConfig(ThreadingConfig(), httpConfig)); } { const AsyncResult result = Facade().getHttpClient().sendRequest(HttpGet(TestHttpClient_BF::URL_INTERNET)); EXPECT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); } } TEST_F(HTTP_CLIENT_NO_INIT, USNOT_SendRequestUsingGlobalProxyInfo) { const US_TEST_NS::ScopedInit autoInit; { HttpConfig httpConfig; httpConfig.m_proxy = HttpProxyConfig(US_TEST_NS::PROXY_NAME, US_TEST_NS::PROXY_PORT); httpConfig.m_proxy.m_proxyType = HttpProxyConfig::Type::Enabled; const GameConfig gameConfig( ApplicationId(NS_COMMON::Options::getApplicationId().c_str()), NS_COMMON::Options::getApplicationBuildId().c_str() #if defined(US_SDK_S2S) , NS_COMMON::Options::environment() #endif , OnlineAccessContext::Standard #if !defined(US_SDK_S2S) , GameConfigEvent(US_SDK_VERSION_CONST_CHAR, US_TEST_NS::US_SDK_DUMMY_GAME_SKU, Vector()) #endif #if defined(US_TARGET_PS4) || defined(US_TARGET_SWITCH) || defined(US_TARGET_XONE) || defined(US_TARGET_GGP) || defined(US_TARGET_KILI) , GameConfigConsole(NS_COMMON::FIRSTPARTY_APP_ID) #endif ); configureSdk(gameConfig, SystemConfig(ThreadingConfig(), httpConfig)); } { const AsyncResult result = Facade().getHttpClient().sendRequest(HttpGet(TestHttpClient_BF::URL_INTERNET)); EXPECT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); } } #if defined(US_USE_LIBCURL) && !defined(US_TARGET_SWITCH) // The Switch ignores libcurl proxy settings. TEST_F(HTTP_CLIENT_NO_INIT, USNOT_SendRequestUsingGlobalProxyInfoWrong) { const US_TEST_NS::ScopedInit autoInit; { HttpConfig httpConfig; httpConfig.m_proxy = HttpProxyConfig("BadProxyInfo", US_TEST_NS::PROXY_PORT, String(), String(), HttpProxyConfig::Type::Enabled); const GameConfig gameConfig( ApplicationId(NS_COMMON::Options::getApplicationId().c_str()), NS_COMMON::Options::getApplicationBuildId().c_str() #if defined(US_SDK_S2S) , NS_COMMON::Options::environment() #endif , OnlineAccessContext::Standard #if !defined(US_SDK_S2S) , GameConfigEvent(US_SDK_VERSION_CONST_CHAR, US_TEST_NS::US_SDK_DUMMY_GAME_SKU, Vector()) #endif #if defined(US_TARGET_PS4) || defined(US_TARGET_SWITCH) || defined(US_TARGET_XONE) || defined(US_TARGET_GGP) || defined(US_TARGET_KILI) , GameConfigConsole(NS_COMMON::FIRSTPARTY_APP_ID) #endif ); configureSdk(gameConfig, SystemConfig(ThreadingConfig(), httpConfig)); } { EXPECT_TRUE(AsyncHelper::waitFailure(Facade().getHttpClient().sendRequest(HttpGet(TestHttpClient_BF::URL_INTERNET)), Errors::HTTP_REQUEST_TRANSFER_FAILURE)); } } #endif #if defined(US_ENABLE_PROXY_CONFIG_PER_REQ) TEST_F(HTTP_CLIENT, USNOT_SendRequestUsingRequestAutoProxy) { Facade facade; HttpGet request(TestHttpClient_BF::URL_INTERNET); HttpProxyConfig proxyConfig; proxyConfig.m_proxyType = HttpProxyConfig::Type::AutoProxy; // This code sets the environment variable all_proxy so that libcurl can detect it automatically. #if defined(US_TARGET_WINDOWS) { _putenv_s("all_proxy", US_TEST_NS::ProxyHelper::findAutomaticProxySettings(TestHttpClient_BF::URL_INTERNET).getUtf8()); } #endif request.setProxyInfo(proxyConfig); const AsyncResult result = facade.getHttpClient().sendRequest(request); EXPECT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); } TEST_F(HTTP_CLIENT, USNOT_SendRequestUsingRequestProxyEnabled) { { Facade facade; HttpGet request(TestHttpClient_BF::URL_INTERNET); HttpProxyConfig proxyConfig = HttpProxyConfig(US_TEST_NS::PROXY_NAME, US_TEST_NS::PROXY_PORT); proxyConfig.m_proxyType = HttpProxyConfig::Type::Enabled; request.setProxyInfo(proxyConfig); const AsyncResult result = facade.getHttpClient().sendRequest(request); EXPECT_TRUE(AsyncHelper::waitSuccess(result)); EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); } { Facade facade; HttpGet request(TestHttpClient_BF::URL_INTERNET); HttpProxyConfig proxyConfig = HttpProxyConfig(US_TEST_NS::PROXY_NAME, US_TEST_NS::PROXY_PORT); proxyConfig.m_proxyType = HttpProxyConfig::Type::Disabled; request.setProxyInfo(proxyConfig); EXPECT_TRUE(AsyncHelper::waitFailure(facade.getHttpClient().sendRequest(request), Errors::HTTP_TIMEOUT)); //EXPECT_EQ(RestStatusCode::OK, result.getResult().getStatusCode()); } { Facade facade; HttpGet request("http://annahar.com/"); // URL provided by elias.chaoul@ubisoft.com for proxy tests. HttpProxyConfig proxyConfig; proxyConfig.m_proxyType = HttpProxyConfig::Type::Disabled; request.setProxyInfo(proxyConfig); EXPECT_TRUE(AsyncHelper::waitFailure(facade.getHttpClient().sendRequest(request), Errors::HTTP_TIMEOUT)); } } TEST_F(HTTP_CLIENT, USNOT_SendRequestUsingRequestProxyWrong) { Facade facade; HttpGet request(TestHttpClient_BF::URL_INTERNET); HttpProxyConfig proxyConfig = HttpProxyConfig("BadProxyUrl", US_TEST_NS::PROXY_PORT); proxyConfig.m_proxyType = HttpProxyConfig::Type::Enabled; request.setProxyInfo(proxyConfig); EXPECT_TRUE(AsyncHelper::waitFailure(facade.getHttpClient().sendRequest(request), Errors::HTTP_REQUEST_TRANSFER_FAILURE)); } #endif // defined(US_ENABLE_PROXY_CONFIG_PER_REQ) #if defined(US_SDK_S2S) // This test make sure that any request can be signed and not just "remotelog" / "ubiservices.ubi.com" domain. This test // use Httpbin because we want to hit a domain that is outside of Ubisoft and it's not possible to do that with HttpBox. TEST_F(HTTP_CLIENT, HTTPBIN_PerformSignedHttpGETOutsideUbisoftDomain) { Facade facade; const AsyncResult resultLogin = facade.createSession(GET_CREDENTIALS(), {}, {}, GET_AUTHENTICATION_CERTIFICATE_PRIVATE()); ASSERT_TRUE(AsyncHelper::waitSuccess(resultLogin)); SessionInfo sessionInfo = facade.getAuthenticationClient().getSessionInfo(); ASSERT_TRUE(sessionInfo.getSessionId().isValid()); const StringUrl url = "http://httpbin.org/headers"; // Return the incoming request's HTTP headers. // Using a custom request timeout to make sure this test won't fail if external site is very slow to respond. HttpRetryConfig retryConfig = HttpRetryConfig(0, HttpRetryConfig::RetryParam(), HttpRetryConfig::TimeoutParam(60000)); const AsyncResult result = facade.getHttpClient().sendRequest(HttpGet(url), retryConfig, HttpRequestSignType::Hmac); ASSERT_TRUE(AsyncHelper::waitSuccess(result)); const HttpResponse& response = result.getResult(); ASSERT_EQ(RestStatusCode::OK, response.getStatusCode()); String body = response.getBodyAsString(); // These headers are present in our request only if the request is signed ASSERT_TRUE(TestHttpClient_BF::validateHttpBoxHeadersInBodyResponse( response.getBodyAsString(), { "Ubi-Signature-Timestamp", "Ubi-Signature", "X-Forwarded-Url" })); } #endif // defined(US_SDK_S2S) #if defined(US_TARGET_WINDOWS) #pragma warning(disable:4068) // unknown pragma #pragma BullseyeCoverage restore #pragma warning(default:4068) #endif };