JD2022-TU1/main/extern/UbiServices/custom/testsUnit/http/curl/testHttpEngineCurl.cpp

530 lines
No EOL
20 KiB
C++

////////////////////////////////////////////////////////////////////////////////
// (c) Copyright, Ubisoft Entertainment Inc. || Created by Harbour //
////////////////////////////////////////////////////////////////////////////////
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <tests/common/helpers.h>
#include <tests/fixture/memoryCheckFixture.h>
#include <ubiservices/core/configs/configpriv.h>
#include <ubiservices/core/http/httpEngine.h>
#include <ubiservices/core/http/httpEngineConfig.h>
#include <ubiservices/core/http/httpRequestContext.h>
#include <ubiservices/core/http/httpRequestInternal.h>
#include <ubiservices/core/http/httpStringEntity.h>
#include <ubiservices/core/http/curl/httpEngineCurl.h>
#include <ubiservices/core/http/curl/httpRequestCurl.h>
#include <ubiservices/services/http/httpConfig.h>
#include <ubiservices/services/http/request/httpGet.h>
#include <testsUnit/mock/mockHttpRequest.h>
#include "mockCurlImpl.h"
namespace US_NS {;
US_DEFINE_TEST(TestHttpEngineCurl);
};
#ifndef US_DLL
namespace US_NS {;
typedef LeakChecker HTTP_ENGINE_CURL;
typedef LeakChecker HTTP_ENGINE_CURL_MOCK;
/// @test Checks that curl is initialized properly (sequence and flags)
/// @note The test involves TWO instances of HttpEngineCurl in memory!
/// - The first one is the real instance created by the SDK.
/// - The second one is created in this scope and uses MockCurlImpl.
TEST_F(HTTP_ENGINE_CURL_MOCK, LOCAL_CurlEngineInitializeSuccess)
{
using namespace testing;
MockCurlImpl* curlImpl = US_NEW(MockCurlImpl);
HttpEngineCurl curlEngine(curlImpl);
#if defined(US_TARGET_WINDOWS) || defined(US_TARGET_SWITCH)
const uint32 FLAGS = CURL_GLOBAL_DEFAULT;
#elif defined(US_USE_OPENSSL)
const uint32 FLAGS = CURL_GLOBAL_SSL;
#else
const uint32 FLAGS = CURL_GLOBAL_NOTHING;
#endif
uint32 multiHandle = 1;
{
InSequence seq; // Validate that curl calls respect the sequence
EXPECT_CALL(*curlImpl, globalInitMem(FLAGS, _, _, _, _, _)).WillOnce(Return(CURLE_OK));
EXPECT_CALL(*curlImpl, multiInit()).WillOnce(Return(&multiHandle));
EXPECT_CALL(*curlImpl, multiCleanup(&multiHandle)).WillOnce(Return(CURLM_OK));
EXPECT_CALL(*curlImpl, globalCleanup()).WillOnce(Return());
}
EXPECT_TRUE(curlEngine.initialize(HttpEngineConfig()));
}
/// @test Validates when curl_global_init_mem fails...
TEST_F(HTTP_ENGINE_CURL_MOCK, LOCAL_CurlEngineGlobalInitError)
{
using namespace testing;
MockCurlImpl* curlImpl = US_NEW(MockCurlImpl);
HttpEngineCurl curlEngine(curlImpl);
EXPECT_CALL(*curlImpl, globalInitMem(_, _, _, _, _, _)).WillOnce(Return(CURLE_FAILED_INIT));
#if defined(US_LOG_ENABLED)
EXPECT_CALL(*curlImpl, easyStrError(CURLE_FAILED_INIT)).WillOnce(Return("Failed init"));
#endif
EXPECT_CALL(*curlImpl, multiInit()).Times(0);
EXPECT_CALL(*curlImpl, multiCleanup(_)).Times(0);
EXPECT_CALL(*curlImpl, globalCleanup()).Times(0);
EXPECT_FALSE(curlEngine.initialize(HttpEngineConfig()));
}
/// @test Validates when curl_multi_init fails...
TEST_F(HTTP_ENGINE_CURL_MOCK, LOCAL_CurlEngineMultiInitError)
{
using namespace testing;
MockCurlImpl* curlImpl = US_NEW(MockCurlImpl);
HttpEngineCurl curlEngine(curlImpl);
EXPECT_CALL(*curlImpl, globalInitMem(_, _, _, _, _, _)).WillOnce(Return(CURLE_OK));
EXPECT_CALL(*curlImpl, multiInit()).WillOnce(ReturnNull());
EXPECT_CALL(*curlImpl, multiCleanup(_)).Times(0);
EXPECT_CALL(*curlImpl, globalCleanup()).WillOnce(Return());
EXPECT_FALSE(curlEngine.initialize(HttpEngineConfig()));
}
/// @test Validates request creation
TEST_F(HTTP_ENGINE_CURL_MOCK, LOCAL_CurlEngineCreateRequestSuccess)
{
using namespace testing;
NiceMock<MockCurlImpl>* curlImpl = US_NEW(NiceMock<MockCurlImpl>);
HttpEngineCurl curlEngine(curlImpl);
uint32 multiHandle = 1;
uint32 easyHandle = 2;
MockCurlImpl_BF::setInitExpectations(curlImpl, &multiHandle);
// Create sequence
{
InSequence seq;
EXPECT_CALL(*curlImpl, easyInit()).WillOnce(Return(&easyHandle));
EXPECT_CALL(*curlImpl, multiAddHandle(&multiHandle, &easyHandle)).WillOnce(Return(CURLM_OK));
}
// Shutdown sequence
{
InSequence seq;
EXPECT_CALL(*curlImpl, multiRemoveHandle(&multiHandle, &easyHandle)).WillOnce(Return(CURLM_OK));
EXPECT_CALL(*curlImpl, easyCleanup(&easyHandle)).WillOnce(Return());
}
EXPECT_TRUE(curlEngine.initialize(HttpEngineConfig()));
// Request is not delete automatically by the curl engine implementation but by HttpEngine components
HttpRequestContext requestContext = MockHttpRequest_BF::createDummyContext();
HttpRequestInternal* request(curlEngine.createRequest(requestContext));
EXPECT_TRUE(request != NULL);
// Check that the request state doesn't change
EXPECT_EQ(HttpRequestState::Init, requestContext.getRequestState());
EXPECT_EQ(1, curlEngine.getNumberOfRequests());
curlEngine.releaseRequest(request);
}
/// @test Validates that when the request is created the options are the same as the engine config.
TEST_F(HTTP_ENGINE_CURL_MOCK, LOCAL_CurlEngineCreateRequestValidateConfigOptions)
{
using namespace testing;
NiceMock<MockCurlImpl>* curlImpl = US_NEW(NiceMock<MockCurlImpl>);
HttpEngineCurl curlEngine(curlImpl);
const uint32 transferBufferSize = 1024;
const String userAgent = "Curl user agent";
HttpEngineConfig engineConfig;
engineConfig.setTransferBufferSize(transferBufferSize);
engineConfig.setUserAgent(userAgent);
uint32 multiHandle = 1;
uint32 easyHandle = 2;
{
MockCurlImpl_BF::setInitExpectations(curlImpl, &multiHandle);
// Create
EXPECT_CALL(*curlImpl, easyInit()).WillOnce(Return(&easyHandle));
EXPECT_CALL(*curlImpl, multiAddHandle(&multiHandle, &easyHandle)).WillOnce(Return(CURLM_OK));
// Options
// Google mock seems to discard the 1st overloaded expectations... Set one that should never happen as a work around.
EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, CURLOPT_MAIL_FROM, An<const char*>())).Times(0);
EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, Not(CURLOPT_USERAGENT), An<const char*>())).Times(AnyNumber());
EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, _, An<long>())).Times(AnyNumber());
EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, CURLOPT_USERAGENT, Matcher<const char*>(StrEq(userAgent.getUtf8())))).Times(1);
EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, CURLOPT_BUFFERSIZE, Matcher<long>(static_cast<long>(transferBufferSize)))).Times(1);
// Shutdown
EXPECT_CALL(*curlImpl, multiRemoveHandle(&multiHandle, &easyHandle)).WillOnce(Return(CURLM_OK));
EXPECT_CALL(*curlImpl, easyCleanup(&easyHandle)).WillOnce(Return());
}
EXPECT_TRUE(curlEngine.initialize(engineConfig));
// Request is not delete automatically by the curl engine implementation but by HttpEngine components
HttpRequestContext requestContext = MockHttpRequest_BF::createDummyContext();
HttpRequestInternal* request(curlEngine.createRequest(requestContext));
EXPECT_TRUE(request != NULL);
curlEngine.releaseRequest(request);
}
/// @test Validates that proxy is set when auto proxy is false (require a host as well)
TEST_F(HTTP_ENGINE_CURL_MOCK, LOCAL_CurlEngineCreateRequestSetProxy)
{
using namespace testing;
NiceMock<MockCurlImpl>* curlImpl = US_NEW(NiceMock<MockCurlImpl>);
HttpEngineCurl curlEngine(curlImpl);
const String proxyHost = "http://myproxy.com";
HttpProxyConfig proxyConfig;
proxyConfig.m_host = proxyHost;
proxyConfig.m_proxyType = HttpProxyConfig::Type::Enabled;
HttpEngineConfig engineConfig;
engineConfig.setProxy(proxyConfig);
uint32 multiHandle = 1;
uint32 easyHandle = 2;
{
MockCurlImpl_BF::setInitExpectations(curlImpl, &multiHandle);
// Create
EXPECT_CALL(*curlImpl, easyInit()).WillOnce(Return(&easyHandle));
EXPECT_CALL(*curlImpl, multiAddHandle(&multiHandle, &easyHandle)).WillOnce(Return(CURLM_OK));
// Options
// Google mock seems to discard the 1st overloaded expectations... Set one that should never happen as a work around.
EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, CURLOPT_MAIL_FROM, An<const char*>())).Times(0);
EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, Not(CURLOPT_PROXY), An<const char*>())).Times(AnyNumber());
EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, CURLOPT_PROXY, An<const char*>())).Times(1);
// Shutdown
EXPECT_CALL(*curlImpl, multiRemoveHandle(&multiHandle, &easyHandle)).WillOnce(Return(CURLM_OK));
EXPECT_CALL(*curlImpl, easyCleanup(&easyHandle)).WillOnce(Return());
}
EXPECT_TRUE(curlEngine.initialize(engineConfig));
// Request is not delete automatically by the curl engine implementation but by HttpEngine components
HttpRequestContext requestContext = MockHttpRequest_BF::createDummyContext();
HttpRequestInternal* request(curlEngine.createRequest(requestContext));
EXPECT_TRUE(request != NULL);
curlEngine.releaseRequest(request);
}
/// @test Validates that proxy is not set when auto proxy is true
TEST_F(HTTP_ENGINE_CURL_MOCK, LOCAL_CurlEngineCreateRequestAutoProxy)
{
using namespace testing;
NiceMock<MockCurlImpl>* curlImpl = US_NEW(NiceMock<MockCurlImpl>);
HttpEngineCurl curlEngine(curlImpl);
HttpEngineConfig engineConfig;
engineConfig.setProxy(HttpProxyConfig(String(), 0u, String(), String(), HttpProxyConfig::Type::AutoProxy));
uint32 multiHandle = 1;
uint32 easyHandle = 2;
{
MockCurlImpl_BF::setInitExpectations(curlImpl, &multiHandle);
// Create
EXPECT_CALL(*curlImpl, easyInit()).WillOnce(Return(&easyHandle));
EXPECT_CALL(*curlImpl, multiAddHandle(&multiHandle, &easyHandle)).WillOnce(Return(CURLM_OK));
// Options
// Google mock seems to discard the 1st overloaded expectations... Set one that should never happen as a work around.
EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, CURLOPT_MAIL_FROM, An<const char*>())).Times(0);
EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, Not(CURLOPT_PROXY), An<const char*>())).Times(AnyNumber());
// The following check is required so that disabling proxy settings does not make the test succeed also!
EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, CURLOPT_NOPROXY, An<const char*>())).Times(0);
EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, CURLOPT_PROXY, An<const char*>())).Times(0);
// Shutdown
EXPECT_CALL(*curlImpl, multiRemoveHandle(&multiHandle, &easyHandle)).WillOnce(Return(CURLM_OK));
EXPECT_CALL(*curlImpl, easyCleanup(&easyHandle)).WillOnce(Return());
}
EXPECT_TRUE(curlEngine.initialize(engineConfig));
// Request is not delete automatically by the curl engine implementation but by HttpEngine components
HttpRequestContext requestContext = MockHttpRequest_BF::createDummyContext();
HttpRequestInternal* request(curlEngine.createRequest(requestContext));
EXPECT_TRUE(request != NULL);
curlEngine.releaseRequest(request);
}
/// @test Ensures that the engine can complete multiple request in the same process call.
/// This validates that the curl engine properly reads the curl messages.
TEST_F(HTTP_ENGINE_CURL_MOCK, LOCAL_CurlEngineProcessCompleteMultiples)
{
using namespace testing;
NiceMock<MockCurlImpl>* curlImpl = US_NEW(NiceMock<MockCurlImpl>);
HttpEngineCurl curlEngine(curlImpl);
const uint32 REQUESTS_COUNT = 3; // Do not change or the expectations will fail
Vector<uint32> handles;
Vector<CURLMsg> curlMsgs;
uint32 multiHandle = 1;
{
MockCurlImpl_BF::setInitExpectations(curlImpl, &multiHandle);
// Expectations for request creation and completion
handles.reserve(REQUESTS_COUNT);
for (int i = 0; i < REQUESTS_COUNT; ++i)
{
handles.push_back(i);
CURLMsg msg = { CURLMSG_DONE, &handles[i], { NULL } };
msg.data.result = CURLE_OK;
curlMsgs.push_back(msg);
InSequence seq;
EXPECT_CALL(*curlImpl, easyInit())
.WillOnce(Return(&handles[i]))
;//.RetiresOnSaturation();
EXPECT_CALL(*curlImpl, multiAddHandle(&multiHandle, &handles[i]))
.WillOnce(Return(CURLM_OK))
;//.RetiresOnSaturation();
EXPECT_CALL(*curlImpl, multiRemoveHandle(&multiHandle, &handles[i]))
.WillOnce(Return(CURLM_OK))
;//.RetiresOnSaturation();
EXPECT_CALL(*curlImpl, easyCleanup(&handles[i]))
.WillOnce(Return())
;//.RetiresOnSaturation();
}
EXPECT_CALL(*curlImpl, multiPerform(&multiHandle, _))
.WillOnce(DoAll(SetArgPointee<1>(0), Return(CURLM_OK)));
// multiInfoRead is called once for each request and returns that the request is complete
EXPECT_CALL(*curlImpl, multiInfoRead(&multiHandle, _))
.WillOnce(DoAll(SetArgPointee<1>(2), Return(&curlMsgs[2])))
.WillOnce(DoAll(SetArgPointee<1>(1), Return(&curlMsgs[1])))
.WillOnce(DoAll(SetArgPointee<1>(0), Return(&curlMsgs[0])));
}
EXPECT_TRUE(curlEngine.initialize(HttpEngineConfig()));
Vector<HttpRequestInternal*> requests;
requests.reserve(REQUESTS_COUNT);
for (int i = 0; i < REQUESTS_COUNT; ++i)
{
const HttpRequestContext requestContext = MockHttpRequest_BF::createDummyContext(i);
requests.push_back(curlEngine.createRequest(requestContext));
}
EXPECT_EQ(static_cast<size_t>(REQUESTS_COUNT), curlEngine.getNumberOfRequests());
curlEngine.perform(); // After this call all the requests shall be completed
for (int i = 0; i < REQUESTS_COUNT; ++i)
{
EXPECT_TRUE(requests[i]->hasSucceeded());
curlEngine.releaseRequest(requests[i]);
}
EXPECT_EQ(0u, curlEngine.getNumberOfRequests());
}
TEST_F(HTTP_ENGINE_CURL_MOCK, LOCAL_CurlEngineCompleteRequestSuccessPartial)
{
using namespace testing;
NiceMock<MockCurlImpl>* curlImpl = US_NEW(NiceMock<MockCurlImpl>);
HttpEngineCurl curlEngine(curlImpl);
uint32 multiHandle = 1;
uint32 easyHandle = 2;
CURLMsg msg = {CURLMSG_DONE, &easyHandle, { NULL }};
msg.data.result = CURLE_PARTIAL_FILE;
//msg.easy_handle = &easyHandle;
{
MockCurlImpl_BF::setInitExpectations(curlImpl, &multiHandle);
// Create sequence
EXPECT_CALL(*curlImpl, easyInit()).WillOnce(Return(&easyHandle));
EXPECT_CALL(*curlImpl, multiAddHandle(&multiHandle, &easyHandle)).WillOnce(Return(CURLM_OK));
EXPECT_CALL(*curlImpl, multiPerform(&multiHandle, _)).WillOnce(DoAll(SetArgPointee<1>(0), Return(CURLM_OK)));
// Multi info read is called once for each request and return that the request is complete
EXPECT_CALL(*curlImpl, multiInfoRead(&multiHandle, _)).WillOnce(DoAll(SetArgPointee<1>(0), Return(&msg)));
EXPECT_CALL(*curlImpl, multiRemoveHandle(&multiHandle, &easyHandle)).WillOnce(Return(CURLM_OK));
EXPECT_CALL(*curlImpl, easyCleanup(&easyHandle)).WillOnce(Return());
}
EXPECT_TRUE(curlEngine.initialize(HttpEngineConfig()));
// Request is not delete automatically by the curl engine implementation but by HttpEngine components
const HttpRequestContext requestContext = MockHttpRequest_BF::createDummyContext();
HttpRequestInternal* request = curlEngine.createRequest(requestContext);
curlEngine.perform();
EXPECT_TRUE(request->hasSucceeded());
curlEngine.releaseRequest(request);
EXPECT_EQ(0u, curlEngine.getNumberOfRequests());
}
TEST_F(HTTP_ENGINE_CURL_MOCK, LOCAL_CurlEngineCompleteRequestError)
{
using namespace testing;
NiceMock<MockCurlImpl>* curlImpl = US_NEW(NiceMock<MockCurlImpl>);
HttpEngineCurl curlEngine(curlImpl);
uint32 multiHandle = 1;
uint32 easyHandle = 2;
CURLMsg msg = {CURLMSG_DONE, &easyHandle, { NULL }};
msg.data.result = CURLE_COULDNT_CONNECT;
//msg.easy_handle = &easyHandle;
{
MockCurlImpl_BF::setInitExpectations(*curlImpl, &multiHandle);
// Create sequence
EXPECT_CALL(*curlImpl, easyInit()).WillOnce(Return(&easyHandle));
EXPECT_CALL(*curlImpl, multiAddHandle(&multiHandle, &easyHandle)).WillOnce(Return(CURLM_OK));
EXPECT_CALL(*curlImpl, multiPerform(&multiHandle, _)).WillOnce(DoAll(SetArgPointee<1>(0), Return(CURLM_OK)));
// Multi info read is called once for each request and return that the request is complete
EXPECT_CALL(*curlImpl, multiInfoRead(&multiHandle, _)).WillOnce(DoAll(SetArgPointee<1>(0), Return(&msg)));
#if defined(US_LOG_ENABLED)
const char* errorMsg = "Failed request";
EXPECT_CALL(*curlImpl, easyStrError(CURLE_COULDNT_CONNECT)).WillOnce(Return(errorMsg));
#endif
EXPECT_CALL(*curlImpl, multiRemoveHandle(&multiHandle, &easyHandle)).WillOnce(Return(CURLM_OK));
EXPECT_CALL(*curlImpl, easyCleanup(&easyHandle)).WillOnce(Return());
}
EXPECT_TRUE(curlEngine.initialize(HttpEngineConfig()));
// Request is not delete automatically by the curl engine implementation but by HttpEngine components
const HttpRequestContext requestContext = MockHttpRequest_BF::createDummyContext();
HttpRequestInternal* request = curlEngine.createRequest(requestContext);
curlEngine.perform();
EXPECT_FALSE(request->hasSucceeded());
curlEngine.releaseRequest(request);
EXPECT_EQ(0u, curlEngine.getNumberOfRequests());
}
TEST_F(HTTP_ENGINE_CURL_MOCK, LOCAL_CurlEngineCancelRequest)
{
using namespace testing;
NiceMock<MockCurlImpl>* curlImpl = US_NEW(NiceMock<MockCurlImpl>);
HttpEngineCurl curlEngine(curlImpl);
uint32 multiHandle = 1;
uint32 easyHandle = 2;
MockCurlImpl_BF::setInitExpectations(curlImpl, &multiHandle);
{
InSequence seq;
EXPECT_CALL(*curlImpl, easyInit()).WillOnce(Return(&easyHandle));
EXPECT_CALL(*curlImpl, multiAddHandle(&multiHandle, &easyHandle)).WillOnce(Return(CURLM_OK));
EXPECT_CALL(*curlImpl, multiRemoveHandle(&multiHandle, &easyHandle)).WillOnce(Return(CURLM_OK));
EXPECT_CALL(*curlImpl, easyCleanup(&easyHandle)).WillOnce(Return());
}
EXPECT_TRUE(curlEngine.initialize(HttpEngineConfig()));
// Request is not delete automatically by the curl engine implementation but by HttpEngine components
const HttpRequestContext requestContext = MockHttpRequest_BF::createDummyContext();
HttpRequestInternal* request(curlEngine.createRequest(requestContext));
request->cancel();
EXPECT_TRUE(request->isComplete());
EXPECT_TRUE(request->isCanceled());
curlEngine.releaseRequest(request);
EXPECT_EQ(0u, curlEngine.getNumberOfRequests());
}
/// @test Validates the integration between of the HttpEngine with the lib Curl implementation
TEST_F(HTTP_ENGINE_CURL_MOCK, LOCAL_CurlEngineIntegration)
{
using namespace testing;
NiceMock<MockCurlImpl>* curlImpl = US_NEW(NiceMock<MockCurlImpl>);
HttpEngineCurl* curlEngine = US_NEW(HttpEngineCurl, curlImpl);
HttpEngine httpEngine(curlEngine);
uint32 multiHandle = 1;
uint32 easyHandle = 2;
CURLMsg msg = {CURLMSG_DONE, &easyHandle, { NULL }};
msg.data.result = CURLE_OK;
MockCurlImpl_BF::setInitExpectations(curlImpl, &multiHandle);
{
// Create sequence
EXPECT_CALL(*curlImpl, easyInit()).WillOnce(Return(&easyHandle));
EXPECT_CALL(*curlImpl, multiAddHandle(&multiHandle, &easyHandle))
.WillOnce(Return(CURLM_OK));
EXPECT_CALL(*curlImpl, multiPerform(&multiHandle, _))
.WillRepeatedly(DoAll(SetArgPointee<1>(0), Return(CURLM_OK)));
EXPECT_CALL(*curlImpl, multiInfoRead(&multiHandle, _))
.WillOnce(ReturnNull())
.WillOnce(DoAll(SetArgPointee<1>(0), Return(&msg)))
.WillRepeatedly(ReturnNull());
EXPECT_CALL(*curlImpl, multiRemoveHandle(&multiHandle, &easyHandle))
.WillOnce(Return(CURLM_OK));
EXPECT_CALL(*curlImpl, easyCleanup(&easyHandle))
.WillOnce(Return());
}
EXPECT_TRUE(httpEngine.initialize(HttpConfig()));
const HttpRequestContext requestContext = httpEngine.createRequest(HttpGet(StringUrl("dummy")), HttpRetryHandler(HttpRetryConfig(0u)));
US_EXPECT_PROPERTY_WAIT(requestContext, HttpRequestContext::isComplete, true, 1000);
EXPECT_TRUE(requestContext.hasSucceeded());
EXPECT_EQ(0u, httpEngine.getNumberOfRequests());
}
};
#endif // US_DLL