JD2022-TU1/main/extern/UbiServices/custom/testsIntegration/testHttpClient.cpp

2600 lines
107 KiB
C++

////////////////////////////////////////////////////////////////////////////////
// (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 <ubiservices/facade.h>
#include <ubiservices/core/errors/errorDetails.h>
#include <../private/ubiservices/core/extensions/staticAssert.h>
#include <ubiservices/core/JSON/jsonWriter.h>
#include <ubiservices/core/types/containers.h>
#include <ubiservices/core/types/string.h>
#include <ubiservices/core/types/dateTime.h>
#include <ubiservices/services/http/httpClient.h>
#include <ubiservices/services/http/request/httpRequestIncludes.h>
#include <ubiservices/services/parameters/parametersClient.h>
#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 <usCommon/authenticationHelper.h>
#include <usCommon/firstPartyInit.h>
#include <usCommon/options.h>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <string>
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<String>& 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<AuthenticationNotification>& 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<uint8> 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<uint8> createUploadData(uint32 iteration)
{
EXPECT_GT(iteration, 0u);
Vector<uint8> 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<Json> getHeaderJsonFromHttpBoxBody(const String& body, const Vector<String> &headersToBeFound = Vector<String>())
{
Vector<Json> headersFound;
if (!body.isEmpty())
{
const Json reader(body);
if (reader.isValid() && reader.isTypeObject())
{
const Vector<Json>& 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<String>& headersToValidate)
{
const Vector<Json>& 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<HttpMultipartData> createMultipartData()
{
const Vector<uint8> 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<HttpMultipartData> multiparts;
multiparts.reserve(2);
multiparts.push_back(HttpMultipartData("image_data", image, "image/png", "test.png"));
multiparts.push_back(HttpMultipartData("text_data", Vector<uint8>(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<size_t>(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<HttpResponse> 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<Empty> 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<HttpResponse> 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<HttpResponse> 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<Empty> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<uint8> 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<HttpResponse> 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<uint8> 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<HttpResponse> 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<HttpResponse> 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<uint8> 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<String, String> queryParam1("param1", "val1");
const ::std::pair<String, String> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<Empty> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<SI::milli>();
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<HttpResponse> 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<HttpResponse> 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<HTTP_CLIENT, TestParametersHttpMultipart_BF::ParamsHttpRequest> 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<HttpResponse> 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<const char*>(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<HttpResponse> 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<const char*>(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<HttpResponse> 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<const char*>(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<HttpResponse> 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<const char*>(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<HttpResponse> 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<const char*>(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<HttpResponse> 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<const char*>(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<size_t>(-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<HttpResponse> 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<Empty> 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<HttpResponse> 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<uint8> 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<HttpResponse> 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<uint8> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<Empty> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<Empty> 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<HttpResponse> 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<size_t>(TestHttpClient_BF::ParamsHmacState::ENUM_COUNT) == 3, "HTTP_CLIENT_NO_INIT_HMAC_FIXTURE needs to be updated");
typedef ParamFixture<HTTP_CLIENT_NO_INIT, TestHttpClient_BF::ParamsHmacState> 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<Empty> 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<HttpResponse> 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<Empty> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<Empty> 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<HttpResponse> 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<Empty> 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<HttpResponse> 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<AuthenticationNotification> 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<HttpResponse> 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<AsyncResultBase> 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<AsyncResultBase>::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<std::pair<AsyncResult<HttpResponse>,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<AsyncResult<HttpResponse>, 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<Ratio<1, 1> >() < 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<const AsyncResult<HttpResponse>&>(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<AsyncResult<HttpResponse> > 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<AsyncResult<HttpResponse> >::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<AsyncResult<HttpResponse> >::const_iterator it = allRequests.begin(); it != allRequests.end(); ++it)
{
EXPECT_TRUE(AsyncHelper::waitSuccess(*it));
EXPECT_EQ(200, reinterpret_cast<const AsyncResult<HttpResponse>&>(*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<pinningTestData> 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<EventTypeInfo>())
#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<StringUrl> 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<HttpResponse> 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<uint32>(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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<uint32> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<EventTypeInfo>())
#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<HttpResponse> 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<EventTypeInfo>())
#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<HttpResponse> 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<EventTypeInfo>())
#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<HttpResponse> 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<HttpResponse> 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<Empty> 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<HttpResponse> 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
};