1//
2// Copyright (C) 2019 The Android Open Source Project
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//      http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17#include "update_engine/libcurl_http_fetcher.h"
18
19#include <string>
20
21#include <brillo/message_loops/fake_message_loop.h>
22#include <gmock/gmock.h>
23#include <gtest/gtest.h>
24
25#include "update_engine/common/fake_hardware.h"
26#include "update_engine/common/mock_proxy_resolver.h"
27#include "update_engine/mock_libcurl_http_fetcher.h"
28
29using std::string;
30
31namespace chromeos_update_engine {
32
33namespace {
34constexpr char kHeaderName[] = "X-Goog-Test-Header";
35}
36
37class LibcurlHttpFetcherTest : public ::testing::Test {
38 protected:
39  void SetUp() override {
40    loop_.SetAsCurrent();
41    fake_hardware_.SetIsOfficialBuild(true);
42    fake_hardware_.SetIsOOBEEnabled(false);
43  }
44
45  brillo::FakeMessageLoop loop_{nullptr};
46  FakeHardware fake_hardware_;
47  MockLibcurlHttpFetcher libcurl_fetcher_{nullptr, &fake_hardware_};
48  UnresolvedHostStateMachine state_machine_;
49};
50
51TEST_F(LibcurlHttpFetcherTest, GetEmptyHeaderValueTest) {
52  const string header_value = "";
53  string actual_header_value;
54  libcurl_fetcher_.SetHeader(kHeaderName, header_value);
55  EXPECT_TRUE(libcurl_fetcher_.GetHeader(kHeaderName, &actual_header_value));
56  EXPECT_EQ("", actual_header_value);
57}
58
59TEST_F(LibcurlHttpFetcherTest, GetHeaderTest) {
60  const string header_value = "This-is-value 123";
61  string actual_header_value;
62  libcurl_fetcher_.SetHeader(kHeaderName, header_value);
63  EXPECT_TRUE(libcurl_fetcher_.GetHeader(kHeaderName, &actual_header_value));
64  EXPECT_EQ(header_value, actual_header_value);
65}
66
67TEST_F(LibcurlHttpFetcherTest, GetNonExistentHeaderValueTest) {
68  string actual_header_value;
69  // Skip |SetHeaader()| call.
70  EXPECT_FALSE(libcurl_fetcher_.GetHeader(kHeaderName, &actual_header_value));
71  // Even after a failed |GetHeaderValue()|, enforce that the passed pointer to
72  // modifiable string was cleared to be empty.
73  EXPECT_EQ("", actual_header_value);
74}
75
76TEST_F(LibcurlHttpFetcherTest, GetHeaderEdgeCaseTest) {
77  const string header_value = "\a\b\t\v\f\r\\ edge:-case: \a\b\t\v\f\r\\";
78  string actual_header_value;
79  libcurl_fetcher_.SetHeader(kHeaderName, header_value);
80  EXPECT_TRUE(libcurl_fetcher_.GetHeader(kHeaderName, &actual_header_value));
81  EXPECT_EQ(header_value, actual_header_value);
82}
83
84TEST_F(LibcurlHttpFetcherTest, InvalidURLTest) {
85  int no_network_max_retries = 1;
86  libcurl_fetcher_.set_no_network_max_retries(no_network_max_retries);
87
88  libcurl_fetcher_.BeginTransfer("not-a-URL");
89  while (loop_.PendingTasks()) {
90    loop_.RunOnce(true);
91  }
92
93  EXPECT_EQ(libcurl_fetcher_.get_no_network_max_retries(),
94            no_network_max_retries);
95}
96
97TEST_F(LibcurlHttpFetcherTest, CouldNotResolveHostTest) {
98  int no_network_max_retries = 1;
99  libcurl_fetcher_.set_no_network_max_retries(no_network_max_retries);
100
101  libcurl_fetcher_.BeginTransfer("https://An-uNres0lvable-uRl.invalid");
102
103  // It's slower on Android that libcurl handle may not finish within 1 cycle.
104  // Will need to wait for more cycles until it finishes. Original test didn't
105  // correctly handle when we need to re-watch libcurl fds.
106  while (loop_.PendingTasks() &&
107         libcurl_fetcher_.GetAuxiliaryErrorCode() == ErrorCode::kSuccess) {
108    loop_.RunOnce(true);
109  }
110
111  EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
112            ErrorCode::kUnresolvedHostError);
113
114  while (loop_.PendingTasks()) {
115    loop_.RunOnce(true);
116  }
117  // The auxilary error code should've have been changed.
118  EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
119            ErrorCode::kUnresolvedHostError);
120
121  // If libcurl fails to resolve the name, we call res_init() to reload
122  // resolv.conf and retry exactly once more. See crbug.com/982813 for details.
123  EXPECT_EQ(libcurl_fetcher_.get_no_network_max_retries(),
124            no_network_max_retries + 1);
125}
126
127TEST_F(LibcurlHttpFetcherTest, HostResolvedTest) {
128  int no_network_max_retries = 2;
129  libcurl_fetcher_.set_no_network_max_retries(no_network_max_retries);
130
131  // This test actually sends request to internet but according to
132  // https://tools.ietf.org/html/rfc2606#section-2, .invalid domain names are
133  // reserved and sure to be invalid. Ideally we should mock libcurl or
134  // reorganize LibcurlHttpFetcher so the part that sends request can be mocked
135  // easily.
136  // TODO(xiaochu) Refactor LibcurlHttpFetcher (and its relates) so it's
137  // easier to mock the part that depends on internet connectivity.
138  libcurl_fetcher_.BeginTransfer("https://An-uNres0lvable-uRl.invalid");
139
140  // It's slower on Android that libcurl handle may not finish within 1 cycle.
141  // Will need to wait for more cycles until it finishes. Original test didn't
142  // correctly handle when we need to re-watch libcurl fds.
143  while (loop_.PendingTasks() &&
144         libcurl_fetcher_.GetAuxiliaryErrorCode() == ErrorCode::kSuccess) {
145    loop_.RunOnce(true);
146  }
147
148  EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
149            ErrorCode::kUnresolvedHostError);
150
151  // The second time, it will resolve, with error code 200 but we set the
152  // download size be smaller than the transfer size so it will retry again.
153  EXPECT_CALL(libcurl_fetcher_, GetHttpResponseCode())
154      .WillOnce(testing::Invoke(
155          [this]() { libcurl_fetcher_.http_response_code_ = 200; }))
156      .WillRepeatedly(testing::Invoke(
157          [this]() { libcurl_fetcher_.http_response_code_ = 0; }));
158  libcurl_fetcher_.transfer_size_ = 10;
159
160  // It's slower on Android that libcurl handle may not finish within 1 cycle.
161  // Will need to wait for more cycles until it finishes. Original test didn't
162  // correctly handle when we need to re-watch libcurl fds.
163  while (loop_.PendingTasks() && libcurl_fetcher_.GetAuxiliaryErrorCode() ==
164                                     ErrorCode::kUnresolvedHostError) {
165    loop_.RunOnce(true);
166  }
167
168  EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
169            ErrorCode::kUnresolvedHostRecovered);
170
171  while (loop_.PendingTasks()) {
172    loop_.RunOnce(true);
173  }
174  // The auxilary error code should not have been changed.
175  EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
176            ErrorCode::kUnresolvedHostRecovered);
177
178  // If libcurl fails to resolve the name, we call res_init() to reload
179  // resolv.conf and retry exactly once more. See crbug.com/982813 for details.
180  EXPECT_EQ(libcurl_fetcher_.get_no_network_max_retries(),
181            no_network_max_retries + 1);
182}
183
184TEST_F(LibcurlHttpFetcherTest, HttpFetcherStateMachineRetryFailedTest) {
185  state_machine_.UpdateState(true);
186  state_machine_.UpdateState(true);
187  EXPECT_EQ(state_machine_.GetState(),
188            UnresolvedHostStateMachine::State::kNotRetry);
189}
190
191TEST_F(LibcurlHttpFetcherTest, HttpFetcherStateMachineRetrySucceedTest) {
192  state_machine_.UpdateState(true);
193  state_machine_.UpdateState(false);
194  EXPECT_EQ(state_machine_.GetState(),
195            UnresolvedHostStateMachine::State::kRetriedSuccess);
196}
197
198TEST_F(LibcurlHttpFetcherTest, HttpFetcherStateMachineNoRetryTest) {
199  state_machine_.UpdateState(false);
200  state_machine_.UpdateState(false);
201  EXPECT_EQ(state_machine_.GetState(),
202            UnresolvedHostStateMachine::State::kInit);
203}
204
205}  // namespace chromeos_update_engine
206