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_request.h> 6 7#include <string> 8 9#include <base/callback.h> 10#include <brillo/bind_lambda.h> 11#include <brillo/http/mock_connection.h> 12#include <brillo/http/mock_transport.h> 13#include <brillo/mime_utils.h> 14#include <brillo/streams/mock_stream.h> 15#include <gmock/gmock.h> 16#include <gtest/gtest.h> 17 18using testing::DoAll; 19using testing::Invoke; 20using testing::Return; 21using testing::SetArgPointee; 22using testing::Unused; 23using testing::WithArg; 24using testing::_; 25 26namespace brillo { 27namespace http { 28 29MATCHER_P(ContainsStringData, str, "") { 30 if (arg->GetSize() != str.size()) 31 return false; 32 33 std::string data; 34 char buf[100]; 35 size_t read = 0; 36 while (arg->ReadBlocking(buf, sizeof(buf), &read, nullptr) && read > 0) { 37 data.append(buf, read); 38 } 39 return data == str; 40} 41 42class HttpRequestTest : public testing::Test { 43 public: 44 void SetUp() override { 45 transport_ = std::make_shared<MockTransport>(); 46 connection_ = std::make_shared<MockConnection>(transport_); 47 } 48 49 void TearDown() override { 50 // Having shared pointers to mock objects (some of methods in these tests 51 // return shared pointers to connection and transport) could cause the 52 // test expectations to hold on to the mock object without releasing them 53 // at the end of a test, causing Mock's object leak detection to erroneously 54 // detect mock object "leaks". Verify and clear the expectations manually 55 // and explicitly to ensure the shared pointer refcounters are not 56 // preventing the mocks to be destroyed at the end of each test. 57 testing::Mock::VerifyAndClearExpectations(connection_.get()); 58 connection_.reset(); 59 testing::Mock::VerifyAndClearExpectations(transport_.get()); 60 transport_.reset(); 61 } 62 63 protected: 64 std::shared_ptr<MockTransport> transport_; 65 std::shared_ptr<MockConnection> connection_; 66}; 67 68TEST_F(HttpRequestTest, Defaults) { 69 Request request{"http://www.foo.bar", request_type::kPost, transport_}; 70 EXPECT_TRUE(request.GetContentType().empty()); 71 EXPECT_TRUE(request.GetReferer().empty()); 72 EXPECT_TRUE(request.GetUserAgent().empty()); 73 EXPECT_EQ("*/*", request.GetAccept()); 74 EXPECT_EQ("http://www.foo.bar", request.GetRequestURL()); 75 EXPECT_EQ(request_type::kPost, request.GetRequestMethod()); 76 77 Request request2{"http://www.foo.bar/baz", request_type::kGet, transport_}; 78 EXPECT_EQ("http://www.foo.bar/baz", request2.GetRequestURL()); 79 EXPECT_EQ(request_type::kGet, request2.GetRequestMethod()); 80} 81 82TEST_F(HttpRequestTest, ContentType) { 83 Request request{"http://www.foo.bar", request_type::kPost, transport_}; 84 request.SetContentType(mime::image::kJpeg); 85 EXPECT_EQ(mime::image::kJpeg, request.GetContentType()); 86} 87 88TEST_F(HttpRequestTest, Referer) { 89 Request request{"http://www.foo.bar", request_type::kPost, transport_}; 90 request.SetReferer("http://www.foo.bar/baz"); 91 EXPECT_EQ("http://www.foo.bar/baz", request.GetReferer()); 92} 93 94TEST_F(HttpRequestTest, UserAgent) { 95 Request request{"http://www.foo.bar", request_type::kPost, transport_}; 96 request.SetUserAgent("FooBar Browser"); 97 EXPECT_EQ("FooBar Browser", request.GetUserAgent()); 98} 99 100TEST_F(HttpRequestTest, Accept) { 101 Request request{"http://www.foo.bar", request_type::kPost, transport_}; 102 request.SetAccept("text/*, text/html, text/html;level=1, */*"); 103 EXPECT_EQ("text/*, text/html, text/html;level=1, */*", request.GetAccept()); 104} 105 106TEST_F(HttpRequestTest, GetResponseAndBlock) { 107 Request request{"http://www.foo.bar", request_type::kPost, transport_}; 108 request.SetUserAgent("FooBar Browser"); 109 request.SetReferer("http://www.foo.bar/baz"); 110 request.SetAccept("text/*, text/html, text/html;level=1, */*"); 111 request.AddHeader(request_header::kAcceptEncoding, "compress, gzip"); 112 request.AddHeaders({ 113 {request_header::kAcceptLanguage, "da, en-gb;q=0.8, en;q=0.7"}, 114 {request_header::kConnection, "close"}, 115 }); 116 request.AddRange(-10); 117 request.AddRange(100, 200); 118 request.AddRange(300); 119 std::string req_body{"Foo bar baz"}; 120 request.AddHeader(request_header::kContentType, mime::text::kPlain); 121 122 EXPECT_CALL(*transport_, CreateConnection( 123 "http://www.foo.bar", 124 request_type::kPost, 125 HeaderList{ 126 {request_header::kAcceptEncoding, "compress, gzip"}, 127 {request_header::kAcceptLanguage, "da, en-gb;q=0.8, en;q=0.7"}, 128 {request_header::kConnection, "close"}, 129 {request_header::kContentType, mime::text::kPlain}, 130 {request_header::kRange, "bytes=-10,100-200,300-"}, 131 {request_header::kAccept, "text/*, text/html, text/html;level=1, */*"}, 132 }, 133 "FooBar Browser", 134 "http://www.foo.bar/baz", 135 nullptr)).WillOnce(Return(connection_)); 136 137 EXPECT_CALL(*connection_, MockSetRequestData(ContainsStringData(req_body), _)) 138 .WillOnce(Return(true)); 139 140 EXPECT_TRUE( 141 request.AddRequestBody(req_body.data(), req_body.size(), nullptr)); 142 143 EXPECT_CALL(*connection_, FinishRequest(_)).WillOnce(Return(true)); 144 auto resp = request.GetResponseAndBlock(nullptr); 145 EXPECT_NE(nullptr, resp.get()); 146} 147 148TEST_F(HttpRequestTest, GetResponse) { 149 Request request{"http://foo.bar", request_type::kGet, transport_}; 150 151 std::string resp_data{"FooBar response body"}; 152 auto read_data = 153 [&resp_data](void* buffer, Unused, size_t* read, Unused) -> bool { 154 memcpy(buffer, resp_data.data(), resp_data.size()); 155 *read = resp_data.size(); 156 return true; 157 }; 158 159 auto success_callback = [](decltype(this) test, 160 const std::string& resp_data, 161 RequestID request_id, 162 std::unique_ptr<Response> resp) { 163 EXPECT_EQ(23, request_id); 164 EXPECT_CALL(*test->connection_, GetResponseStatusCode()) 165 .WillOnce(Return(status_code::Partial)); 166 EXPECT_EQ(status_code::Partial, resp->GetStatusCode()); 167 168 EXPECT_CALL(*test->connection_, GetResponseStatusText()) 169 .WillOnce(Return("Partial completion")); 170 EXPECT_EQ("Partial completion", resp->GetStatusText()); 171 172 EXPECT_CALL(*test->connection_, 173 GetResponseHeader(response_header::kContentType)) 174 .WillOnce(Return(mime::text::kHtml)); 175 EXPECT_EQ(mime::text::kHtml, resp->GetContentType()); 176 177 EXPECT_EQ(resp_data, resp->ExtractDataAsString()); 178 }; 179 180 auto finish_request_async = 181 [this, &read_data](const SuccessCallback& success_callback) { 182 std::unique_ptr<MockStream> mock_stream{new MockStream}; 183 EXPECT_CALL(*mock_stream, ReadBlocking(_, _, _, _)) 184 .WillOnce(Invoke(read_data)) 185 .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); 186 187 EXPECT_CALL(*connection_, MockExtractDataStream(_)) 188 .WillOnce(Return(mock_stream.release())); 189 std::unique_ptr<Response> resp{new Response{connection_}}; 190 success_callback.Run(23, std::move(resp)); 191 }; 192 193 EXPECT_CALL( 194 *transport_, 195 CreateConnection("http://foo.bar", request_type::kGet, _, "", "", _)) 196 .WillOnce(Return(connection_)); 197 198 EXPECT_CALL(*connection_, FinishRequestAsync(_, _)) 199 .WillOnce(DoAll(WithArg<0>(Invoke(finish_request_async)), Return(23))); 200 201 EXPECT_EQ( 202 23, 203 request.GetResponse( 204 base::Bind(success_callback, base::Unretained(this), resp_data), {})); 205} 206 207} // namespace http 208} // namespace brillo 209