//////////////////////////////////////////////////////////////////////////////// // (c) Copyright, Ubisoft Entertainment Inc. || Created by Harbour // //////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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* curlImpl = US_NEW(NiceMock); 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* curlImpl = US_NEW(NiceMock); 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())).Times(0); EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, Not(CURLOPT_USERAGENT), An())).Times(AnyNumber()); EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, _, An())).Times(AnyNumber()); EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, CURLOPT_USERAGENT, Matcher(StrEq(userAgent.getUtf8())))).Times(1); EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, CURLOPT_BUFFERSIZE, Matcher(static_cast(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* curlImpl = US_NEW(NiceMock); 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())).Times(0); EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, Not(CURLOPT_PROXY), An())).Times(AnyNumber()); EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, CURLOPT_PROXY, An())).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* curlImpl = US_NEW(NiceMock); 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())).Times(0); EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, Not(CURLOPT_PROXY), An())).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())).Times(0); EXPECT_CALL(*curlImpl, easySetOpt(&easyHandle, CURLOPT_PROXY, An())).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* curlImpl = US_NEW(NiceMock); HttpEngineCurl curlEngine(curlImpl); const uint32 REQUESTS_COUNT = 3; // Do not change or the expectations will fail Vector handles; Vector 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 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(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* curlImpl = US_NEW(NiceMock); 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* curlImpl = US_NEW(NiceMock); 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* curlImpl = US_NEW(NiceMock); 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* curlImpl = US_NEW(NiceMock); 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