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 =
160      [this, &resp_data](RequestID request_id, std::unique_ptr<Response> resp) {
161    EXPECT_EQ(23, request_id);
162    EXPECT_CALL(*connection_, GetResponseStatusCode())
163        .WillOnce(Return(status_code::Partial));
164    EXPECT_EQ(status_code::Partial, resp->GetStatusCode());
165
166    EXPECT_CALL(*connection_, GetResponseStatusText())
167        .WillOnce(Return("Partial completion"));
168    EXPECT_EQ("Partial completion", resp->GetStatusText());
169
170    EXPECT_CALL(*connection_, GetResponseHeader(response_header::kContentType))
171        .WillOnce(Return(mime::text::kHtml));
172    EXPECT_EQ(mime::text::kHtml, resp->GetContentType());
173
174    EXPECT_EQ(resp_data, resp->ExtractDataAsString());
175  };
176
177  auto finish_request_async =
178      [this, &read_data, &resp_data](const SuccessCallback& success_callback) {
179    std::unique_ptr<MockStream> mock_stream{new MockStream};
180    EXPECT_CALL(*mock_stream, ReadBlocking(_, _, _, _))
181        .WillOnce(Invoke(read_data))
182        .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
183
184    EXPECT_CALL(*connection_, MockExtractDataStream(_))
185      .WillOnce(Return(mock_stream.release()));
186    std::unique_ptr<Response> resp{new Response{connection_}};
187    success_callback.Run(23, std::move(resp));
188  };
189
190  EXPECT_CALL(
191      *transport_,
192      CreateConnection("http://foo.bar", request_type::kGet, _, "", "", _))
193      .WillOnce(Return(connection_));
194
195  EXPECT_CALL(*connection_, FinishRequestAsync(_, _))
196      .WillOnce(DoAll(WithArg<0>(Invoke(finish_request_async)), Return(23)));
197
198  EXPECT_EQ(23, request.GetResponse(base::Bind(success_callback), {}));
199}
200
201}  // namespace http
202}  // namespace brillo
203