| // Copyright 2018 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 "shill/http_request.h" |
| |
| #include <netinet/in.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/stringprintf.h> |
| #include <brillo/http/mock_connection.h> |
| #include <brillo/http/mock_transport.h> |
| #include <brillo/mime_utils.h> |
| #include <brillo/streams/mock_stream.h> |
| #include <curl/curl.h> |
| #include <gtest/gtest.h> |
| |
| #include "shill/http_url.h" |
| #include "shill/mock_control.h" |
| #include "shill/mock_device_info.h" |
| #include "shill/mock_dns_client.h" |
| #include "shill/mock_manager.h" |
| #include "shill/net/ip_address.h" |
| #include "shill/net/mock_sockets.h" |
| #include "shill/test_event_dispatcher.h" |
| |
| using base::Bind; |
| using base::Callback; |
| using base::NumberToString; |
| using base::Unretained; |
| using std::string; |
| using std::vector; |
| using ::testing::_; |
| using ::testing::AtLeast; |
| using ::testing::DoAll; |
| using ::testing::Invoke; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using ::testing::ReturnRef; |
| using ::testing::SetArgPointee; |
| using ::testing::StrEq; |
| using ::testing::StrictMock; |
| using ::testing::Test; |
| using ::testing::Unused; |
| using ::testing::WithArg; |
| |
| namespace shill { |
| |
| namespace { |
| const char kTextSiteName[] = "www.chromium.org"; |
| const char kTextURL[] = "http://www.chromium.org/path/to/resource"; |
| const char kNumericURL[] = "http://10.1.1.1"; |
| const char kInterfaceName[] = "int0"; |
| const char kDNSServer0[] = "8.8.8.8"; |
| const char kDNSServer1[] = "8.8.4.4"; |
| const char* const kDNSServers[] = {kDNSServer0, kDNSServer1}; |
| const char kServerAddress[] = "10.1.1.1"; |
| } // namespace |
| |
| MATCHER_P(IsIPAddress, address, "") { |
| IPAddress ip_address(IPAddress::kFamilyIPv4); |
| EXPECT_TRUE(ip_address.SetAddressFromString(address)); |
| return ip_address.Equals(arg); |
| } |
| |
| MATCHER_P(ByteStringMatches, byte_string, "") { |
| return byte_string.Equals(arg); |
| } |
| |
| MATCHER_P(CallbackEq, callback, "") { |
| return arg.Equals(callback); |
| } |
| |
| class HttpRequestTest : public Test { |
| public: |
| HttpRequestTest() |
| : interface_name_(kInterfaceName), |
| dns_list_(kDNSServers, kDNSServers + 2), |
| dns_client_(new StrictMock<MockDnsClient>()), |
| transport_(std::make_shared<brillo::http::MockTransport>()), |
| brillo_connection_( |
| std::make_shared<brillo::http::MockConnection>(transport_)), |
| manager_(&control_, &dispatcher_, nullptr), |
| device_info_(new NiceMock<MockDeviceInfo>(&manager_)) {} |
| |
| protected: |
| class CallbackTarget { |
| public: |
| CallbackTarget() |
| : request_success_callback_(Bind( |
| &CallbackTarget::RequestSuccessCallTarget, Unretained(this))), |
| request_error_callback_(Bind(&CallbackTarget::RequestErrorCallTarget, |
| Unretained(this))) {} |
| |
| MOCK_METHOD(void, |
| RequestSuccessCallTarget, |
| (std::shared_ptr<brillo::http::Response>)); |
| MOCK_METHOD(void, RequestErrorCallTarget, (HttpRequest::Result)); |
| |
| const Callback<void(std::shared_ptr<brillo::http::Response>)>& |
| request_success_callback() const { |
| return request_success_callback_; |
| } |
| |
| const Callback<void(HttpRequest::Result)>& request_error_callback() const { |
| return request_error_callback_; |
| } |
| |
| private: |
| Callback<void(std::shared_ptr<brillo::http::Response>)> |
| request_success_callback_; |
| Callback<void(HttpRequest::Result)> request_error_callback_; |
| }; |
| |
| void SetUp() override { |
| request_.reset(new HttpRequest(&dispatcher_, interface_name_, |
| IPAddress::kFamilyIPv4, dns_list_, true)); |
| // Passes ownership. |
| request_->dns_client_.reset(dns_client_); |
| request_->transport_ = transport_; |
| } |
| void TearDown() override { |
| if (request_->is_running_) { |
| ExpectStop(); |
| |
| // Subtle: Make sure the finalization of the request happens while our |
| // expectations are still active. |
| request_.reset(); |
| } |
| testing::Mock::VerifyAndClearExpectations(brillo_connection_.get()); |
| brillo_connection_.reset(); |
| testing::Mock::VerifyAndClearExpectations(transport_.get()); |
| transport_.reset(); |
| } |
| HttpRequest* request() { return request_.get(); } |
| |
| // Expectations |
| void ExpectReset() { |
| EXPECT_TRUE(request_->request_error_callback_.is_null()); |
| EXPECT_TRUE(request_->request_success_callback_.is_null()); |
| EXPECT_FALSE(request_->dns_client_callback_.is_null()); |
| EXPECT_EQ(dns_client_, request_->dns_client_.get()); |
| EXPECT_TRUE(request_->server_hostname_.empty()); |
| EXPECT_FALSE(request_->is_running_); |
| } |
| void ExpectStop() { EXPECT_CALL(*dns_client_, Stop()).Times(AtLeast(1)); } |
| void ExpectDNSRequest(const string& host, bool return_value) { |
| EXPECT_CALL(*dns_client_, Start(StrEq(host), _)) |
| .WillOnce(Return(return_value)); |
| } |
| void ExpectRequestErrorCallback(HttpRequest::Result result) { |
| EXPECT_CALL(target_, RequestErrorCallTarget(result)); |
| } |
| void InvokeResultVerify(std::shared_ptr<brillo::http::Response> response) { |
| EXPECT_CALL(*brillo_connection_, GetResponseStatusCode()) |
| .WillOnce(Return(brillo::http::status_code::Partial)); |
| EXPECT_EQ(brillo::http::status_code::Partial, response->GetStatusCode()); |
| |
| EXPECT_CALL(*brillo_connection_, GetResponseStatusText()) |
| .WillOnce(Return("Partial completion")); |
| EXPECT_EQ("Partial completion", response->GetStatusText()); |
| |
| EXPECT_CALL(*brillo_connection_, |
| GetResponseHeader(brillo::http::response_header::kContentType)) |
| .WillOnce(Return(brillo::mime::text::kHtml)); |
| EXPECT_EQ(brillo::mime::text::kHtml, response->GetContentType()); |
| |
| EXPECT_EQ(expected_response_, response->ExtractDataAsString()); |
| } |
| void ExpectRequestSuccessCallback(const string& resp_data) { |
| expected_response_ = resp_data; |
| EXPECT_CALL(target_, RequestSuccessCallTarget(_)) |
| .WillOnce(Invoke(this, &HttpRequestTest::InvokeResultVerify)); |
| } |
| void GetDNSResultFailure(const string& error_msg) { |
| Error error(Error::kOperationFailed, error_msg); |
| IPAddress address(IPAddress::kFamilyUnknown); |
| request_->GetDNSResult(error, address); |
| } |
| void GetDNSResultSuccess(const IPAddress& address) { |
| Error error; |
| request_->GetDNSResult(error, address); |
| } |
| HttpRequest::Result StartRequest(const string& url) { |
| return request_->Start(url, {}, target_.request_success_callback(), |
| target_.request_error_callback()); |
| } |
| void ExpectCreateConnection(const string& url) { |
| EXPECT_CALL( |
| *transport_, |
| CreateConnection(url, brillo::http::request_type::kGet, _, "", "", _)) |
| .WillOnce(Return(brillo_connection_)); |
| } |
| void ExpectResolveHostToIp(const string& host, int port, const string& path) { |
| EXPECT_CALL(*transport_, ResolveHostToIp(host, port, path)); |
| } |
| void FinishRequestAsyncSuccess( |
| const brillo::http::SuccessCallback& success_callback) { |
| auto read_data = [this](void* buffer, Unused, size_t* read, |
| Unused) -> bool { |
| memcpy(buffer, resp_data_.data(), resp_data_.size()); |
| *read = resp_data_.size(); |
| return true; |
| }; |
| |
| auto mock_stream = std::make_unique<brillo::MockStream>(); |
| EXPECT_CALL(*mock_stream, ReadBlocking(_, _, _, _)) |
| .WillOnce(Invoke(read_data)) |
| .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); |
| |
| EXPECT_CALL(*brillo_connection_, MockExtractDataStream(_)) |
| .WillOnce(Return(mock_stream.release())); |
| auto resp = std::make_unique<brillo::http::Response>(brillo_connection_); |
| // request_id_ has not yet been set. Pass -1 to match default value. |
| success_callback.Run(-1, std::move(resp)); |
| } |
| void FinishRequestAsyncFail( |
| const brillo::http::ErrorCallback& error_callback) { |
| brillo::ErrorPtr error; |
| brillo::Error::AddTo(&error, FROM_HERE, "curl_easy_error", |
| NumberToString(CURLE_COULDNT_CONNECT), ""); |
| // request_id_ has not yet been set. Pass -1 to match default value. |
| error_callback.Run(-1, error.get()); |
| } |
| void ExpectFinishRequestAsyncSuccess(const string& resp_data) { |
| resp_data_ = resp_data; |
| EXPECT_CALL(*brillo_connection_, FinishRequestAsync(_, _)) |
| .WillOnce(DoAll(WithArg<0>(Invoke( |
| this, &HttpRequestTest::FinishRequestAsyncSuccess)), |
| Return(0))); |
| } |
| void ExpectFinishRequestAsyncFail() { |
| EXPECT_CALL(*brillo_connection_, FinishRequestAsync(_, _)) |
| .WillOnce(DoAll( |
| WithArg<1>(Invoke(this, &HttpRequestTest::FinishRequestAsyncFail)), |
| Return(0))); |
| } |
| |
| private: |
| const string interface_name_; |
| vector<string> dns_list_; |
| // Owned by the HttpRequest, but tracked here for EXPECT(). |
| StrictMock<MockDnsClient>* dns_client_; |
| std::shared_ptr<brillo::http::MockTransport> transport_; |
| std::shared_ptr<brillo::http::MockConnection> brillo_connection_; |
| EventDispatcherForTest dispatcher_; |
| MockControl control_; |
| MockManager manager_; |
| std::unique_ptr<MockDeviceInfo> device_info_; |
| std::unique_ptr<HttpRequest> request_; |
| StrictMock<CallbackTarget> target_; |
| string expected_response_; |
| string resp_data_; |
| }; |
| |
| TEST_F(HttpRequestTest, Constructor) { |
| ExpectReset(); |
| } |
| |
| TEST_F(HttpRequestTest, NumericRequestSuccess) { |
| const string resp{"Sample response."}; |
| ExpectRequestSuccessCallback(resp); |
| |
| ExpectCreateConnection(kNumericURL); |
| ExpectFinishRequestAsyncSuccess(resp); |
| |
| ExpectStop(); |
| EXPECT_EQ(HttpRequest::kResultInProgress, StartRequest(kNumericURL)); |
| ExpectReset(); |
| } |
| |
| TEST_F(HttpRequestTest, RequestFail) { |
| ExpectRequestErrorCallback(HttpRequest::kResultConnectionFailure); |
| |
| ExpectCreateConnection(kNumericURL); |
| ExpectFinishRequestAsyncFail(); |
| |
| ExpectStop(); |
| EXPECT_EQ(HttpRequest::kResultInProgress, StartRequest(kNumericURL)); |
| ExpectReset(); |
| } |
| |
| TEST_F(HttpRequestTest, TextRequestSuccess) { |
| ExpectDNSRequest(kTextSiteName, true); |
| |
| const string resp{"Sample response."}; |
| ExpectRequestSuccessCallback(resp); |
| HttpUrl url; |
| EXPECT_TRUE(url.ParseFromString(kTextURL)); |
| ExpectResolveHostToIp(url.host(), url.port(), kServerAddress); |
| ExpectCreateConnection(kTextURL); |
| ExpectFinishRequestAsyncSuccess(resp); |
| |
| ExpectStop(); |
| EXPECT_EQ(HttpRequest::kResultInProgress, StartRequest(kTextURL)); |
| IPAddress addr(IPAddress::kFamilyIPv4); |
| EXPECT_TRUE(addr.SetAddressFromString(kServerAddress)); |
| GetDNSResultSuccess(addr); |
| ExpectReset(); |
| } |
| |
| TEST_F(HttpRequestTest, FailDNSStart) { |
| ExpectDNSRequest(kTextSiteName, false); |
| ExpectStop(); |
| EXPECT_EQ(HttpRequest::kResultDNSFailure, StartRequest(kTextURL)); |
| ExpectReset(); |
| } |
| |
| TEST_F(HttpRequestTest, FailDNSFailure) { |
| ExpectDNSRequest(kTextSiteName, true); |
| EXPECT_EQ(HttpRequest::kResultInProgress, StartRequest(kTextURL)); |
| ExpectRequestErrorCallback(HttpRequest::kResultDNSFailure); |
| ExpectStop(); |
| GetDNSResultFailure(DnsClient::kErrorNoData); |
| ExpectReset(); |
| } |
| |
| TEST_F(HttpRequestTest, FailDNSTimeout) { |
| ExpectDNSRequest(kTextSiteName, true); |
| EXPECT_EQ(HttpRequest::kResultInProgress, StartRequest(kTextURL)); |
| ExpectRequestErrorCallback(HttpRequest::kResultDNSTimeout); |
| ExpectStop(); |
| const string error(DnsClient::kErrorTimedOut); |
| GetDNSResultFailure(error); |
| ExpectReset(); |
| } |
| |
| } // namespace shill |