1 // Copyright 2014 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <brillo/http/http_transport_curl.h>
6
7 #include <base/at_exit.h>
8 #include <base/message_loop/message_loop.h>
9 #include <base/run_loop.h>
10 #include <brillo/bind_lambda.h>
11 #include <brillo/http/http_connection_curl.h>
12 #include <brillo/http/http_request.h>
13 #include <brillo/http/mock_curl_api.h>
14 #include <gmock/gmock.h>
15 #include <gtest/gtest.h>
16
17 using testing::DoAll;
18 using testing::Invoke;
19 using testing::Return;
20 using testing::SaveArg;
21 using testing::SetArgPointee;
22 using testing::WithoutArgs;
23 using testing::_;
24
25 namespace brillo {
26 namespace http {
27 namespace curl {
28
29 class HttpCurlTransportTest : public testing::Test {
30 public:
SetUp()31 void SetUp() override {
32 curl_api_ = std::make_shared<MockCurlInterface>();
33 transport_ = std::make_shared<Transport>(curl_api_);
34 handle_ = reinterpret_cast<CURL*>(100); // Mock handle value.
35 EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_));
36 EXPECT_CALL(*curl_api_, EasySetOptStr(handle_, CURLOPT_CAPATH, _))
37 .WillOnce(Return(CURLE_OK));
38 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYPEER, 1))
39 .WillOnce(Return(CURLE_OK));
40 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYHOST, 2))
41 .WillOnce(Return(CURLE_OK));
42 EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_PRIVATE, _))
43 .WillRepeatedly(Return(CURLE_OK));
44 }
45
TearDown()46 void TearDown() override {
47 transport_.reset();
48 curl_api_.reset();
49 }
50
51 protected:
52 std::shared_ptr<MockCurlInterface> curl_api_;
53 std::shared_ptr<Transport> transport_;
54 CURL* handle_{nullptr};
55 };
56
TEST_F(HttpCurlTransportTest,RequestGet)57 TEST_F(HttpCurlTransportTest, RequestGet) {
58 EXPECT_CALL(*curl_api_,
59 EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
60 .WillOnce(Return(CURLE_OK));
61 EXPECT_CALL(*curl_api_,
62 EasySetOptStr(handle_, CURLOPT_USERAGENT, "User Agent"))
63 .WillOnce(Return(CURLE_OK));
64 EXPECT_CALL(*curl_api_,
65 EasySetOptStr(handle_, CURLOPT_REFERER, "http://foo.bar/baz"))
66 .WillOnce(Return(CURLE_OK));
67 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
68 .WillOnce(Return(CURLE_OK));
69 auto connection = transport_->CreateConnection("http://foo.bar/get",
70 request_type::kGet,
71 {},
72 "User Agent",
73 "http://foo.bar/baz",
74 nullptr);
75 EXPECT_NE(nullptr, connection.get());
76
77 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
78 connection.reset();
79 }
80
TEST_F(HttpCurlTransportTest,RequestHead)81 TEST_F(HttpCurlTransportTest, RequestHead) {
82 EXPECT_CALL(*curl_api_,
83 EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/head"))
84 .WillOnce(Return(CURLE_OK));
85 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_NOBODY, 1))
86 .WillOnce(Return(CURLE_OK));
87 auto connection = transport_->CreateConnection(
88 "http://foo.bar/head", request_type::kHead, {}, "", "", nullptr);
89 EXPECT_NE(nullptr, connection.get());
90
91 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
92 connection.reset();
93 }
94
TEST_F(HttpCurlTransportTest,RequestPut)95 TEST_F(HttpCurlTransportTest, RequestPut) {
96 EXPECT_CALL(*curl_api_,
97 EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/put"))
98 .WillOnce(Return(CURLE_OK));
99 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_UPLOAD, 1))
100 .WillOnce(Return(CURLE_OK));
101 auto connection = transport_->CreateConnection(
102 "http://foo.bar/put", request_type::kPut, {}, "", "", nullptr);
103 EXPECT_NE(nullptr, connection.get());
104
105 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
106 connection.reset();
107 }
108
TEST_F(HttpCurlTransportTest,RequestPost)109 TEST_F(HttpCurlTransportTest, RequestPost) {
110 EXPECT_CALL(*curl_api_,
111 EasySetOptStr(handle_, CURLOPT_URL, "http://www.foo.bar/post"))
112 .WillOnce(Return(CURLE_OK));
113 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_POST, 1))
114 .WillOnce(Return(CURLE_OK));
115 EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_POSTFIELDS, nullptr))
116 .WillOnce(Return(CURLE_OK));
117 auto connection = transport_->CreateConnection(
118 "http://www.foo.bar/post", request_type::kPost, {}, "", "", nullptr);
119 EXPECT_NE(nullptr, connection.get());
120
121 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
122 connection.reset();
123 }
124
TEST_F(HttpCurlTransportTest,RequestPatch)125 TEST_F(HttpCurlTransportTest, RequestPatch) {
126 EXPECT_CALL(*curl_api_,
127 EasySetOptStr(handle_, CURLOPT_URL, "http://www.foo.bar/patch"))
128 .WillOnce(Return(CURLE_OK));
129 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_POST, 1))
130 .WillOnce(Return(CURLE_OK));
131 EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_POSTFIELDS, nullptr))
132 .WillOnce(Return(CURLE_OK));
133 EXPECT_CALL(
134 *curl_api_,
135 EasySetOptStr(handle_, CURLOPT_CUSTOMREQUEST, request_type::kPatch))
136 .WillOnce(Return(CURLE_OK));
137 auto connection = transport_->CreateConnection(
138 "http://www.foo.bar/patch", request_type::kPatch, {}, "", "", nullptr);
139 EXPECT_NE(nullptr, connection.get());
140
141 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
142 connection.reset();
143 }
144
TEST_F(HttpCurlTransportTest,CurlFailure)145 TEST_F(HttpCurlTransportTest, CurlFailure) {
146 EXPECT_CALL(*curl_api_,
147 EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
148 .WillOnce(Return(CURLE_OK));
149 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
150 .WillOnce(Return(CURLE_OUT_OF_MEMORY));
151 EXPECT_CALL(*curl_api_, EasyStrError(CURLE_OUT_OF_MEMORY))
152 .WillOnce(Return("Out of Memory"));
153 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
154 ErrorPtr error;
155 auto connection = transport_->CreateConnection(
156 "http://foo.bar/get", request_type::kGet, {}, "", "", &error);
157
158 EXPECT_EQ(nullptr, connection.get());
159 EXPECT_EQ("curl_easy_error", error->GetDomain());
160 EXPECT_EQ(std::to_string(CURLE_OUT_OF_MEMORY), error->GetCode());
161 EXPECT_EQ("Out of Memory", error->GetMessage());
162 }
163
164 class HttpCurlTransportAsyncTest : public testing::Test {
165 public:
SetUp()166 void SetUp() override {
167 curl_api_ = std::make_shared<MockCurlInterface>();
168 transport_ = std::make_shared<Transport>(curl_api_);
169 EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_));
170 EXPECT_CALL(*curl_api_, EasySetOptStr(handle_, CURLOPT_CAPATH, _))
171 .WillOnce(Return(CURLE_OK));
172 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYPEER, 1))
173 .WillOnce(Return(CURLE_OK));
174 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYHOST, 2))
175 .WillOnce(Return(CURLE_OK));
176 EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_PRIVATE, _))
177 .WillOnce(Return(CURLE_OK));
178 }
179
180 protected:
181 std::shared_ptr<MockCurlInterface> curl_api_;
182 std::shared_ptr<Transport> transport_;
183 CURL* handle_{reinterpret_cast<CURL*>(123)}; // Mock handle value.
184 CURLM* multi_handle_{reinterpret_cast<CURLM*>(456)}; // Mock handle value.
185 curl_socket_t dummy_socket_{789};
186 };
187
TEST_F(HttpCurlTransportAsyncTest,StartAsyncTransfer)188 TEST_F(HttpCurlTransportAsyncTest, StartAsyncTransfer) {
189 // This test is a bit tricky because it deals with asynchronous I/O which
190 // relies on a message loop to run all the async tasks.
191 // For this, create a temporary I/O message loop and run it ourselves for the
192 // duration of the test.
193 base::MessageLoopForIO message_loop;
194 base::RunLoop run_loop;
195
196 // Initial expectations for creating a CURL connection.
197 EXPECT_CALL(*curl_api_,
198 EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
199 .WillOnce(Return(CURLE_OK));
200 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
201 .WillOnce(Return(CURLE_OK));
202 auto connection = transport_->CreateConnection(
203 "http://foo.bar/get", request_type::kGet, {}, "", "", nullptr);
204 ASSERT_NE(nullptr, connection.get());
205
206 // Success/error callback needed to report the result of an async operation.
207 int success_call_count = 0;
208 auto success_callback = [](int* success_call_count,
209 base::RunLoop* run_loop,
210 RequestID /* request_id */,
211 std::unique_ptr<http::Response> /* resp */) {
212 base::MessageLoop::current()->task_runner()->PostTask(
213 FROM_HERE, run_loop->QuitClosure());
214 (*success_call_count)++;
215 };
216
217 auto error_callback = [](RequestID /* request_id */,
218 const Error* /* error */) {
219 FAIL() << "This callback shouldn't have been called";
220 };
221
222 EXPECT_CALL(*curl_api_, MultiInit()).WillOnce(Return(multi_handle_));
223 EXPECT_CALL(*curl_api_, EasyGetInfoInt(handle_, CURLINFO_RESPONSE_CODE, _))
224 .WillRepeatedly(DoAll(SetArgPointee<2>(200), Return(CURLE_OK)));
225
226 curl_socket_callback socket_callback = nullptr;
227 EXPECT_CALL(*curl_api_,
228 MultiSetSocketCallback(multi_handle_, _, transport_.get()))
229 .WillOnce(DoAll(SaveArg<1>(&socket_callback), Return(CURLM_OK)));
230
231 curl_multi_timer_callback timer_callback = nullptr;
232 EXPECT_CALL(*curl_api_,
233 MultiSetTimerCallback(multi_handle_, _, transport_.get()))
234 .WillOnce(DoAll(SaveArg<1>(&timer_callback), Return(CURLM_OK)));
235
236 EXPECT_CALL(*curl_api_, MultiAddHandle(multi_handle_, handle_))
237 .WillOnce(Return(CURLM_OK));
238
239 EXPECT_EQ(1,
240 transport_->StartAsyncTransfer(
241 connection.get(),
242 base::Bind(success_callback,
243 base::Unretained(&success_call_count),
244 base::Unretained(&run_loop)),
245 base::Bind(error_callback)));
246 EXPECT_EQ(0, success_call_count);
247
248 timer_callback(multi_handle_, 1, transport_.get());
249
250 auto do_socket_action = [&socket_callback, this] {
251 EXPECT_CALL(*curl_api_, MultiAssign(multi_handle_, dummy_socket_, _))
252 .Times(2).WillRepeatedly(Return(CURLM_OK));
253 EXPECT_EQ(0, socket_callback(handle_, dummy_socket_, CURL_POLL_REMOVE,
254 transport_.get(), nullptr));
255 };
256
257 EXPECT_CALL(*curl_api_,
258 MultiSocketAction(multi_handle_, CURL_SOCKET_TIMEOUT, 0, _))
259 .WillOnce(DoAll(SetArgPointee<3>(1),
260 WithoutArgs(Invoke(do_socket_action)),
261 Return(CURLM_OK)))
262 .WillRepeatedly(DoAll(SetArgPointee<3>(0), Return(CURLM_OK)));
263
264 CURLMsg msg = {};
265 msg.msg = CURLMSG_DONE;
266 msg.easy_handle = handle_;
267 msg.data.result = CURLE_OK;
268
269 EXPECT_CALL(*curl_api_, MultiInfoRead(multi_handle_, _))
270 .WillOnce(DoAll(SetArgPointee<1>(0), Return(&msg)))
271 .WillRepeatedly(DoAll(SetArgPointee<1>(0), Return(nullptr)));
272 EXPECT_CALL(*curl_api_, EasyGetInfoPtr(handle_, CURLINFO_PRIVATE, _))
273 .WillRepeatedly(DoAll(SetArgPointee<2>(connection.get()),
274 Return(CURLE_OK)));
275
276 EXPECT_CALL(*curl_api_, MultiRemoveHandle(multi_handle_, handle_))
277 .WillOnce(Return(CURLM_OK));
278
279 // Just in case something goes wrong and |success_callback| isn't called,
280 // post a time-out quit closure to abort the message loop after 1 second.
281 message_loop.task_runner()->PostDelayedTask(
282 FROM_HERE, run_loop.QuitClosure(), base::TimeDelta::FromSeconds(1));
283 run_loop.Run();
284 EXPECT_EQ(1, success_call_count);
285
286 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
287 connection.reset();
288
289 EXPECT_CALL(*curl_api_, MultiCleanup(multi_handle_))
290 .WillOnce(Return(CURLM_OK));
291 transport_.reset();
292 }
293
TEST_F(HttpCurlTransportTest,RequestGetTimeout)294 TEST_F(HttpCurlTransportTest, RequestGetTimeout) {
295 transport_->SetDefaultTimeout(base::TimeDelta::FromMilliseconds(2000));
296 EXPECT_CALL(*curl_api_,
297 EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
298 .WillOnce(Return(CURLE_OK));
299 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_TIMEOUT_MS, 2000))
300 .WillOnce(Return(CURLE_OK));
301 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
302 .WillOnce(Return(CURLE_OK));
303 auto connection = transport_->CreateConnection(
304 "http://foo.bar/get", request_type::kGet, {}, "", "", nullptr);
305 EXPECT_NE(nullptr, connection.get());
306
307 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
308 connection.reset();
309 }
310
311 } // namespace curl
312 } // namespace http
313 } // namespace brillo
314