1 // Copyright 2013 The Chromium 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 "components/update_client/crx_downloader.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/files/file_path.h"
12 #include "base/files/file_util.h"
13 #include "base/memory/ref_counted.h"
14 #include "base/path_service.h"
15 #include "base/run_loop.h"
16 #include "base/test/bind_test_util.h"
17 #include "base/test/task_environment.h"
18 #include "base/threading/thread_task_runner_handle.h"
19 #include "build/build_config.h"
20 #include "components/update_client/net/network_chromium.h"
21 #include "components/update_client/update_client_errors.h"
22 #include "components/update_client/utils.h"
23 #include "net/base/net_errors.h"
24 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
25 #include "services/network/test/test_url_loader_factory.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27
28 using base::ContentsEqual;
29
30 namespace update_client {
31
32 namespace {
33
34 const char kTestFileName[] = "jebgalgnebhfojomionfpkfelancnnkf.crx";
35
36 const char hash_jebg[] =
37 "7ab32f071cd9b5ef8e0d7913be161f532d98b3e9fa284a7cd8059c3409ce0498";
38
MakeTestFilePath(const char * file)39 base::FilePath MakeTestFilePath(const char* file) {
40 base::FilePath path;
41 base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
42 return path.AppendASCII("components/test/data/update_client")
43 .AppendASCII(file);
44 }
45
46 } // namespace
47
48 class CrxDownloaderTest : public testing::Test {
49 public:
50 CrxDownloaderTest();
51 ~CrxDownloaderTest() override;
52
53 // Overrides from testing::Test.
54 void SetUp() override;
55 void TearDown() override;
56
57 void Quit();
58 void RunThreads();
59 void RunThreadsUntilIdle();
60
61 void DownloadComplete(int crx_context, const CrxDownloader::Result& result);
62
63 void DownloadProgress(int crx_context);
64
GetInterceptorCount()65 int GetInterceptorCount() { return interceptor_count_; }
66
67 void AddResponse(const GURL& url,
68 const base::FilePath& file_path,
69 int net_error);
70
71 protected:
72 std::unique_ptr<CrxDownloader> crx_downloader_;
73
74 network::TestURLLoaderFactory test_url_loader_factory_;
75
76 CrxDownloader::DownloadCallback callback_;
77 CrxDownloader::ProgressCallback progress_callback_;
78
79 int crx_context_;
80
81 int num_download_complete_calls_;
82 CrxDownloader::Result download_complete_result_;
83
84 // These members are updated by DownloadProgress.
85 int num_progress_calls_;
86
87 // Accumulates the number of loads triggered.
88 int interceptor_count_ = 0;
89
90 // A magic value for the context to be used in the tests.
91 static const int kExpectedContext = 0xaabb;
92
93 private:
94 base::test::TaskEnvironment task_environment_;
95 scoped_refptr<network::SharedURLLoaderFactory>
96 test_shared_url_loader_factory_;
97 base::OnceClosure quit_closure_;
98 };
99
100 const int CrxDownloaderTest::kExpectedContext;
101
CrxDownloaderTest()102 CrxDownloaderTest::CrxDownloaderTest()
103 : callback_(base::BindOnce(&CrxDownloaderTest::DownloadComplete,
104 base::Unretained(this),
105 kExpectedContext)),
106 progress_callback_(
107 base::BindRepeating(&CrxDownloaderTest::DownloadProgress,
108 base::Unretained(this),
109 kExpectedContext)),
110 crx_context_(0),
111 num_download_complete_calls_(0),
112 num_progress_calls_(0),
113 task_environment_(base::test::TaskEnvironment::MainThreadType::IO),
114 test_shared_url_loader_factory_(
115 base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
116 &test_url_loader_factory_)) {}
117
118 CrxDownloaderTest::~CrxDownloaderTest() = default;
119
SetUp()120 void CrxDownloaderTest::SetUp() {
121 num_download_complete_calls_ = 0;
122 download_complete_result_ = CrxDownloader::Result();
123 num_progress_calls_ = 0;
124
125 // Do not use the background downloader in these tests.
126 crx_downloader_ = CrxDownloader::Create(
127 false, base::MakeRefCounted<NetworkFetcherChromiumFactory>(
128 test_shared_url_loader_factory_,
129 base::BindRepeating([](const GURL& url) { return false; })));
130 crx_downloader_->set_progress_callback(progress_callback_);
131
132 test_url_loader_factory_.SetInterceptor(base::BindLambdaForTesting(
133 [&](const network::ResourceRequest& request) { interceptor_count_++; }));
134 }
135
TearDown()136 void CrxDownloaderTest::TearDown() {
137 crx_downloader_.reset();
138 }
139
Quit()140 void CrxDownloaderTest::Quit() {
141 if (!quit_closure_.is_null())
142 std::move(quit_closure_).Run();
143 }
144
DownloadComplete(int crx_context,const CrxDownloader::Result & result)145 void CrxDownloaderTest::DownloadComplete(int crx_context,
146 const CrxDownloader::Result& result) {
147 ++num_download_complete_calls_;
148 crx_context_ = crx_context;
149 download_complete_result_ = result;
150 Quit();
151 }
152
DownloadProgress(int crx_context)153 void CrxDownloaderTest::DownloadProgress(int crx_context) {
154 ++num_progress_calls_;
155 }
156
AddResponse(const GURL & url,const base::FilePath & file_path,int net_error)157 void CrxDownloaderTest::AddResponse(const GURL& url,
158 const base::FilePath& file_path,
159 int net_error) {
160 if (net_error == net::OK) {
161 std::string data;
162 EXPECT_TRUE(base::ReadFileToString(file_path, &data));
163 auto head = network::mojom::URLResponseHead::New();
164 head->content_length = data.size();
165 network::URLLoaderCompletionStatus status(net_error);
166 status.decoded_body_length = data.size();
167 test_url_loader_factory_.AddResponse(url, std::move(head), data, status);
168 return;
169 }
170
171 EXPECT_NE(net_error, net::OK);
172 test_url_loader_factory_.AddResponse(
173 url, network::mojom::URLResponseHead::New(), std::string(),
174 network::URLLoaderCompletionStatus(net_error));
175 }
176
RunThreads()177 void CrxDownloaderTest::RunThreads() {
178 base::RunLoop runloop;
179 quit_closure_ = runloop.QuitClosure();
180 runloop.Run();
181
182 // Since some tests need to drain currently enqueued tasks such as network
183 // intercepts on the IO thread, run the threads until they are
184 // idle. The component updater service won't loop again until the loop count
185 // is set and the service is started.
186 RunThreadsUntilIdle();
187 }
188
RunThreadsUntilIdle()189 void CrxDownloaderTest::RunThreadsUntilIdle() {
190 task_environment_.RunUntilIdle();
191 base::RunLoop().RunUntilIdle();
192 }
193
194 // Tests that starting a download without a url results in an error.
TEST_F(CrxDownloaderTest,NoUrl)195 TEST_F(CrxDownloaderTest, NoUrl) {
196 std::vector<GURL> urls;
197 crx_downloader_->StartDownload(urls, std::string("abcd"),
198 std::move(callback_));
199 RunThreadsUntilIdle();
200
201 EXPECT_EQ(1, num_download_complete_calls_);
202 EXPECT_EQ(kExpectedContext, crx_context_);
203 EXPECT_EQ(static_cast<int>(CrxDownloaderError::NO_URL),
204 download_complete_result_.error);
205 EXPECT_TRUE(download_complete_result_.response.empty());
206 EXPECT_EQ(0, num_progress_calls_);
207 }
208
209 // Tests that starting a download without providing a hash results in an error.
TEST_F(CrxDownloaderTest,NoHash)210 TEST_F(CrxDownloaderTest, NoHash) {
211 std::vector<GURL> urls(1, GURL("http://somehost/somefile"));
212
213 crx_downloader_->StartDownload(urls, std::string(), std::move(callback_));
214 RunThreadsUntilIdle();
215
216 EXPECT_EQ(1, num_download_complete_calls_);
217 EXPECT_EQ(kExpectedContext, crx_context_);
218 EXPECT_EQ(static_cast<int>(CrxDownloaderError::NO_HASH),
219 download_complete_result_.error);
220 EXPECT_TRUE(download_complete_result_.response.empty());
221 EXPECT_EQ(0, num_progress_calls_);
222 }
223
224 // Tests that downloading from one url is successful.
TEST_F(CrxDownloaderTest,OneUrl)225 TEST_F(CrxDownloaderTest, OneUrl) {
226 const GURL expected_crx_url =
227 GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
228
229 const base::FilePath test_file(MakeTestFilePath(kTestFileName));
230 AddResponse(expected_crx_url, test_file, net::OK);
231
232 crx_downloader_->StartDownloadFromUrl(
233 expected_crx_url, std::string(hash_jebg), std::move(callback_));
234 RunThreads();
235
236 EXPECT_EQ(1, GetInterceptorCount());
237
238 EXPECT_EQ(1, num_download_complete_calls_);
239 EXPECT_EQ(kExpectedContext, crx_context_);
240 EXPECT_EQ(0, download_complete_result_.error);
241 EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
242
243 EXPECT_TRUE(
244 DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
245
246 EXPECT_LE(1, num_progress_calls_);
247 }
248
249 // Tests that downloading from one url fails if the actual hash of the file
250 // does not match the expected hash.
TEST_F(CrxDownloaderTest,OneUrlBadHash)251 TEST_F(CrxDownloaderTest, OneUrlBadHash) {
252 const GURL expected_crx_url =
253 GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
254
255 const base::FilePath test_file(MakeTestFilePath(kTestFileName));
256 AddResponse(expected_crx_url, test_file, net::OK);
257
258 crx_downloader_->StartDownloadFromUrl(
259 expected_crx_url,
260 std::string(
261 "813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9"),
262 std::move(callback_));
263 RunThreads();
264
265 EXPECT_EQ(1, GetInterceptorCount());
266
267 EXPECT_EQ(1, num_download_complete_calls_);
268 EXPECT_EQ(kExpectedContext, crx_context_);
269 EXPECT_EQ(static_cast<int>(CrxDownloaderError::BAD_HASH),
270 download_complete_result_.error);
271 EXPECT_TRUE(download_complete_result_.response.empty());
272
273 EXPECT_LE(1, num_progress_calls_);
274 }
275
276 // Tests that specifying two urls has no side effects. Expect a successful
277 // download, and only one download request be made.
TEST_F(CrxDownloaderTest,TwoUrls)278 TEST_F(CrxDownloaderTest, TwoUrls) {
279 const GURL expected_crx_url =
280 GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
281
282 const base::FilePath test_file(MakeTestFilePath(kTestFileName));
283 AddResponse(expected_crx_url, test_file, net::OK);
284
285 std::vector<GURL> urls;
286 urls.push_back(expected_crx_url);
287 urls.push_back(expected_crx_url);
288
289 crx_downloader_->StartDownload(urls, std::string(hash_jebg),
290 std::move(callback_));
291 RunThreads();
292
293 EXPECT_EQ(1, GetInterceptorCount());
294
295 EXPECT_EQ(1, num_download_complete_calls_);
296 EXPECT_EQ(kExpectedContext, crx_context_);
297 EXPECT_EQ(0, download_complete_result_.error);
298 EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
299
300 EXPECT_TRUE(
301 DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
302
303 EXPECT_LE(1, num_progress_calls_);
304 }
305
306 // Tests that the fallback to a valid url is successful.
TEST_F(CrxDownloaderTest,TwoUrls_FirstInvalid)307 TEST_F(CrxDownloaderTest, TwoUrls_FirstInvalid) {
308 const GURL expected_crx_url =
309 GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
310 const GURL no_file_url =
311 GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc.crx");
312
313 const base::FilePath test_file(MakeTestFilePath(kTestFileName));
314 AddResponse(expected_crx_url, test_file, net::OK);
315 AddResponse(no_file_url, base::FilePath(), net::ERR_FILE_NOT_FOUND);
316
317 std::vector<GURL> urls;
318 urls.push_back(no_file_url);
319 urls.push_back(expected_crx_url);
320
321 crx_downloader_->StartDownload(urls, std::string(hash_jebg),
322 std::move(callback_));
323 RunThreads();
324
325 EXPECT_EQ(2, GetInterceptorCount());
326
327 EXPECT_EQ(1, num_download_complete_calls_);
328 EXPECT_EQ(kExpectedContext, crx_context_);
329 EXPECT_EQ(0, download_complete_result_.error);
330 EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
331
332 EXPECT_TRUE(
333 DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
334
335 // Expect at least some progress reported by the loader.
336 EXPECT_LE(1, num_progress_calls_);
337
338 const auto download_metrics = crx_downloader_->download_metrics();
339 ASSERT_EQ(2u, download_metrics.size());
340 EXPECT_EQ(no_file_url, download_metrics[0].url);
341 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, download_metrics[0].error);
342 EXPECT_EQ(-1, download_metrics[0].downloaded_bytes);
343 EXPECT_EQ(-1, download_metrics[0].total_bytes);
344 EXPECT_EQ(expected_crx_url, download_metrics[1].url);
345 EXPECT_EQ(0, download_metrics[1].error);
346 EXPECT_EQ(1015, download_metrics[1].downloaded_bytes);
347 EXPECT_EQ(1015, download_metrics[1].total_bytes);
348 }
349
350 // Tests that the download succeeds if the first url is correct and the
351 // second bad url does not have a side-effect.
TEST_F(CrxDownloaderTest,TwoUrls_SecondInvalid)352 TEST_F(CrxDownloaderTest, TwoUrls_SecondInvalid) {
353 const GURL expected_crx_url =
354 GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
355 const GURL no_file_url =
356 GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc.crx");
357
358 const base::FilePath test_file(MakeTestFilePath(kTestFileName));
359 AddResponse(expected_crx_url, test_file, net::OK);
360 AddResponse(no_file_url, base::FilePath(), net::ERR_FILE_NOT_FOUND);
361
362 std::vector<GURL> urls;
363 urls.push_back(expected_crx_url);
364 urls.push_back(no_file_url);
365
366 crx_downloader_->StartDownload(urls, std::string(hash_jebg),
367 std::move(callback_));
368 RunThreads();
369
370 EXPECT_EQ(1, GetInterceptorCount());
371
372 EXPECT_EQ(1, num_download_complete_calls_);
373 EXPECT_EQ(kExpectedContext, crx_context_);
374 EXPECT_EQ(0, download_complete_result_.error);
375 EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
376
377 EXPECT_TRUE(
378 DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
379
380 EXPECT_LE(1, num_progress_calls_);
381
382 EXPECT_EQ(1u, crx_downloader_->download_metrics().size());
383 }
384
385 // Tests that the download fails if both urls don't serve content.
TEST_F(CrxDownloaderTest,TwoUrls_BothInvalid)386 TEST_F(CrxDownloaderTest, TwoUrls_BothInvalid) {
387 const GURL expected_crx_url =
388 GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
389
390 AddResponse(expected_crx_url, base::FilePath(), net::ERR_FILE_NOT_FOUND);
391
392 std::vector<GURL> urls;
393 urls.push_back(expected_crx_url);
394 urls.push_back(expected_crx_url);
395
396 crx_downloader_->StartDownload(urls, std::string(hash_jebg),
397 std::move(callback_));
398 RunThreads();
399
400 EXPECT_EQ(2, GetInterceptorCount());
401
402 EXPECT_EQ(1, num_download_complete_calls_);
403 EXPECT_EQ(kExpectedContext, crx_context_);
404 EXPECT_NE(0, download_complete_result_.error);
405 EXPECT_TRUE(download_complete_result_.response.empty());
406
407 const auto download_metrics = crx_downloader_->download_metrics();
408 ASSERT_EQ(2u, download_metrics.size());
409 EXPECT_EQ(expected_crx_url, download_metrics[0].url);
410 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, download_metrics[0].error);
411 EXPECT_EQ(-1, download_metrics[0].downloaded_bytes);
412 EXPECT_EQ(-1, download_metrics[0].total_bytes);
413 EXPECT_EQ(expected_crx_url, download_metrics[1].url);
414 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, download_metrics[1].error);
415 EXPECT_EQ(-1, download_metrics[1].downloaded_bytes);
416 EXPECT_EQ(-1, download_metrics[1].total_bytes);
417 }
418
419 } // namespace update_client
420