libchromeos: Add some missing unit tests for HTTP utilities.
Added missing unit tests for HTTP utility functions and classes.
In order to test the CURL-specific transport and connection
classes, added an abstraction on top of the raw CURL APIs so
they can be mocked out in the tests.
Also changed the header list implementation from std::map to
std::multimap since it is allowed to have more than one header
with the same name in http requests and responses.
Also silenced one bogus GMOCK warning in D-Bus tests related to
unexpected mocked function call for SendSignal().
BUG=None
TEST=FEATURES=test emerge-link libchromeos
Change-Id: I200672d890c06fdde668dc7974c1ff5242cc5233
Reviewed-on: https://chromium-review.googlesource.com/239617
Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/chromeos/dbus/exported_property_set_unittest.cc b/chromeos/dbus/exported_property_set_unittest.cc
index 2338aee..5e3965d 100644
--- a/chromeos/dbus/exported_property_set_unittest.cc
+++ b/chromeos/dbus/exported_property_set_unittest.cc
@@ -541,6 +541,7 @@
p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite);
p_->string_prop_.SetValidator(
property_validator.validate_property_callback());
+ EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1);
p_->string_prop_.SetValue(kTestString);
// No need to validate the value if it is the same as the current one.
diff --git a/chromeos/http/curl_api.cc b/chromeos/http/curl_api.cc
new file mode 100644
index 0000000..e1327dd
--- /dev/null
+++ b/chromeos/http/curl_api.cc
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <chromeos/http/curl_api.h>
+
+#include <base/logging.h>
+
+namespace chromeos {
+namespace http {
+
+namespace {
+
+static_assert(CURLOPTTYPE_LONG == 0 &&
+ CURLOPTTYPE_OBJECTPOINT == 10000 &&
+ CURLOPTTYPE_FUNCTIONPOINT == 20000 &&
+ CURLOPTTYPE_OFF_T == 30000,
+ "CURL option types are expected to be multiples of 10000");
+
+inline bool VerifyOptionType(CURLoption option, int expected_type) {
+ int option_type = (static_cast<int>(option) / 10000) * 10000;
+ return (option_type == expected_type);
+}
+
+} // anonymous namespace
+
+CurlApi::CurlApi() {
+ curl_global_init(CURL_GLOBAL_ALL);
+}
+
+CurlApi::~CurlApi() {
+ curl_global_cleanup();
+}
+
+CURL* CurlApi::EasyInit() {
+ return curl_easy_init();
+}
+
+void CurlApi::EasyCleanup(CURL* curl) {
+ curl_easy_cleanup(curl);
+}
+
+CURLcode CurlApi::EasySetOptInt(CURL* curl, CURLoption option, int value) {
+ CHECK(VerifyOptionType(option, CURLOPTTYPE_LONG))
+ << "Only options that expect a LONG data type must be specified here";
+ // CURL actually uses "long" type, so have to make sure we feed it what it
+ // expects.
+ // NOLINTNEXTLINE(runtime/int)
+ return curl_easy_setopt(curl, option, static_cast<long>(value));
+}
+
+CURLcode CurlApi::EasySetOptStr(CURL* curl,
+ CURLoption option,
+ const std::string& value) {
+ CHECK(VerifyOptionType(option, CURLOPTTYPE_OBJECTPOINT))
+ << "Only options that expect a STRING data type must be specified here";
+ return curl_easy_setopt(curl, option, value.c_str());
+}
+
+CURLcode CurlApi::EasySetOptPtr(CURL* curl, CURLoption option, void* value) {
+ CHECK(VerifyOptionType(option, CURLOPTTYPE_OBJECTPOINT))
+ << "Only options that expect a pointer data type must be specified here";
+ return curl_easy_setopt(curl, option, value);
+}
+
+CURLcode CurlApi::EasySetOptCallback(CURL* curl,
+ CURLoption option,
+ intptr_t address) {
+ CHECK(VerifyOptionType(option, CURLOPTTYPE_FUNCTIONPOINT))
+ << "Only options that expect a function pointers must be specified here";
+ return curl_easy_setopt(curl, option, address);
+}
+
+CURLcode CurlApi::EasySetOptOffT(CURL* curl,
+ CURLoption option,
+ curl_off_t value) {
+ CHECK(VerifyOptionType(option, CURLOPTTYPE_OFF_T))
+ << "Only options that expect a large data size must be specified here";
+ return curl_easy_setopt(curl, option, value);
+}
+
+CURLcode CurlApi::EasyPerform(CURL* curl) {
+ return curl_easy_perform(curl);
+}
+
+CURLcode CurlApi::EasyGetInfoInt(CURL* curl, CURLINFO info, int* value) const {
+ CHECK_EQ(CURLINFO_LONG, info & CURLINFO_TYPEMASK) << "Wrong option type";
+ long data = 0; // NOLINT(runtime/int) - curl expects a long here.
+ CURLcode code = curl_easy_getinfo(curl, info, &data);
+ if (code == CURLE_OK)
+ *value = static_cast<int>(data);
+ return code;
+}
+
+CURLcode CurlApi::EasyGetInfoDbl(CURL* curl,
+ CURLINFO info,
+ double* value) const {
+ CHECK_EQ(CURLINFO_DOUBLE, info & CURLINFO_TYPEMASK) << "Wrong option type";
+ return curl_easy_getinfo(curl, info, value);
+}
+
+CURLcode CurlApi::EasyGetInfoStr(CURL* curl,
+ CURLINFO info,
+ std::string* value) const {
+ CHECK_EQ(CURLINFO_STRING, info & CURLINFO_TYPEMASK) << "Wrong option type";
+ char* data = nullptr;
+ CURLcode code = curl_easy_getinfo(curl, info, &data);
+ if (code == CURLE_OK)
+ *value = data;
+ return code;
+}
+
+std::string CurlApi::EasyStrError(CURLcode code) const {
+ return curl_easy_strerror(code);
+}
+
+} // namespace http
+} // namespace chromeos
diff --git a/chromeos/http/curl_api.h b/chromeos/http/curl_api.h
new file mode 100644
index 0000000..eb47a96
--- /dev/null
+++ b/chromeos/http/curl_api.h
@@ -0,0 +1,123 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBCHROMEOS_CHROMEOS_HTTP_CURL_API_H_
+#define LIBCHROMEOS_CHROMEOS_HTTP_CURL_API_H_
+
+#include <curl/curl.h>
+
+#include <string>
+
+#include <base/macros.h>
+#include <chromeos/chromeos_export.h>
+
+namespace chromeos {
+namespace http {
+
+// Abstract wrapper around libcurl C API that allows us to mock it out in tests.
+class CurlInterface {
+ public:
+ CurlInterface() = default;
+ virtual ~CurlInterface() = default;
+
+ // Wrapper around curl_easy_init().
+ virtual CURL* EasyInit() = 0;
+
+ // Wrapper around curl_easy_cleanup().
+ virtual void EasyCleanup(CURL* curl) = 0;
+
+ // Wrappers around curl_easy_setopt().
+ virtual CURLcode EasySetOptInt(CURL* curl, CURLoption option, int value) = 0;
+ virtual CURLcode EasySetOptStr(CURL* curl,
+ CURLoption option,
+ const std::string& value) = 0;
+ virtual CURLcode EasySetOptPtr(CURL* curl,
+ CURLoption option,
+ void* value) = 0;
+ virtual CURLcode EasySetOptCallback(CURL* curl,
+ CURLoption option,
+ intptr_t address) = 0;
+ virtual CURLcode EasySetOptOffT(CURL* curl,
+ CURLoption option,
+ curl_off_t value) = 0;
+
+ // A type-safe wrapper around function callback options.
+ template<typename R, typename... Args>
+ inline CURLcode EasySetOptCallback(CURL* curl,
+ CURLoption option,
+ R(*callback)(Args...)) {
+ return EasySetOptCallback(curl, option,
+ reinterpret_cast<intptr_t>(callback));
+ }
+
+ // Wrapper around curl_easy_perform().
+ virtual CURLcode EasyPerform(CURL* curl) = 0;
+
+ // Wrappers around curl_easy_getinfo().
+ virtual CURLcode EasyGetInfoInt(CURL* curl,
+ CURLINFO info,
+ int* value) const = 0;
+ virtual CURLcode EasyGetInfoDbl(CURL* curl,
+ CURLINFO info,
+ double* value) const = 0;
+ virtual CURLcode EasyGetInfoStr(CURL* curl,
+ CURLINFO info,
+ std::string* value) const = 0;
+
+ // Wrapper around curl_easy_strerror().
+ virtual std::string EasyStrError(CURLcode code) const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CurlInterface);
+};
+
+class CHROMEOS_EXPORT CurlApi : public CurlInterface {
+ public:
+ CurlApi();
+ ~CurlApi() override;
+
+ // Wrapper around curl_easy_init().
+ CURL* EasyInit() override;
+
+ // Wrapper around curl_easy_cleanup().
+ void EasyCleanup(CURL* curl) override;
+
+ // Wrappers around curl_easy_setopt().
+ CURLcode EasySetOptInt(CURL* curl, CURLoption option, int value) override;
+ CURLcode EasySetOptStr(CURL* curl,
+ CURLoption option,
+ const std::string& value) override;
+ CURLcode EasySetOptPtr(CURL* curl, CURLoption option, void* value) override;
+ CURLcode EasySetOptCallback(CURL* curl,
+ CURLoption option,
+ intptr_t address) override;
+ CURLcode EasySetOptOffT(CURL* curl,
+ CURLoption option,
+ curl_off_t value) override;
+
+ // Wrapper around curl_easy_perform().
+ CURLcode EasyPerform(CURL* curl) override;
+
+ // Wrappers around curl_easy_getinfo().
+ CURLcode EasyGetInfoInt(CURL* curl,
+ CURLINFO info,
+ int* value) const override;
+ CURLcode EasyGetInfoDbl(CURL* curl,
+ CURLINFO info,
+ double* value) const override;
+ CURLcode EasyGetInfoStr(CURL* curl,
+ CURLINFO info,
+ std::string* value) const override;
+
+ // Wrapper around curl_easy_strerror().
+ std::string EasyStrError(CURLcode code) const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CurlApi);
+};
+
+} // namespace http
+} // namespace chromeos
+
+#endif // LIBCHROMEOS_CHROMEOS_HTTP_CURL_API_H_
diff --git a/chromeos/http/http_connection.h b/chromeos/http/http_connection.h
index 335697d..fa1e7f1 100644
--- a/chromeos/http/http_connection.h
+++ b/chromeos/http/http_connection.h
@@ -36,7 +36,7 @@
class CHROMEOS_EXPORT Connection
: public std::enable_shared_from_this<Connection> {
public:
- explicit Connection(std::shared_ptr<Transport> transport)
+ explicit Connection(const std::shared_ptr<Transport>& transport)
: transport_(transport) {}
virtual ~Connection() = default;
diff --git a/chromeos/http/http_connection_curl.cc b/chromeos/http/http_connection_curl.cc
index 2c97121..62c9c97 100644
--- a/chromeos/http/http_connection_curl.cc
+++ b/chromeos/http/http_connection_curl.cc
@@ -45,15 +45,21 @@
return 0;
}
-Connection::Connection(CURL* curl_handle, const std::string& method,
- std::shared_ptr<http::Transport> transport) :
- http::Connection(transport), method_(method), curl_handle_(curl_handle) {
+Connection::Connection(CURL* curl_handle,
+ const std::string& method,
+ const std::shared_ptr<CurlInterface>& curl_interface,
+ const std::shared_ptr<http::Transport>& transport)
+ : http::Connection(transport),
+ method_(method),
+ curl_handle_(curl_handle),
+ curl_interface_(curl_interface) {
VLOG(1) << "curl::Connection created: " << method_;
}
Connection::~Connection() {
if (header_list_)
curl_slist_free_all(header_list_);
+ curl_interface_->EasyCleanup(curl_handle_);
VLOG(1) << "curl::Connection destroyed";
}
@@ -72,60 +78,59 @@
void Connection::PrepareRequest() {
if (VLOG_IS_ON(3)) {
- curl_easy_setopt(curl_handle_, CURLOPT_DEBUGFUNCTION, curl_trace);
- curl_easy_setopt(curl_handle_, CURLOPT_VERBOSE, 1L);
+ curl_interface_->EasySetOptCallback(curl_handle_, CURLOPT_DEBUGFUNCTION,
+ &curl_trace);
+ curl_interface_->EasySetOptInt(curl_handle_, CURLOPT_VERBOSE, 1);
}
// Set up HTTP request data.
uint64_t data_size =
request_data_reader_ ? request_data_reader_->GetDataSize() : 0;
if (method_ == request_type::kPut) {
- curl_easy_setopt(curl_handle_, CURLOPT_INFILESIZE_LARGE,
- curl_off_t(data_size));
+ curl_interface_->EasySetOptOffT(curl_handle_, CURLOPT_INFILESIZE_LARGE,
+ data_size);
} else {
- curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE_LARGE,
- curl_off_t(data_size));
+ curl_interface_->EasySetOptOffT(curl_handle_, CURLOPT_POSTFIELDSIZE_LARGE,
+ data_size);
}
if (request_data_reader_) {
- curl_easy_setopt(curl_handle_,
- CURLOPT_READFUNCTION, &Connection::read_callback);
- curl_easy_setopt(curl_handle_, CURLOPT_READDATA, this);
+ curl_interface_->EasySetOptCallback(curl_handle_, CURLOPT_READFUNCTION,
+ &Connection::read_callback);
+ curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_READDATA, this);
}
if (!headers_.empty()) {
CHECK(header_list_ == nullptr);
for (auto pair : headers_) {
- std::string header = chromeos::string_utils::Join(": ",
- pair.first,
- pair.second);
+ std::string header =
+ chromeos::string_utils::Join(": ", pair.first, pair.second);
VLOG(2) << "Request header: " << header;
header_list_ = curl_slist_append(header_list_, header.c_str());
}
- curl_easy_setopt(curl_handle_, CURLOPT_HTTPHEADER, header_list_);
+ curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_HTTPHEADER,
+ header_list_);
}
headers_.clear();
// Set up HTTP response data.
if (method_ != request_type::kHead) {
- curl_easy_setopt(curl_handle_,
- CURLOPT_WRITEFUNCTION, &Connection::write_callback);
- curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this);
+ curl_interface_->EasySetOptCallback(curl_handle_, CURLOPT_WRITEFUNCTION,
+ &Connection::write_callback);
+ curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_WRITEDATA, this);
}
// HTTP response headers
- curl_easy_setopt(curl_handle_,
- CURLOPT_HEADERFUNCTION, &Connection::header_callback);
- curl_easy_setopt(curl_handle_, CURLOPT_HEADERDATA, this);
+ curl_interface_->EasySetOptCallback(curl_handle_, CURLOPT_HEADERFUNCTION,
+ &Connection::header_callback);
+ curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_HEADERDATA, this);
}
bool Connection::FinishRequest(chromeos::ErrorPtr* error) {
PrepareRequest();
- CURLcode ret = curl_easy_perform(curl_handle_);
+ CURLcode ret = curl_interface_->EasyPerform(curl_handle_);
if (ret != CURLE_OK) {
- chromeos::Error::AddTo(error, FROM_HERE, "curl_error",
- chromeos::string_utils::ToString(ret),
- curl_easy_strerror(ret));
+ Transport::AddCurlError(error, FROM_HERE, ret, curl_interface_.get());
} else {
LOG(INFO) << "Response: " << GetResponseStatusCode() << " ("
<< GetResponseStatusText() << ")";
@@ -144,8 +149,9 @@
}
int Connection::GetResponseStatusCode() const {
- long status_code = 0; // NOLINT(runtime/int) - curl expects a long here.
- curl_easy_getinfo(curl_handle_, CURLINFO_RESPONSE_CODE, &status_code);
+ int status_code = 0;
+ curl_interface_->EasyGetInfoInt(curl_handle_, CURLINFO_RESPONSE_CODE,
+ &status_code);
return status_code;
}
diff --git a/chromeos/http/http_connection_curl.h b/chromeos/http/http_connection_curl.h
index cf17552..d926237 100644
--- a/chromeos/http/http_connection_curl.h
+++ b/chromeos/http/http_connection_curl.h
@@ -22,8 +22,10 @@
// This is a libcurl-based implementation of http::Connection.
class CHROMEOS_EXPORT Connection : public http::Connection {
public:
- Connection(CURL* curl_handle, const std::string& method,
- std::shared_ptr<http::Transport> transport);
+ Connection(CURL* curl_handle,
+ const std::string& method,
+ const std::shared_ptr<CurlInterface>& curl_interface,
+ const std::shared_ptr<http::Transport>& transport);
~Connection() override;
// Overrides from http::Connection.
@@ -71,7 +73,7 @@
// List of optional request headers provided by the caller.
// After request has been sent, contains the received response headers.
- std::map<std::string, std::string> headers_;
+ std::multimap<std::string, std::string> headers_;
// HTTP protocol version, such as HTTP/1.1
std::string protocol_version_;
@@ -84,6 +86,8 @@
CURL* curl_handle_{nullptr};
curl_slist* header_list_{nullptr};
+ std::shared_ptr<CurlInterface> curl_interface_;
+
private:
friend class http::curl::Transport;
DISALLOW_COPY_AND_ASSIGN(Connection);
diff --git a/chromeos/http/http_connection_curl_unittest.cc b/chromeos/http/http_connection_curl_unittest.cc
new file mode 100644
index 0000000..0a46450
--- /dev/null
+++ b/chromeos/http/http_connection_curl_unittest.cc
@@ -0,0 +1,312 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <chromeos/http/http_connection_curl.h>
+
+#include <algorithm>
+#include <set>
+
+#include <base/callback.h>
+#include <chromeos/http/http_request.h>
+#include <chromeos/http/http_transport.h>
+#include <chromeos/http/mock_curl_api.h>
+#include <chromeos/http/mock_transport.h>
+#include <chromeos/strings/string_utils.h>
+#include <chromeos/mime_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::DoAll;
+using testing::Invoke;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::_;
+
+namespace chromeos {
+namespace http {
+namespace curl {
+
+namespace {
+
+using ReadWriteCallback =
+ size_t(char* ptr, size_t size, size_t num, void* data);
+
+// A helper class to simulate curl_easy_perform action. It invokes the
+// read callbacks to obtain the request data from the Connection and then
+// calls the header and write callbacks to "send" the response header and body.
+class CurlPerformer {
+ public:
+ // During the tests, use the address of |this| as the CURL* handle.
+ // This allows the static Perform() method to obtain the instance pointer
+ // having only CURL*.
+ CURL* GetCurlHandle() { return reinterpret_cast<CURL*>(this); }
+
+ // Callback to be invoked when mocking out curl_easy_perform() method.
+ static CURLcode Perform(CURL* curl) {
+ CurlPerformer* me = reinterpret_cast<CurlPerformer*>(curl);
+ return me->DoPerform();
+ }
+
+ // CURL callback functions and |connection| pointer needed to invoke the
+ // callbacks from the Connection class.
+ Connection* connection{nullptr};
+ ReadWriteCallback* write_callback{nullptr};
+ ReadWriteCallback* read_callback{nullptr};
+ ReadWriteCallback* header_callback{nullptr};
+
+ // Request body read from the connection.
+ std::string request_body;
+
+ // Response data to be sent back to connection.
+ std::string status_line;
+ HeaderList response_headers;
+ std::string response_body;
+
+ private:
+ // The actual implementation of curl_easy_perform() fake.
+ CURLcode DoPerform() {
+ // Read request body.
+ char buffer[1024];
+ for (;;) {
+ size_t size_read = read_callback(buffer, sizeof(buffer), 1, connection);
+ if (size_read == CURL_READFUNC_ABORT)
+ return CURLE_ABORTED_BY_CALLBACK;
+ if (size_read == CURL_READFUNC_PAUSE)
+ return CURLE_READ_ERROR; // Shouldn't happen.
+ if (size_read == 0)
+ break;
+ request_body.append(buffer, size_read);
+ }
+
+ // Send the response headers.
+ std::vector<std::string> header_lines;
+ header_lines.push_back(status_line + "\r\n");
+ for (const auto& pair : response_headers) {
+ header_lines.push_back(
+ string_utils::Join(": ", pair.first, pair.second) + "\r\n");
+ }
+
+ for (const std::string& line : header_lines) {
+ CURLcode code = WriteString(header_callback, line);
+ if (code != CURLE_OK)
+ return code;
+ }
+
+ // Send response body.
+ return WriteString(write_callback, response_body);
+ }
+
+ // Helper method to send a string to a write callback. Keeps calling
+ // the callback until all the data is written.
+ CURLcode WriteString(ReadWriteCallback* callback, const std::string& str) {
+ size_t pos = 0;
+ size_t size_remaining = str.size();
+ while (size_remaining) {
+ size_t size_written = callback(const_cast<char*>(str.data() + pos),
+ size_remaining, 1, connection);
+ if (size_written == CURL_WRITEFUNC_PAUSE)
+ return CURLE_WRITE_ERROR; // Shouldn't happen.
+ CHECK(size_written <= size_remaining) << "Unexpected size returned";
+ size_remaining -= size_written;
+ pos += size_written;
+ }
+ return CURLE_OK;
+ }
+};
+
+// Custom matcher to validate the parameter of CURLOPT_HTTPHEADER CURL option
+// which contains the request headers as curl_slist* chain.
+MATCHER_P(HeadersMatch, headers, "") {
+ std::multiset<std::string> test_headers;
+ for (const auto& pair : headers)
+ test_headers.insert(string_utils::Join(": ", pair.first, pair.second));
+
+ std::multiset<std::string> src_headers;
+ const curl_slist* head = static_cast<const curl_slist*>(arg);
+ while (head) {
+ src_headers.insert(head->data);
+ head = head->next;
+ }
+
+ std::vector<std::string> difference;
+ std::set_symmetric_difference(src_headers.begin(), src_headers.end(),
+ test_headers.begin(), test_headers.end(),
+ std::back_inserter(difference));
+ return difference.empty();
+}
+
+// Custom action to save a CURL callback pointer into a member of CurlPerformer.
+ACTION_TEMPLATE(SaveCallback,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_2_VALUE_PARAMS(performer, mem_ptr)) {
+ performer->*mem_ptr = reinterpret_cast<ReadWriteCallback*>(std::get<k>(args));
+}
+
+} // anonymous namespace
+
+class HttpCurlConnectionTest : public testing::Test {
+ public:
+ void SetUp() override {
+ curl_api_ = std::make_shared<MockCurlInterface>();
+ transport_ = std::make_shared<MockTransport>();
+ connection_ = std::make_shared<Connection>(handle_, request_type::kPost,
+ curl_api_, transport_);
+ performer_.connection = connection_.get();
+ }
+
+ void TearDown() override {
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ connection_.reset();
+ transport_.reset();
+ curl_api_.reset();
+ }
+
+ protected:
+ std::shared_ptr<MockCurlInterface> curl_api_;
+ std::shared_ptr<MockTransport> transport_;
+ CurlPerformer performer_;
+ CURL* handle_{performer_.GetCurlHandle()};
+ std::shared_ptr<Connection> connection_;
+};
+
+TEST_F(HttpCurlConnectionTest, FinishRequestAsync) {
+ std::string request_data{"Foo Bar Baz"};
+ std::unique_ptr<MemoryDataReader> reader{new MemoryDataReader{request_data}};
+ EXPECT_TRUE(connection_->SetRequestData(std::move(reader), nullptr));
+ EXPECT_TRUE(connection_->SendHeaders({{"X-Foo", "bar"}}, nullptr));
+
+ if (VLOG_IS_ON(3)) {
+ EXPECT_CALL(*curl_api_,
+ EasySetOptCallback(handle_, CURLOPT_DEBUGFUNCTION, _))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_VERBOSE, 1))
+ .WillOnce(Return(CURLE_OK));
+ }
+
+ EXPECT_CALL(*curl_api_, EasySetOptOffT(handle_, CURLOPT_POSTFIELDSIZE_LARGE,
+ request_data.size()))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_READFUNCTION, _))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_READDATA, _))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_HTTPHEADER, _))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_WRITEFUNCTION, _))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_WRITEDATA, _))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_,
+ EasySetOptCallback(handle_, CURLOPT_HEADERFUNCTION, _))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_HEADERDATA, _))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*transport_, StartAsyncTransfer(connection_.get(), _, _))
+ .Times(1);
+
+ connection_->FinishRequestAsync({}, {});
+}
+
+TEST_F(HttpCurlConnectionTest, FinishRequest) {
+ // Set up the request data.
+ std::string request_data{"Foo Bar Baz"};
+ std::unique_ptr<MemoryDataReader> reader{new MemoryDataReader{request_data}};
+ HeaderList headers{
+ {request_header::kAccept, "*/*"},
+ {request_header::kContentType, mime::text::kPlain},
+ {request_header::kContentLength, std::to_string(request_data.size())},
+ {"X-Foo", "bar"},
+ };
+ EXPECT_TRUE(connection_->SetRequestData(std::move(reader), nullptr));
+ EXPECT_TRUE(connection_->SendHeaders(headers, nullptr));
+
+ // Expectations for Connection::FinishRequest() call.
+ if (VLOG_IS_ON(3)) {
+ EXPECT_CALL(*curl_api_,
+ EasySetOptCallback(handle_, CURLOPT_DEBUGFUNCTION, _))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_VERBOSE, 1))
+ .WillOnce(Return(CURLE_OK));
+ }
+
+ EXPECT_CALL(*curl_api_, EasySetOptOffT(handle_, CURLOPT_POSTFIELDSIZE_LARGE,
+ request_data.size()))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_READFUNCTION, _))
+ .WillOnce(DoAll(
+ SaveCallback<2>(&performer_, &CurlPerformer::read_callback),
+ Return(CURLE_OK)));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_READDATA, _))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_,
+ EasySetOptPtr(handle_, CURLOPT_HTTPHEADER, HeadersMatch(headers)))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_WRITEFUNCTION, _))
+ .WillOnce(DoAll(
+ SaveCallback<2>(&performer_, &CurlPerformer::write_callback),
+ Return(CURLE_OK)));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_WRITEDATA, _))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_,
+ EasySetOptCallback(handle_, CURLOPT_HEADERFUNCTION, _))
+ .WillOnce(DoAll(
+ SaveCallback<2>(&performer_, &CurlPerformer::header_callback),
+ Return(CURLE_OK)));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_HEADERDATA, _))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_, EasyPerform(handle_))
+ .WillOnce(Invoke(&CurlPerformer::Perform));
+
+ EXPECT_CALL(*curl_api_, EasyGetInfoInt(handle_, CURLINFO_RESPONSE_CODE, _))
+ .WillOnce(DoAll(SetArgPointee<2>(status_code::Ok), Return(CURLE_OK)));
+
+ // Set up the CurlPerformer with the response data expected to be received.
+ std::string response_data{"<html><body>OK</body></html>"};
+ HeaderList response_headers{
+ {response_header::kContentLength, std::to_string(response_data.size())},
+ {response_header::kContentType, mime::text::kHtml},
+ {"X-Foo", "baz"},
+ };
+ performer_.status_line = "HTTP/1.1 200 OK";
+ performer_.response_body = response_data;
+ performer_.response_headers = response_headers;
+
+ // Perform the request.
+ EXPECT_TRUE(connection_->FinishRequest(nullptr));
+
+ // Make sure we sent out the request body correctly.
+ EXPECT_EQ(request_data, performer_.request_body);
+
+ // Validate the parsed response data.
+ EXPECT_CALL(*curl_api_, EasyGetInfoInt(handle_, CURLINFO_RESPONSE_CODE, _))
+ .WillOnce(DoAll(SetArgPointee<2>(status_code::Ok), Return(CURLE_OK)));
+ EXPECT_EQ(status_code::Ok, connection_->GetResponseStatusCode());
+ EXPECT_EQ("HTTP/1.1", connection_->GetProtocolVersion());
+ EXPECT_EQ("OK", connection_->GetResponseStatusText());
+ EXPECT_EQ(std::to_string(response_data.size()),
+ connection_->GetResponseHeader(response_header::kContentLength));
+ EXPECT_EQ(mime::text::kHtml,
+ connection_->GetResponseHeader(response_header::kContentType));
+ EXPECT_EQ("baz", connection_->GetResponseHeader("X-Foo"));
+ EXPECT_EQ(response_data.size(), connection_->GetResponseDataSize());
+ char buffer[100];
+ size_t size_read = 0;
+ EXPECT_TRUE(connection_->ReadResponseData(buffer, sizeof(buffer), &size_read,
+ nullptr));
+ EXPECT_EQ(response_data, (std::string{buffer, size_read}));
+}
+
+} // namespace curl
+} // namespace http
+} // namespace chromeos
diff --git a/chromeos/http/http_connection_fake.cc b/chromeos/http/http_connection_fake.cc
index dc358af..d10d242 100644
--- a/chromeos/http/http_connection_fake.cc
+++ b/chromeos/http/http_connection_fake.cc
@@ -15,7 +15,7 @@
namespace fake {
Connection::Connection(const std::string& url, const std::string& method,
- std::shared_ptr<http::Transport> transport) :
+ const std::shared_ptr<http::Transport>& transport) :
http::Connection(transport), request_(url, method) {
VLOG(1) << "fake::Connection created: " << method;
}
diff --git a/chromeos/http/http_connection_fake.h b/chromeos/http/http_connection_fake.h
index 44e5622..b81d473 100644
--- a/chromeos/http/http_connection_fake.h
+++ b/chromeos/http/http_connection_fake.h
@@ -21,7 +21,7 @@
class Connection : public http::Connection {
public:
Connection(const std::string& url, const std::string& method,
- std::shared_ptr<http::Transport> transport);
+ const std::shared_ptr<http::Transport>& transport);
virtual ~Connection();
// Overrides from http::Connection.
diff --git a/chromeos/http/http_request.cc b/chromeos/http/http_request.cc
index aaab23b..40e1a14 100644
--- a/chromeos/http/http_request.cc
+++ b/chromeos/http/http_request.cc
@@ -154,7 +154,7 @@
accept_ = accept_mime_types;
}
-std::string Request::GetAccept() const {
+const std::string& Request::GetAccept() const {
return accept_;
}
@@ -162,12 +162,12 @@
content_type_ = contentType;
}
-std::string Request::GetContentType() const {
+const std::string& Request::GetContentType() const {
return content_type_;
}
void Request::AddHeader(const std::string& header, const std::string& value) {
- headers_[header] = value;
+ headers_.emplace(header, value);
}
void Request::AddHeaders(const HeaderList& headers) {
@@ -199,11 +199,19 @@
return connection_->SetRequestData(std::move(form_data), error);
}
+const std::string& Request::GetRequestURL() const {
+ return request_url_;
+}
+
+const std::string& Request::GetRequestMethod() const {
+ return method_;
+}
+
void Request::SetReferer(const std::string& referer) {
referer_ = referer;
}
-std::string Request::GetReferer() const {
+const std::string& Request::GetReferer() const {
return referer_;
}
@@ -211,7 +219,7 @@
user_agent_ = user_agent;
}
-std::string Request::GetUserAgent() const {
+const std::string& Request::GetUserAgent() const {
return user_agent_;
}
@@ -247,10 +255,8 @@
if (!content_type_.empty())
headers.emplace_back(request_header::kContentType, content_type_);
}
- connection_ = transport_->CreateConnection(transport_, request_url_,
- method_, headers,
- user_agent_, referer_,
- error);
+ connection_ = transport_->CreateConnection(request_url_, method_, headers,
+ user_agent_, referer_, error);
}
if (connection_)
@@ -274,7 +280,7 @@
if (connection_) {
size_t size = static_cast<size_t>(connection_->GetResponseDataSize());
response_data_.reserve(size);
- unsigned char buffer[1024];
+ uint8_t buffer[1024];
size_t read = 0;
while (connection_->ReadResponseData(buffer, sizeof(buffer),
&read, nullptr) && read > 0) {
@@ -310,7 +316,7 @@
return GetHeader(response_header::kContentType);
}
-const std::vector<unsigned char>& Response::GetData() const {
+const std::vector<uint8_t>& Response::GetData() const {
return response_data_;
}
diff --git a/chromeos/http/http_request.h b/chromeos/http/http_request.h
index f207d03..c57573f 100644
--- a/chromeos/http/http_request.h
+++ b/chromeos/http/http_request.h
@@ -223,11 +223,11 @@
// Gets/Sets "Accept:" header value. The default value is "*/*" if not set.
void SetAccept(const std::string& accept_mime_types);
- std::string GetAccept() const;
+ const std::string& GetAccept() const;
// Gets/Sets "Content-Type:" header value
void SetContentType(const std::string& content_type);
- std::string GetContentType() const;
+ const std::string& GetContentType() const;
// Adds additional HTTP request header
void AddHeader(const std::string& header, const std::string& value);
@@ -260,15 +260,18 @@
void AddRange(uint64_t from_byte, uint64_t to_byte);
// Returns the request URL
- std::string GetRequestURL() const;
+ const std::string& GetRequestURL() const;
+
+ // Returns the request verb.
+ const std::string& GetRequestMethod() const;
// Gets/Sets a request referer URL (sent as "Referer:" request header).
void SetReferer(const std::string& referer);
- std::string GetReferer() const;
+ const std::string& GetReferer() const;
// Gets/Sets a user agent string (sent as "User-Agent:" request header).
void SetUserAgent(const std::string& user_agent);
- std::string GetUserAgent() const;
+ const std::string& GetUserAgent() const;
// Sends the request to the server and blocks until the response is received,
// which is returned as the response object.
@@ -283,6 +286,8 @@
const ErrorCallback& error_callback);
private:
+ friend class HttpRequestTest;
+
// Helper function to create an http::Connection and send off request headers.
CHROMEOS_PRIVATE bool SendRequestIfNeeded(chromeos::ErrorPtr* error);
@@ -312,7 +317,7 @@
// List of optional request headers provided by the caller.
// After request has been sent, contains the received response headers.
- std::map<std::string, std::string> headers_;
+ std::multimap<std::string, std::string> headers_;
// List of optional data ranges to request partial content from the server.
// Sent to the server as "Range: " header.
std::vector<std::pair<uint64_t, uint64_t>> ranges_;
@@ -348,7 +353,7 @@
std::string GetContentType() const;
// Returns response data as a byte array
- const std::vector<unsigned char>& GetData() const;
+ const std::vector<uint8_t>& GetData() const;
// Returns response data as a string
std::string GetDataAsString() const;
@@ -357,8 +362,10 @@
std::string GetHeader(const std::string& header_name) const;
private:
+ friend class HttpRequestTest;
+
std::shared_ptr<Connection> connection_;
- std::vector<unsigned char> response_data_;
+ std::vector<uint8_t> response_data_;
DISALLOW_COPY_AND_ASSIGN(Response);
};
diff --git a/chromeos/http/http_request_unittest.cc b/chromeos/http/http_request_unittest.cc
new file mode 100644
index 0000000..84e3c91
--- /dev/null
+++ b/chromeos/http/http_request_unittest.cc
@@ -0,0 +1,201 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <chromeos/http/http_request.h>
+
+#include <string>
+
+#include <base/callback.h>
+#include <chromeos/bind_lambda.h>
+#include <chromeos/http/mock_connection.h>
+#include <chromeos/http/mock_transport.h>
+#include <chromeos/mime_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::DoAll;
+using testing::Invoke;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::Unused;
+using testing::WithArg;
+using testing::_;
+
+namespace chromeos {
+namespace http {
+
+MATCHER_P(ContainsStringData, str, "") {
+ if (arg->GetDataSize() != str.size())
+ return false;
+
+ std::string data;
+ char buf[100];
+ size_t read = 0;
+ while (arg->ReadData(buf, sizeof(buf), &read, nullptr) && read > 0) {
+ data.append(buf, read);
+ }
+ return data == str;
+}
+
+class HttpRequestTest : public testing::Test {
+ public:
+ void SetUp() override {
+ transport_ = std::make_shared<MockTransport>();
+ connection_ = std::make_shared<MockConnection>(transport_);
+ }
+
+ void TearDown() override {
+ // Having shared pointers to mock objects (some of methods in these tests
+ // return shared pointers to connection and transport) could cause the
+ // test expectations to hold on to the mock object without releasing them
+ // at the end of a test, causing Mock's object leak detection to erroneously
+ // detect mock object "leaks". Verify and clear the expectations manually
+ // and explicitly to ensure the shared pointer refcounters are not
+ // preventing the mocks to be destroyed at the end of each test.
+ testing::Mock::VerifyAndClearExpectations(connection_.get());
+ connection_.reset();
+ testing::Mock::VerifyAndClearExpectations(transport_.get());
+ transport_.reset();
+ }
+
+ protected:
+ std::shared_ptr<MockTransport> transport_;
+ std::shared_ptr<MockConnection> connection_;
+};
+
+TEST_F(HttpRequestTest, Defaults) {
+ Request request{"http://www.foo.bar", request_type::kPost, transport_};
+ EXPECT_TRUE(request.GetContentType().empty());
+ EXPECT_TRUE(request.GetReferer().empty());
+ EXPECT_TRUE(request.GetUserAgent().empty());
+ EXPECT_EQ("*/*", request.GetAccept());
+ EXPECT_EQ("http://www.foo.bar", request.GetRequestURL());
+ EXPECT_EQ(request_type::kPost, request.GetRequestMethod());
+
+ Request request2{"http://www.foo.bar/baz", request_type::kGet, transport_};
+ EXPECT_EQ("http://www.foo.bar/baz", request2.GetRequestURL());
+ EXPECT_EQ(request_type::kGet, request2.GetRequestMethod());
+}
+
+TEST_F(HttpRequestTest, ContentType) {
+ Request request{"http://www.foo.bar", request_type::kPost, transport_};
+ request.SetContentType(mime::image::kJpeg);
+ EXPECT_EQ(mime::image::kJpeg, request.GetContentType());
+}
+
+TEST_F(HttpRequestTest, Referer) {
+ Request request{"http://www.foo.bar", request_type::kPost, transport_};
+ request.SetReferer("http://www.foo.bar/baz");
+ EXPECT_EQ("http://www.foo.bar/baz", request.GetReferer());
+}
+
+TEST_F(HttpRequestTest, UserAgent) {
+ Request request{"http://www.foo.bar", request_type::kPost, transport_};
+ request.SetUserAgent("FooBar Browser");
+ EXPECT_EQ("FooBar Browser", request.GetUserAgent());
+}
+
+TEST_F(HttpRequestTest, Accept) {
+ Request request{"http://www.foo.bar", request_type::kPost, transport_};
+ request.SetAccept("text/*, text/html, text/html;level=1, */*");
+ EXPECT_EQ("text/*, text/html, text/html;level=1, */*", request.GetAccept());
+}
+
+TEST_F(HttpRequestTest, GetResponseAndBlock) {
+ Request request{"http://www.foo.bar", request_type::kPost, transport_};
+ request.SetUserAgent("FooBar Browser");
+ request.SetReferer("http://www.foo.bar/baz");
+ request.SetAccept("text/*, text/html, text/html;level=1, */*");
+ request.AddHeader(request_header::kAcceptEncoding, "compress, gzip");
+ request.AddHeaders({
+ {request_header::kAcceptLanguage, "da, en-gb;q=0.8, en;q=0.7"},
+ {request_header::kConnection, "close"},
+ });
+ request.AddRange(-10);
+ request.AddRange(100, 200);
+ request.AddRange(300);
+ std::string req_body{"Foo bar baz"};
+ request.AddHeader(request_header::kContentType, mime::text::kPlain);
+
+ EXPECT_CALL(*transport_, CreateConnection(
+ "http://www.foo.bar",
+ request_type::kPost,
+ HeaderList{
+ {request_header::kAcceptEncoding, "compress, gzip"},
+ {request_header::kAcceptLanguage, "da, en-gb;q=0.8, en;q=0.7"},
+ {request_header::kConnection, "close"},
+ {request_header::kContentType, mime::text::kPlain},
+ {request_header::kRange, "bytes=-10,100-200,300-"},
+ {request_header::kAccept, "text/*, text/html, text/html;level=1, */*"},
+ },
+ "FooBar Browser",
+ "http://www.foo.bar/baz",
+ nullptr)).WillOnce(Return(connection_));
+
+ EXPECT_CALL(*connection_, MockSetRequestData(ContainsStringData(req_body), _))
+ .WillOnce(Return(true));
+
+ EXPECT_TRUE(request.AddRequestBody(req_body.data(), req_body.size(),
+ nullptr));
+
+ EXPECT_CALL(*connection_, FinishRequest(_))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*connection_, GetResponseDataSize())
+ .WillOnce(Return(0));
+ EXPECT_CALL(*connection_, ReadResponseData(_, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
+ auto resp = request.GetResponseAndBlock(nullptr);
+ EXPECT_NE(nullptr, resp.get());
+}
+
+TEST_F(HttpRequestTest, GetResponse) {
+ Request request{"http://foo.bar", request_type::kGet, transport_};
+
+ std::string resp_data{"FooBar response body"};
+ auto read_data =
+ [&resp_data](void* buffer, Unused, size_t* read, Unused) -> bool {
+ memcpy(buffer, resp_data.data(), resp_data.size());
+ *read = resp_data.size();
+ return true;
+ };
+
+ auto success_callback = [this, &resp_data](scoped_ptr<Response> resp) {
+ EXPECT_CALL(*connection_, GetResponseStatusCode())
+ .WillOnce(Return(status_code::Partial));
+ EXPECT_EQ(status_code::Partial, resp->GetStatusCode());
+
+ EXPECT_CALL(*connection_, GetResponseStatusText())
+ .WillOnce(Return("Partial completion"));
+ EXPECT_EQ("Partial completion", resp->GetStatusText());
+
+ EXPECT_CALL(*connection_, GetResponseHeader(response_header::kContentType))
+ .WillOnce(Return(mime::text::kHtml));
+ EXPECT_EQ(mime::text::kHtml, resp->GetContentType());
+
+ EXPECT_EQ(resp_data, resp->GetDataAsString());
+ };
+
+ auto finish_request_async =
+ [this, &read_data, &resp_data](const SuccessCallback& success_callback) {
+ EXPECT_CALL(*connection_, GetResponseDataSize())
+ .WillOnce(Return(resp_data.size()));
+ EXPECT_CALL(*connection_, ReadResponseData(_, _, _, _))
+ .WillOnce(Invoke(read_data))
+ .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
+ scoped_ptr<Response> resp{new Response{connection_}};
+ success_callback.Run(resp.Pass());
+ };
+
+ EXPECT_CALL(*transport_, CreateConnection("http://foo.bar",
+ request_type::kGet, _, "", "", _))
+ .WillOnce(Return(connection_));
+
+ EXPECT_CALL(*connection_, FinishRequestAsync(_, _))
+ .WillOnce(WithArg<0>(Invoke(finish_request_async)));
+
+ request.GetResponse(base::Bind(success_callback), {});
+}
+
+} // namespace http
+} // namespace chromeos
diff --git a/chromeos/http/http_transport.cc b/chromeos/http/http_transport.cc
index 01b4776..785e5fd 100644
--- a/chromeos/http/http_transport.cc
+++ b/chromeos/http/http_transport.cc
@@ -12,7 +12,7 @@
const char kErrorDomain[] = "http_transport";
std::shared_ptr<Transport> Transport::CreateDefault() {
- return std::make_shared<http::curl::Transport>();
+ return std::make_shared<http::curl::Transport>(std::make_shared<CurlApi>());
}
} // namespace http
diff --git a/chromeos/http/http_transport.h b/chromeos/http/http_transport.h
index 100d268..080db33 100644
--- a/chromeos/http/http_transport.h
+++ b/chromeos/http/http_transport.h
@@ -38,7 +38,8 @@
// This class (and its underlying implementation) is used by http::Request and
// http::Response classes to provide HTTP functionality to the clients.
///////////////////////////////////////////////////////////////////////////////
-class CHROMEOS_EXPORT Transport {
+class CHROMEOS_EXPORT Transport
+ : public std::enable_shared_from_this<Transport> {
public:
Transport() = default;
virtual ~Transport() = default;
@@ -47,7 +48,6 @@
// |transport| is a shared pointer to this transport object instance,
// used to maintain the object alive as long as the connection exists.
virtual std::shared_ptr<Connection> CreateConnection(
- std::shared_ptr<Transport> transport,
const std::string& url,
const std::string& method,
const HeaderList& headers,
diff --git a/chromeos/http/http_transport_curl.cc b/chromeos/http/http_transport_curl.cc
index 6212414..43f0429 100644
--- a/chromeos/http/http_transport_curl.cc
+++ b/chromeos/http/http_transport_curl.cc
@@ -15,18 +15,23 @@
namespace http {
namespace curl {
-Transport::Transport()
- : task_runner_{base::MessageLoopProxy::current()} {
+Transport::Transport(const std::shared_ptr<CurlInterface>& curl_interface)
+ : curl_interface_{curl_interface},
+ task_runner_{base::MessageLoopProxy::current()} {
VLOG(1) << "curl::Transport created";
}
-Transport::Transport(scoped_refptr<base::TaskRunner> task_runner)
- : task_runner_{task_runner} {
+Transport::Transport(const std::shared_ptr<CurlInterface>& curl_interface,
+ scoped_refptr<base::TaskRunner> task_runner)
+ : curl_interface_{curl_interface},
+ task_runner_{task_runner} {
VLOG(1) << "curl::Transport created";
}
-Transport::Transport(const std::string& proxy)
- : proxy_{proxy},
+Transport::Transport(const std::shared_ptr<CurlInterface>& curl_interface,
+ const std::string& proxy)
+ : curl_interface_{curl_interface},
+ proxy_{proxy},
task_runner_{base::MessageLoopProxy::current()} {
VLOG(1) << "curl::Transport created with proxy " << proxy;
}
@@ -36,55 +41,64 @@
}
std::shared_ptr<http::Connection> Transport::CreateConnection(
- std::shared_ptr<http::Transport> transport,
const std::string& url,
const std::string& method,
const HeaderList& headers,
const std::string& user_agent,
const std::string& referer,
chromeos::ErrorPtr* error) {
- CURL* curl_handle = curl_easy_init();
+ std::shared_ptr<http::Connection> connection;
+ CURL* curl_handle = curl_interface_->EasyInit();
if (!curl_handle) {
LOG(ERROR) << "Failed to initialize CURL";
chromeos::Error::AddTo(error, FROM_HERE, http::kErrorDomain,
"curl_init_failed", "Failed to initialize CURL");
- return std::shared_ptr<http::Connection>();
+ return connection;
}
LOG(INFO) << "Sending a " << method << " request to " << url;
- curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
+ CURLcode code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_URL, url);
- if (!user_agent.empty()) {
- curl_easy_setopt(curl_handle,
- CURLOPT_USERAGENT, user_agent.c_str());
+ if (code == CURLE_OK && !user_agent.empty()) {
+ code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_USERAGENT,
+ user_agent);
}
-
- if (!referer.empty()) {
- curl_easy_setopt(curl_handle,
- CURLOPT_REFERER, referer.c_str());
+ if (code == CURLE_OK && !referer.empty()) {
+ code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_REFERER,
+ referer);
}
- if (!proxy_.empty()) {
- curl_easy_setopt(curl_handle,
- CURLOPT_PROXY, proxy_.c_str());
+ if (code == CURLE_OK && !proxy_.empty()) {
+ code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_PROXY, proxy_);
}
// Setup HTTP request method and optional request body.
- if (method == request_type::kGet) {
- curl_easy_setopt(curl_handle, CURLOPT_HTTPGET, 1L);
- } else if (method == request_type::kHead) {
- curl_easy_setopt(curl_handle, CURLOPT_NOBODY, 1L);
- } else if (method == request_type::kPut) {
- curl_easy_setopt(curl_handle, CURLOPT_UPLOAD, 1L);
- } else {
- // POST and custom request methods
- curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
- curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, nullptr);
- if (method != request_type::kPost)
- curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, method.c_str());
+ if (code == CURLE_OK) {
+ if (method == request_type::kGet) {
+ code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_HTTPGET, 1);
+ } else if (method == request_type::kHead) {
+ code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_NOBODY, 1);
+ } else if (method == request_type::kPut) {
+ code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_UPLOAD, 1);
+ } else {
+ // POST and custom request methods
+ code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_POST, 1);
+ if (code == CURLE_OK)
+ code = curl_interface_->EasySetOptPtr(curl_handle, CURLOPT_POSTFIELDS,
+ nullptr);
+ if (code == CURLE_OK && method != request_type::kPost)
+ code = curl_interface_->EasySetOptStr(curl_handle,
+ CURLOPT_CUSTOMREQUEST, method);
+ }
}
- std::shared_ptr<http::Connection> connection =
- std::make_shared<http::curl::Connection>(curl_handle, method, transport);
+ if (code != CURLE_OK) {
+ AddCurlError(error, FROM_HERE, code, curl_interface_.get());
+ curl_interface_->EasyCleanup(curl_handle);
+ return connection;
+ }
+
+ connection = std::make_shared<http::curl::Connection>(
+ curl_handle, method, curl_interface_, shared_from_this());
if (!connection->SendHeaders(headers, error)) {
connection.reset();
}
@@ -103,12 +117,10 @@
// but this is to change in the follow-up CLs.
http::curl::Connection* curl_connection =
static_cast<http::curl::Connection*>(connection);
- CURLcode ret = curl_easy_perform(curl_connection->curl_handle_);
+ CURLcode ret = curl_interface_->EasyPerform(curl_connection->curl_handle_);
if (ret != CURLE_OK) {
chromeos::ErrorPtr error;
- chromeos::Error::AddTo(&error, FROM_HERE, "curl_error",
- chromeos::string_utils::ToString(ret),
- curl_easy_strerror(ret));
+ AddCurlError(&error, FROM_HERE, ret, curl_interface_.get());
RunCallbackAsync(FROM_HERE,
base::Bind(error_callback, base::Owned(error.release())));
} else {
@@ -118,6 +130,15 @@
}
}
+void Transport::AddCurlError(chromeos::ErrorPtr* error,
+ const tracked_objects::Location& location,
+ CURLcode code,
+ CurlInterface* curl_interface) {
+ chromeos::Error::AddTo(error, location, "curl_error",
+ chromeos::string_utils::ToString(code),
+ curl_interface->EasyStrError(code));
+}
+
} // namespace curl
} // namespace http
} // namespace chromeos
diff --git a/chromeos/http/http_transport_curl.h b/chromeos/http/http_transport_curl.h
index dd2a341..fcc23d8 100644
--- a/chromeos/http/http_transport_curl.h
+++ b/chromeos/http/http_transport_curl.h
@@ -10,6 +10,7 @@
#include <base/memory/ref_counted.h>
#include <base/task_runner.h>
#include <chromeos/chromeos_export.h>
+#include <chromeos/http/curl_api.h>
#include <chromeos/http/http_transport.h>
namespace chromeos {
@@ -27,18 +28,19 @@
public:
// Constructs the transport using the current message loop for async
// operations.
- Transport();
+ explicit Transport(const std::shared_ptr<CurlInterface>& curl_interface);
// Constructs the transport with a custom task runner for async operations.
- explicit Transport(scoped_refptr<base::TaskRunner> task_runner);
+ Transport(const std::shared_ptr<CurlInterface>& curl_interface,
+ scoped_refptr<base::TaskRunner> task_runner);
// Creates a transport object using a proxy.
// |proxy| is of the form [protocol://][user:password@]host[:port].
// If not defined, protocol is assumed to be http://.
- explicit Transport(const std::string& proxy);
+ Transport(const std::shared_ptr<CurlInterface>& curl_interface,
+ const std::string& proxy);
virtual ~Transport();
// Overrides from http::Transport.
std::shared_ptr<http::Connection> CreateConnection(
- std::shared_ptr<http::Transport> transport,
const std::string& url,
const std::string& method,
const HeaderList& headers,
@@ -53,7 +55,13 @@
const SuccessCallback& success_callback,
const ErrorCallback& error_callback) override;
+ static void AddCurlError(chromeos::ErrorPtr* error,
+ const tracked_objects::Location& location,
+ CURLcode code,
+ CurlInterface* curl_interface);
+
private:
+ std::shared_ptr<CurlInterface> curl_interface_;
std::string proxy_;
scoped_refptr<base::TaskRunner> task_runner_;
diff --git a/chromeos/http/http_transport_curl_unittest.cc b/chromeos/http/http_transport_curl_unittest.cc
new file mode 100644
index 0000000..b330182
--- /dev/null
+++ b/chromeos/http/http_transport_curl_unittest.cc
@@ -0,0 +1,155 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <chromeos/http/http_transport_curl.h>
+
+#include <chromeos/http/http_request.h>
+#include <chromeos/http/mock_curl_api.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::InSequence;
+using testing::Return;
+
+namespace chromeos {
+namespace http {
+namespace curl {
+
+class HttpCurlTransportTest : public testing::Test {
+ public:
+ void SetUp() override {
+ curl_api_ = std::make_shared<MockCurlInterface>();
+ transport_ = std::make_shared<Transport>(curl_api_);
+ handle_ = reinterpret_cast<CURL*>(100); // Mock handle value.
+ }
+
+ void TearDown() override {
+ transport_.reset();
+ curl_api_.reset();
+ }
+
+ protected:
+ std::shared_ptr<MockCurlInterface> curl_api_;
+ std::shared_ptr<Transport> transport_;
+ CURL* handle_{nullptr};
+};
+
+TEST_F(HttpCurlTransportTest, RequestGet) {
+ InSequence seq;
+ EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_));
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_USERAGENT, "User Agent"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_REFERER, "http://foo.bar/baz"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
+ .WillOnce(Return(CURLE_OK));
+ auto connection = transport_->CreateConnection(
+ "http://foo.bar/get", request_type::kGet, {}, "User Agent",
+ "http://foo.bar/baz", nullptr);
+ EXPECT_NE(nullptr, connection.get());
+
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ connection.reset();
+}
+
+TEST_F(HttpCurlTransportTest, RequestHead) {
+ InSequence seq;
+ EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_));
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/head"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_NOBODY, 1))
+ .WillOnce(Return(CURLE_OK));
+ auto connection = transport_->CreateConnection(
+ "http://foo.bar/head", request_type::kHead, {}, "", "", nullptr);
+ EXPECT_NE(nullptr, connection.get());
+
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ connection.reset();
+}
+
+TEST_F(HttpCurlTransportTest, RequestPut) {
+ InSequence seq;
+ EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_));
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/put"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_UPLOAD, 1))
+ .WillOnce(Return(CURLE_OK));
+ auto connection = transport_->CreateConnection(
+ "http://foo.bar/put", request_type::kPut, {}, "", "", nullptr);
+ EXPECT_NE(nullptr, connection.get());
+
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ connection.reset();
+}
+
+TEST_F(HttpCurlTransportTest, RequestPost) {
+ InSequence seq;
+ EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_));
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_URL, "http://www.foo.bar/post"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_POST, 1))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_POSTFIELDS, nullptr))
+ .WillOnce(Return(CURLE_OK));
+ auto connection = transport_->CreateConnection(
+ "http://www.foo.bar/post", request_type::kPost, {}, "", "", nullptr);
+ EXPECT_NE(nullptr, connection.get());
+
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ connection.reset();
+}
+
+TEST_F(HttpCurlTransportTest, RequestPatch) {
+ InSequence seq;
+ EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_));
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_URL, "http://www.foo.bar/patch"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_POST, 1))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_POSTFIELDS, nullptr))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptStr(handle_, CURLOPT_CUSTOMREQUEST,
+ request_type::kPatch))
+ .WillOnce(Return(CURLE_OK));
+ auto connection = transport_->CreateConnection(
+ "http://www.foo.bar/patch", request_type::kPatch, {}, "", "", nullptr);
+ EXPECT_NE(nullptr, connection.get());
+
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ connection.reset();
+}
+
+TEST_F(HttpCurlTransportTest, CurlFailure) {
+ InSequence seq;
+ EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_));
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
+ .WillOnce(Return(CURLE_OUT_OF_MEMORY));
+ EXPECT_CALL(*curl_api_, EasyStrError(CURLE_OUT_OF_MEMORY))
+ .WillOnce(Return("Out of Memory"));
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ ErrorPtr error;
+ auto connection = transport_->CreateConnection(
+ "http://foo.bar/get", request_type::kGet, {}, "", "", &error);
+
+ EXPECT_EQ(nullptr, connection.get());
+ EXPECT_EQ("curl_error", error->GetDomain());
+ EXPECT_EQ(std::to_string(CURLE_OUT_OF_MEMORY), error->GetCode());
+ EXPECT_EQ("Out of Memory", error->GetMessage());
+}
+
+} // namespace curl
+} // namespace http
+} // namespace chromeos
diff --git a/chromeos/http/http_transport_fake.cc b/chromeos/http/http_transport_fake.cc
index dfe162d..aa8e429 100644
--- a/chromeos/http/http_transport_fake.cc
+++ b/chromeos/http/http_transport_fake.cc
@@ -32,13 +32,18 @@
}
std::shared_ptr<http::Connection> Transport::CreateConnection(
- std::shared_ptr<http::Transport> transport,
const std::string& url,
const std::string& method,
const HeaderList& headers,
const std::string& user_agent,
const std::string& referer,
chromeos::ErrorPtr* error) {
+ std::shared_ptr<http::Connection> connection;
+ if (create_connection_error_) {
+ if (error)
+ *error = std::move(create_connection_error_);
+ return connection;
+ }
HeaderList headers_copy = headers;
if (!user_agent.empty()) {
headers_copy.push_back(std::make_pair(http::request_header::kUserAgent,
@@ -48,8 +53,8 @@
headers_copy.push_back(std::make_pair(http::request_header::kReferer,
referer));
}
- std::shared_ptr<http::Connection> connection =
- std::make_shared<http::fake::Connection>(url, method, transport);
+ connection =
+ std::make_shared<http::fake::Connection>(url, method, shared_from_this());
CHECK(connection) << "Unable to create Connection object";
if (!connection->SendHeaders(headers_copy, error))
connection.reset();
diff --git a/chromeos/http/http_transport_fake.h b/chromeos/http/http_transport_fake.h
index c278e2e..ee6022a 100644
--- a/chromeos/http/http_transport_fake.h
+++ b/chromeos/http/http_transport_fake.h
@@ -45,7 +45,8 @@
// any requests. So, ("http://localhost","*") will handle any request type
// on that URL and ("*","GET") will handle any GET requests.
// The lookup starts with the most specific data pair to the catch-all (*,*).
- void AddHandler(const std::string& url, const std::string& method,
+ void AddHandler(const std::string& url,
+ const std::string& method,
const HandlerCallback& handler);
// Simple version of AddHandler. AddSimpleReplyHandler just returns the
// specified text response of given MIME type.
@@ -63,9 +64,14 @@
int GetRequestCount() const { return request_count_; }
void ResetRequestCount() { request_count_ = 0; }
+ // For tests that wish to simulate critical transport errors, this method
+ // can be used to specify the error to be returned when creating a connection.
+ void SetCreateConnectionError(chromeos::ErrorPtr create_connection_error) {
+ create_connection_error_ = std::move(create_connection_error);
+ }
+
// Overrides from http::Transport.
std::shared_ptr<http::Connection> CreateConnection(
- std::shared_ptr<http::Transport> transport,
const std::string& url,
const std::string& method,
const HeaderList& headers,
@@ -82,10 +88,13 @@
private:
// A list of user-supplied request handlers.
- std::map<std::string, HandlerCallback> handlers_;
+ std::multimap<std::string, HandlerCallback> handlers_;
// Counter incremented each time a request is made.
int request_count_ = 0;
+ // Fake error to be returned from CreateConnection method.
+ chromeos::ErrorPtr create_connection_error_;
+
DISALLOW_COPY_AND_ASSIGN(Transport);
};
@@ -106,7 +115,7 @@
// Add/retrieve request/response HTTP headers.
void AddHeaders(const HeaderList& headers);
std::string GetHeader(const std::string& header_name) const;
- const std::map<std::string, std::string>& GetHeaders() const {
+ const std::multimap<std::string, std::string>& GetHeaders() const {
return headers_;
}
@@ -114,7 +123,7 @@
// Data buffer.
std::vector<uint8_t> data_;
// Header map.
- std::map<std::string, std::string> headers_;
+ std::multimap<std::string, std::string> headers_;
private:
DISALLOW_COPY_AND_ASSIGN(ServerRequestResponseBase);
diff --git a/chromeos/http/http_utils_unittest.cc b/chromeos/http/http_utils_unittest.cc
index 6ac2cb1..590bd02 100644
--- a/chromeos/http/http_utils_unittest.cc
+++ b/chromeos/http/http_utils_unittest.cc
@@ -43,7 +43,7 @@
base::Bind(EchoDataHandler));
// Test binary data round-tripping.
- std::vector<unsigned char> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F};
+ std::vector<uint8_t> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F};
auto response = http::SendRequestAndBlock(
request_type::kPost, kEchoUrl, custom_data.data(), custom_data.size(),
chromeos::mime::application::kOctet_stream, {}, transport, nullptr);
@@ -54,12 +54,35 @@
EXPECT_EQ(custom_data, response->GetData());
}
+TEST(HttpUtils, SendRequestAsync_BinaryData) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kEchoUrl, request_type::kPost,
+ base::Bind(EchoDataHandler));
+
+ // Test binary data round-tripping.
+ std::vector<uint8_t> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F};
+ auto success_callback = [&custom_data](scoped_ptr<http::Response> response) {
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(chromeos::mime::application::kOctet_stream,
+ response->GetContentType());
+ EXPECT_EQ(custom_data.size(), response->GetData().size());
+ EXPECT_EQ(custom_data, response->GetData());
+ };
+ auto error_callback = [](const Error* error) {
+ FAIL() << "This callback shouldn't have been called";
+ };
+ http::SendRequest(
+ request_type::kPost, kEchoUrl, custom_data.data(), custom_data.size(),
+ chromeos::mime::application::kOctet_stream, {}, transport,
+ base::Bind(success_callback), base::Bind(error_callback));
+}
+
TEST(HttpUtils, SendRequest_Post) {
std::shared_ptr<fake::Transport> transport(new fake::Transport);
transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
// Test binary data round-tripping.
- std::vector<unsigned char> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F};
+ std::vector<uint8_t> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F};
// Check the correct HTTP method used.
auto response = http::SendRequestAndBlock(
@@ -98,13 +121,27 @@
TEST(HttpUtils, SendRequest_NotFound) {
std::shared_ptr<fake::Transport> transport(new fake::Transport);
// Test failed response (URL not found).
- auto response = http::SendRequestAndBlock(
- request_type::kGet, "http://blah.com", nullptr, 0, std::string{}, {},
- transport, nullptr);
+ auto response = http::SendRequestWithNoDataAndBlock(
+ request_type::kGet, "http://blah.com", {}, transport, nullptr);
EXPECT_FALSE(response->IsSuccessful());
EXPECT_EQ(status_code::NotFound, response->GetStatusCode());
}
+TEST(HttpUtils, SendRequestAsync_NotFound) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ // Test failed response (URL not found).
+ auto success_callback = [](scoped_ptr<http::Response> response) {
+ EXPECT_FALSE(response->IsSuccessful());
+ EXPECT_EQ(status_code::NotFound, response->GetStatusCode());
+ };
+ auto error_callback = [](const Error* error) {
+ FAIL() << "This callback shouldn't have been called";
+ };
+ http::SendRequestWithNoData(
+ request_type::kGet, "http://blah.com", {}, transport,
+ base::Bind(success_callback), base::Bind(error_callback));
+}
+
TEST(HttpUtils, SendRequest_Headers) {
std::shared_ptr<fake::Transport> transport(new fake::Transport);
@@ -217,7 +254,7 @@
transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(Handler));
/// Fill the data buffer with bytes from 0x00 to 0xFF.
- std::vector<unsigned char> data(256);
+ std::vector<uint8_t> data(256);
std::iota(data.begin(), data.end(), 0);
auto response = http::PostBinaryAndBlock(
@@ -367,5 +404,39 @@
EXPECT_EQ(status_code::NotFound, code);
}
+TEST(HttpUtils, SendRequest_Failure) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
+ ErrorPtr error;
+ Error::AddTo(&error, FROM_HERE, "test_domain", "test_code", "Test message");
+ transport->SetCreateConnectionError(std::move(error));
+ error.reset(); // Just to make sure it is empty...
+ auto response = http::SendRequestWithNoDataAndBlock(
+ request_type::kGet, "http://blah.com", {}, transport, &error);
+ EXPECT_EQ(nullptr, response.get());
+ EXPECT_EQ("test_domain", error->GetDomain());
+ EXPECT_EQ("test_code", error->GetCode());
+ EXPECT_EQ("Test message", error->GetMessage());
+}
+
+TEST(HttpUtils, SendRequestAsync_Failure) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
+ ErrorPtr error;
+ Error::AddTo(&error, FROM_HERE, "test_domain", "test_code", "Test message");
+ transport->SetCreateConnectionError(std::move(error));
+ auto success_callback = [](scoped_ptr<http::Response> response) {
+ FAIL() << "This callback shouldn't have been called";
+ };
+ auto error_callback = [](const Error* error) {
+ EXPECT_EQ("test_domain", error->GetDomain());
+ EXPECT_EQ("test_code", error->GetCode());
+ EXPECT_EQ("Test message", error->GetMessage());
+ };
+ http::SendRequestWithNoData(
+ request_type::kGet, "http://blah.com", {}, transport,
+ base::Bind(success_callback), base::Bind(error_callback));
+}
+
} // namespace http
} // namespace chromeos
diff --git a/chromeos/http/mock_connection.h b/chromeos/http/mock_connection.h
new file mode 100644
index 0000000..e2c5adc
--- /dev/null
+++ b/chromeos/http/mock_connection.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBCHROMEOS_CHROMEOS_HTTP_MOCK_CONNECTION_H_
+#define LIBCHROMEOS_CHROMEOS_HTTP_MOCK_CONNECTION_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <chromeos/http/http_connection.h>
+#include <gmock/gmock.h>
+
+namespace chromeos {
+namespace http {
+
+class MockConnection : public Connection {
+ public:
+ using Connection::Connection;
+
+ MOCK_METHOD2(SendHeaders, bool(const HeaderList&, ErrorPtr*));
+ MOCK_METHOD2(MockSetRequestData, bool(DataReaderInterface*, ErrorPtr*));
+ MOCK_METHOD1(FinishRequest, bool(ErrorPtr*));
+ MOCK_METHOD2(FinishRequestAsync,
+ void(const SuccessCallback&, const ErrorCallback&));
+ MOCK_CONST_METHOD0(GetResponseStatusCode, int());
+ MOCK_CONST_METHOD0(GetResponseStatusText, std::string());
+ MOCK_CONST_METHOD0(GetProtocolVersion, std::string());
+ MOCK_CONST_METHOD1(GetResponseHeader, std::string(const std::string&));
+ MOCK_CONST_METHOD0(GetResponseDataSize, uint64_t());
+ MOCK_METHOD4(ReadResponseData, bool(void*, size_t, size_t*, ErrorPtr*));
+
+ private:
+ bool SetRequestData(std::unique_ptr<DataReaderInterface> data_reader,
+ chromeos::ErrorPtr* error) override {
+ return MockSetRequestData(data_reader.get(), error);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(MockConnection);
+};
+
+} // namespace http
+} // namespace chromeos
+
+#endif // LIBCHROMEOS_CHROMEOS_HTTP_MOCK_CONNECTION_H_
diff --git a/chromeos/http/mock_curl_api.h b/chromeos/http/mock_curl_api.h
new file mode 100644
index 0000000..10005c6
--- /dev/null
+++ b/chromeos/http/mock_curl_api.h
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBCHROMEOS_CHROMEOS_HTTP_MOCK_CURL_API_H_
+#define LIBCHROMEOS_CHROMEOS_HTTP_MOCK_CURL_API_H_
+
+#include <string>
+
+#include <chromeos/http/curl_api.h>
+#include <gmock/gmock.h>
+
+namespace chromeos {
+namespace http {
+
+class MockCurlInterface : public CurlInterface {
+ public:
+ MockCurlInterface() = default;
+
+ MOCK_METHOD0(EasyInit, CURL*());
+ MOCK_METHOD1(EasyCleanup, void(CURL*));
+ MOCK_METHOD3(EasySetOptInt, CURLcode(CURL*, CURLoption, int));
+ MOCK_METHOD3(EasySetOptStr, CURLcode(CURL*, CURLoption, const std::string&));
+ MOCK_METHOD3(EasySetOptPtr, CURLcode(CURL*, CURLoption, void*));
+ MOCK_METHOD3(EasySetOptCallback, CURLcode(CURL*, CURLoption, intptr_t));
+ MOCK_METHOD3(EasySetOptOffT, CURLcode(CURL*, CURLoption, curl_off_t));
+ MOCK_METHOD1(EasyPerform, CURLcode(CURL*));
+ MOCK_CONST_METHOD3(EasyGetInfoInt, CURLcode(CURL*, CURLINFO, int*));
+ MOCK_CONST_METHOD3(EasyGetInfoDbl, CURLcode(CURL*, CURLINFO, double*));
+ MOCK_CONST_METHOD3(EasyGetInfoStr, CURLcode(CURL*, CURLINFO, std::string*));
+ MOCK_CONST_METHOD1(EasyStrError, std::string(CURLcode));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCurlInterface);
+};
+
+} // namespace http
+} // namespace chromeos
+
+#endif // LIBCHROMEOS_CHROMEOS_HTTP_MOCK_CURL_API_H_
diff --git a/chromeos/http/mock_transport.h b/chromeos/http/mock_transport.h
new file mode 100644
index 0000000..e48883a
--- /dev/null
+++ b/chromeos/http/mock_transport.h
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBCHROMEOS_CHROMEOS_HTTP_MOCK_TRANSPORT_H_
+#define LIBCHROMEOS_CHROMEOS_HTTP_MOCK_TRANSPORT_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <chromeos/http/http_transport.h>
+#include <gmock/gmock.h>
+
+namespace chromeos {
+namespace http {
+
+class MockTransport : public Transport {
+ public:
+ MockTransport() = default;
+
+ MOCK_METHOD6(CreateConnection,
+ std::shared_ptr<Connection>(const std::string&,
+ const std::string&,
+ const HeaderList&,
+ const std::string&,
+ const std::string&,
+ chromeos::ErrorPtr*));
+ MOCK_METHOD2(RunCallbackAsync,
+ void(const tracked_objects::Location&, const base::Closure&));
+ MOCK_METHOD3(StartAsyncTransfer,
+ void(Connection*, const SuccessCallback&, const ErrorCallback&));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockTransport);
+};
+
+} // namespace http
+} // namespace chromeos
+
+#endif // LIBCHROMEOS_CHROMEOS_HTTP_MOCK_TRANSPORT_H_
diff --git a/libchromeos.gypi b/libchromeos.gypi
index dbd5509..44f0e9c 100644
--- a/libchromeos.gypi
+++ b/libchromeos.gypi
@@ -102,6 +102,7 @@
},
},
'sources': [
+ 'chromeos/http/curl_api.cc',
'chromeos/http/data_reader.cc',
'chromeos/http/http_connection_curl.cc',
'chromeos/http/http_form_data.cc',
@@ -269,7 +270,10 @@
'chromeos/flag_helper_unittest.cc',
'chromeos/glib/object_unittest.cc',
'chromeos/http/data_reader_unittest.cc',
+ 'chromeos/http/http_connection_curl_unittest.cc',
'chromeos/http/http_form_data_unittest.cc',
+ 'chromeos/http/http_request_unittest.cc',
+ 'chromeos/http/http_transport_curl_unittest.cc',
'chromeos/http/http_utils_unittest.cc',
'chromeos/key_value_store_unittest.cc',
'chromeos/map_utils_unittest.cc',