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 
29 using std::string;
30 
31 namespace chromeos_update_engine {
32 
33 namespace {
34 constexpr char kHeaderName[] = "X-Goog-Test-Header";
35 }
36 
37 class LibcurlHttpFetcherTest : public ::testing::Test {
38  protected:
SetUp()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 
TEST_F(LibcurlHttpFetcherTest,GetEmptyHeaderValueTest)51 TEST_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 
TEST_F(LibcurlHttpFetcherTest,GetHeaderTest)59 TEST_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 
TEST_F(LibcurlHttpFetcherTest,GetNonExistentHeaderValueTest)67 TEST_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 
TEST_F(LibcurlHttpFetcherTest,GetHeaderEdgeCaseTest)76 TEST_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 
TEST_F(LibcurlHttpFetcherTest,InvalidURLTest)84 TEST_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 
TEST_F(LibcurlHttpFetcherTest,CouldNotResolveHostTest)97 TEST_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 #ifdef __ANDROID__
104   // It's slower on Android that libcurl handle may not finish within 1 cycle.
105   // Will need to wait for more cycles until it finishes. Original test didn't
106   // correctly handle when we need to re-watch libcurl fds.
107   while (loop_.PendingTasks() &&
108          libcurl_fetcher_.GetAuxiliaryErrorCode() == ErrorCode::kSuccess) {
109     loop_.RunOnce(true);
110   }
111 #else
112   // The first time it can't resolve.
113   loop_.RunOnce(true);
114 #endif
115   EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
116             ErrorCode::kUnresolvedHostError);
117 
118   while (loop_.PendingTasks()) {
119     loop_.RunOnce(true);
120   }
121   // The auxilary error code should've have been changed.
122   EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
123             ErrorCode::kUnresolvedHostError);
124 
125   // If libcurl fails to resolve the name, we call res_init() to reload
126   // resolv.conf and retry exactly once more. See crbug.com/982813 for details.
127   EXPECT_EQ(libcurl_fetcher_.get_no_network_max_retries(),
128             no_network_max_retries + 1);
129 }
130 
TEST_F(LibcurlHttpFetcherTest,HostResolvedTest)131 TEST_F(LibcurlHttpFetcherTest, HostResolvedTest) {
132   int no_network_max_retries = 2;
133   libcurl_fetcher_.set_no_network_max_retries(no_network_max_retries);
134 
135   // This test actually sends request to internet but according to
136   // https://tools.ietf.org/html/rfc2606#section-2, .invalid domain names are
137   // reserved and sure to be invalid. Ideally we should mock libcurl or
138   // reorganize LibcurlHttpFetcher so the part that sends request can be mocked
139   // easily.
140   // TODO(xiaochu) Refactor LibcurlHttpFetcher (and its relates) so it's
141   // easier to mock the part that depends on internet connectivity.
142   libcurl_fetcher_.BeginTransfer("https://An-uNres0lvable-uRl.invalid");
143 
144 #ifdef __ANDROID__
145   // It's slower on Android that libcurl handle may not finish within 1 cycle.
146   // Will need to wait for more cycles until it finishes. Original test didn't
147   // correctly handle when we need to re-watch libcurl fds.
148   while (loop_.PendingTasks() &&
149          libcurl_fetcher_.GetAuxiliaryErrorCode() == ErrorCode::kSuccess) {
150     loop_.RunOnce(true);
151   }
152 #else
153   // The first time it can't resolve.
154   loop_.RunOnce(true);
155 #endif
156   EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
157             ErrorCode::kUnresolvedHostError);
158 
159   // The second time, it will resolve, with error code 200 but we set the
160   // download size be smaller than the transfer size so it will retry again.
161   EXPECT_CALL(libcurl_fetcher_, GetHttpResponseCode())
162       .WillOnce(testing::Invoke(
163           [this]() { libcurl_fetcher_.http_response_code_ = 200; }))
164       .WillRepeatedly(testing::Invoke(
165           [this]() { libcurl_fetcher_.http_response_code_ = 0; }));
166   libcurl_fetcher_.transfer_size_ = 10;
167 
168 #ifdef __ANDROID__
169   // It's slower on Android that libcurl handle may not finish within 1 cycle.
170   // Will need to wait for more cycles until it finishes. Original test didn't
171   // correctly handle when we need to re-watch libcurl fds.
172   while (loop_.PendingTasks() && libcurl_fetcher_.GetAuxiliaryErrorCode() ==
173                                      ErrorCode::kUnresolvedHostError) {
174     loop_.RunOnce(true);
175   }
176 #else
177   // This time the host is resolved. But after that again we can't resolve
178   // anymore (See above).
179   loop_.RunOnce(true);
180 #endif
181   EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
182             ErrorCode::kUnresolvedHostRecovered);
183 
184   while (loop_.PendingTasks()) {
185     loop_.RunOnce(true);
186   }
187   // The auxilary error code should not have been changed.
188   EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
189             ErrorCode::kUnresolvedHostRecovered);
190 
191   // If libcurl fails to resolve the name, we call res_init() to reload
192   // resolv.conf and retry exactly once more. See crbug.com/982813 for details.
193   EXPECT_EQ(libcurl_fetcher_.get_no_network_max_retries(),
194             no_network_max_retries + 1);
195 }
196 
TEST_F(LibcurlHttpFetcherTest,HttpFetcherStateMachineRetryFailedTest)197 TEST_F(LibcurlHttpFetcherTest, HttpFetcherStateMachineRetryFailedTest) {
198   state_machine_.UpdateState(true);
199   state_machine_.UpdateState(true);
200   EXPECT_EQ(state_machine_.GetState(),
201             UnresolvedHostStateMachine::State::kNotRetry);
202 }
203 
TEST_F(LibcurlHttpFetcherTest,HttpFetcherStateMachineRetrySucceedTest)204 TEST_F(LibcurlHttpFetcherTest, HttpFetcherStateMachineRetrySucceedTest) {
205   state_machine_.UpdateState(true);
206   state_machine_.UpdateState(false);
207   EXPECT_EQ(state_machine_.GetState(),
208             UnresolvedHostStateMachine::State::kRetriedSuccess);
209 }
210 
TEST_F(LibcurlHttpFetcherTest,HttpFetcherStateMachineNoRetryTest)211 TEST_F(LibcurlHttpFetcherTest, HttpFetcherStateMachineNoRetryTest) {
212   state_machine_.UpdateState(false);
213   state_machine_.UpdateState(false);
214   EXPECT_EQ(state_machine_.GetState(),
215             UnresolvedHostStateMachine::State::kInit);
216 }
217 
218 }  // namespace chromeos_update_engine
219