Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / safe_browsing / incident_reporting / download_metadata_manager_unittest.cc
blob7ac18559a88f1011de8a737bd6ae8dcb79bb64bd
1 // Copyright 2014 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.
5 #include "chrome/browser/safe_browsing/incident_reporting/download_metadata_manager.h"
7 #include <string>
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/files/file_path.h"
12 #include "base/files/file_util.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "chrome/common/safe_browsing/csd.pb.h"
17 #include "chrome/test/base/testing_profile.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/download_manager.h"
20 #include "content/public/test/mock_download_item.h"
21 #include "content/public/test/mock_download_manager.h"
22 #include "content/public/test/test_browser_thread_bundle.h"
23 #include "testing/gmock/include/gmock/gmock.h"
24 #include "testing/gtest/include/gtest/gtest.h"
26 using ::testing::AllOf;
27 using ::testing::Eq;
28 using ::testing::IsNull;
29 using ::testing::Ne;
30 using ::testing::NiceMock;
31 using ::testing::NotNull;
32 using ::testing::ResultOf;
33 using ::testing::Return;
34 using ::testing::SaveArg;
35 using ::testing::StrEq;
36 using ::testing::_;
38 namespace safe_browsing {
40 namespace {
42 const uint32_t kTestDownloadId = 47;
43 const uint32_t kOtherDownloadId = 48;
44 const uint32_t kCrazyDowloadId = 655;
45 const int64 kTestDownloadTimeMsec = 84;
46 const char kTestUrl[] = "http://test.test/foo";
47 const uint64_t kTestDownloadLength = 1000;
48 const double kTestDownloadEndTimeMs = 1413514824057;
50 // A utility class suitable for mocking that exposes a
51 // GetDownloadDetailsCallback.
52 class DownloadDetailsGetter {
53 public:
54 virtual ~DownloadDetailsGetter() {}
55 virtual void OnDownloadDetails(
56 ClientIncidentReport_DownloadDetails* details) = 0;
57 DownloadMetadataManager::GetDownloadDetailsCallback GetCallback() {
58 return base::Bind(&DownloadDetailsGetter::DownloadDetailsCallback,
59 base::Unretained(this));
62 private:
63 void DownloadDetailsCallback(
64 scoped_ptr<ClientIncidentReport_DownloadDetails> details) {
65 OnDownloadDetails(details.get());
69 // A mock DownloadDetailsGetter.
70 class MockDownloadDetailsGetter : public DownloadDetailsGetter {
71 public:
72 MOCK_METHOD1(OnDownloadDetails, void(ClientIncidentReport_DownloadDetails*));
75 // A mock DownloadMetadataManager that can be used to map a BrowserContext to
76 // a DownloadManager.
77 class MockDownloadMetadataManager : public DownloadMetadataManager {
78 public:
79 MockDownloadMetadataManager(
80 const scoped_refptr<base::SequencedTaskRunner>& task_runner)
81 : DownloadMetadataManager(task_runner) {}
83 MOCK_METHOD1(GetDownloadManagerForBrowserContext,
84 content::DownloadManager*(content::BrowserContext*));
87 // A helper function that returns the download URL from a DownloadDetails.
88 const std::string& GetDetailsDownloadUrl(
89 const ClientIncidentReport_DownloadDetails* details) {
90 return details->download().url();
93 // A helper function that returns the open time from a DownloadDetails.
94 int64_t GetDetailsOpenTime(
95 const ClientIncidentReport_DownloadDetails* details) {
96 return details->open_time_msec();
99 } // namespace
101 // The basis upon which unit tests of the DownloadMetadataManager are built.
102 class DownloadMetadataManagerTestBase : public ::testing::Test {
103 protected:
104 // Sets up a DownloadMetadataManager that will run tasks on the main test
105 // thread.
106 DownloadMetadataManagerTestBase()
107 : manager_(scoped_refptr<base::SequencedTaskRunner>(
108 base::ThreadTaskRunnerHandle::Get())),
109 download_manager_(),
110 dm_observer_() {}
112 // Returns the path to the test profile's DownloadMetadata file.
113 base::FilePath GetMetadataPath() const {
114 return profile_.GetPath().Append(FILE_PATH_LITERAL("DownloadMetadata"));
117 // Returns a new ClientDownloadRequest for the given download URL.
118 static scoped_ptr<ClientDownloadRequest> MakeTestRequest(const char* url) {
119 scoped_ptr<ClientDownloadRequest> request(new ClientDownloadRequest());
120 request->set_url(url);
121 request->mutable_digests();
122 request->set_length(kTestDownloadLength);
123 return request.Pass();
126 // Returns a new DownloadMetdata for the given download id.
127 static scoped_ptr<DownloadMetadata> GetTestMetadata(uint32_t download_id) {
128 scoped_ptr<DownloadMetadata> metadata(new DownloadMetadata());
129 metadata->set_download_id(download_id);
130 ClientIncidentReport_DownloadDetails* details =
131 metadata->mutable_download();
132 details->set_download_time_msec(kTestDownloadTimeMsec);
133 details->set_allocated_download(MakeTestRequest(kTestUrl).release());
134 return metadata.Pass();
137 // Writes a test DownloadMetadata file for the given download id to the
138 // test profile directory.
139 void WriteTestMetadataFileForItem(uint32_t download_id) {
140 std::string data;
141 ASSERT_TRUE(GetTestMetadata(download_id)->SerializeToString(&data));
142 ASSERT_TRUE(base::WriteFile(GetMetadataPath(), data.data(), data.size()));
145 // Writes a test DownloadMetadata file for kTestDownloadId to the test profile
146 // directory.
147 void WriteTestMetadataFile() {
148 WriteTestMetadataFileForItem(kTestDownloadId);
151 // Returns the DownloadMetadata read from the test profile's directory.
152 scoped_ptr<DownloadMetadata> ReadTestMetadataFile() const {
153 std::string data;
154 if (!base::ReadFileToString(GetMetadataPath(), &data))
155 return scoped_ptr<DownloadMetadata>();
156 scoped_ptr<DownloadMetadata> result(new DownloadMetadata);
157 EXPECT_TRUE(result->ParseFromString(data));
158 return result.Pass();
161 // Runs all tasks posted to the test thread's message loop.
162 void RunAllTasks() { base::MessageLoop::current()->RunUntilIdle(); }
164 // Adds a DownloadManager for the test profile. The DownloadMetadataManager's
165 // observer is stashed for later use. Only call once per call to
166 // ShutdownDownloadManager.
167 void AddDownloadManager() {
168 ASSERT_EQ(nullptr, dm_observer_);
169 // Shove the manager into the browser context.
170 ON_CALL(download_manager_, GetBrowserContext())
171 .WillByDefault(Return(&profile_));
172 ON_CALL(manager_, GetDownloadManagerForBrowserContext(Eq(&profile_)))
173 .WillByDefault(Return(&download_manager_));
174 // Capture the metadata manager's observer on the download manager.
175 EXPECT_CALL(download_manager_, AddObserver(&manager_))
176 .WillOnce(SaveArg<0>(&dm_observer_));
177 manager_.AddDownloadManager(&download_manager_);
180 // Shuts down the DownloadManager. Safe to call any number of times.
181 void ShutdownDownloadManager() {
182 if (dm_observer_) {
183 dm_observer_->ManagerGoingDown(&download_manager_);
184 // Note: these calls may result in "Uninteresting mock function call"
185 // warnings as a result of MockDownloadItem invoking observers in its
186 // dtor. This happens after the NiceMock wrapper has removed its niceness
187 // hook. These can safely be ignored, as they are entirely expected. The
188 // values specified by ON_CALL invocations in AddDownloadItems are
189 // returned as desired.
190 other_item_.reset();
191 test_item_.reset();
192 zero_item_.reset();
193 dm_observer_ = nullptr;
197 // Adds two test DownloadItems to the DownloadManager.
198 void AddDownloadItems() {
199 ASSERT_NE(nullptr, dm_observer_);
200 // Add the item under test.
201 test_item_.reset(new NiceMock<content::MockDownloadItem>);
202 ON_CALL(*test_item_, GetId())
203 .WillByDefault(Return(kTestDownloadId));
204 ON_CALL(*test_item_, GetBrowserContext())
205 .WillByDefault(Return(&profile_));
206 ON_CALL(*test_item_, GetEndTime())
207 .WillByDefault(Return(base::Time::FromJsTime(kTestDownloadEndTimeMs)));
208 ON_CALL(*test_item_, GetState())
209 .WillByDefault(Return(content::DownloadItem::COMPLETE));
210 dm_observer_->OnDownloadCreated(&download_manager_, test_item_.get());
212 // Add another item.
213 other_item_.reset(new NiceMock<content::MockDownloadItem>);
214 ON_CALL(*other_item_, GetId())
215 .WillByDefault(Return(kOtherDownloadId));
216 ON_CALL(*other_item_, GetBrowserContext())
217 .WillByDefault(Return(&profile_));
218 ON_CALL(*test_item_, GetEndTime())
219 .WillByDefault(Return(base::Time::FromJsTime(kTestDownloadEndTimeMs)));
220 dm_observer_->OnDownloadCreated(&download_manager_, other_item_.get());
222 // Add an item with an id of zero.
223 zero_item_.reset(new NiceMock<content::MockDownloadItem>);
224 ON_CALL(*zero_item_, GetId())
225 .WillByDefault(Return(0));
226 ON_CALL(*zero_item_, GetBrowserContext())
227 .WillByDefault(Return(&profile_));
228 ON_CALL(*zero_item_, GetEndTime())
229 .WillByDefault(Return(base::Time::FromJsTime(kTestDownloadEndTimeMs)));
230 ON_CALL(*zero_item_, GetState())
231 .WillByDefault(Return(content::DownloadItem::COMPLETE));
232 dm_observer_->OnDownloadCreated(&download_manager_, zero_item_.get());
234 ON_CALL(download_manager_, GetAllDownloads(_))
235 .WillByDefault(
236 Invoke(this, &DownloadMetadataManagerTestBase::GetAllDownloads));
239 // An implementation of the MockDownloadManager's
240 // DownloadManager::GetAllDownloads method that returns all items.
241 void GetAllDownloads(content::DownloadManager::DownloadVector* downloads) {
242 downloads->clear();
243 if (test_item_)
244 downloads->push_back(test_item_.get());
245 if (other_item_)
246 downloads->push_back(other_item_.get());
247 if (zero_item_)
248 downloads->push_back(zero_item_.get());
251 content::TestBrowserThreadBundle thread_bundle_;
252 NiceMock<MockDownloadMetadataManager> manager_;
253 TestingProfile profile_;
254 NiceMock<content::MockDownloadManager> download_manager_;
255 scoped_ptr<content::MockDownloadItem> test_item_;
256 scoped_ptr<content::MockDownloadItem> other_item_;
257 scoped_ptr<content::MockDownloadItem> zero_item_;
259 // The DownloadMetadataManager's content::DownloadManager::Observer. Captured
260 // by download_manager_'s AddObserver action.
261 content::DownloadManager::Observer* dm_observer_;
264 // A parameterized test that exercises GetDownloadDetails. The parameters
265 // dictate the exact state of affairs leading up to the call as follows:
266 // 0: if "present", the profile has a pre-existing DownloadMetadata file.
267 // 1: if "managed", the profile's DownloadManager has been created.
268 // 2: the state of the DownloadItem prior to the call:
269 // "not_created": the DownloadItem has not been created.
270 // "created": the DownloadItem has been created.
271 // "opened": the DownloadItem has been opened.
272 // "removed": the DownloadItem has been removed.
273 // 3: if "loaded", the task to load the DownloadMetadata file is allowed to
274 // complete.
275 // 4: if "early_shutdown", the DownloadManager is shut down before the callback
276 // is allowed to complete.
277 class GetDetailsTest
278 : public DownloadMetadataManagerTestBase,
279 public ::testing::WithParamInterface<testing::tuple<const char*,
280 const char*,
281 const char*,
282 const char*,
283 const char*>> {
284 protected:
285 enum DownloadItemAction {
286 NOT_CREATED,
287 CREATED,
288 OPENED,
289 REMOVED,
291 GetDetailsTest()
292 : metadata_file_present_(),
293 manager_added_(),
294 item_action_(NOT_CREATED),
295 details_loaded_(),
296 early_shutdown_() {}
298 void SetUp() override {
299 DownloadMetadataManagerTestBase::SetUp();
300 metadata_file_present_ =
301 (std::string(testing::get<0>(GetParam())) == "present");
302 manager_added_ = (std::string(testing::get<1>(GetParam())) == "managed");
303 const std::string item_action(testing::get<2>(GetParam()));
304 item_action_ = (item_action == "not_created" ? NOT_CREATED :
305 (item_action == "created" ? CREATED :
306 (item_action == "opened" ? OPENED : REMOVED)));
307 details_loaded_ = (std::string(testing::get<3>(GetParam())) == "loaded");
308 early_shutdown_ =
309 (std::string(testing::get<4>(GetParam())) == "early_shutdown");
311 // Fixup combinations that don't make sense.
312 if (!manager_added_)
313 item_action_ = NOT_CREATED;
316 bool metadata_file_present_;
317 bool manager_added_;
318 DownloadItemAction item_action_;
319 bool details_loaded_;
320 bool early_shutdown_;
323 // Tests that DownloadMetadataManager::GetDownloadDetails works for all
324 // combinations of states.
325 TEST_P(GetDetailsTest, GetDownloadDetails) {
326 // Optionally put a metadata file in the profile directory.
327 if (metadata_file_present_)
328 WriteTestMetadataFile();
330 // Optionally add a download manager for the profile.
331 if (manager_added_)
332 AddDownloadManager();
334 // Optionally create download items and perform actions on the one under test.
335 if (item_action_ != NOT_CREATED)
336 AddDownloadItems();
337 if (item_action_ == OPENED)
338 test_item_->NotifyObserversDownloadOpened();
339 else if (item_action_ == REMOVED)
340 test_item_->NotifyObserversDownloadRemoved();
342 // Optionally allow the task to read the file to complete.
343 if (details_loaded_)
344 RunAllTasks();
346 // In http://crbug.com/433928, open after removal during load caused a crash.
347 if (item_action_ == REMOVED)
348 test_item_->NotifyObserversDownloadOpened();
350 MockDownloadDetailsGetter details_getter;
351 if (metadata_file_present_ && item_action_ != REMOVED) {
352 // The file is present, so expect that the callback is invoked with the
353 // details of the test download data written by WriteTestMetadataFile.
354 if (item_action_ == OPENED) {
355 EXPECT_CALL(details_getter,
356 OnDownloadDetails(
357 AllOf(ResultOf(GetDetailsDownloadUrl, StrEq(kTestUrl)),
358 ResultOf(GetDetailsOpenTime, Ne(0)))));
359 } else {
360 EXPECT_CALL(details_getter,
361 OnDownloadDetails(
362 AllOf(ResultOf(GetDetailsDownloadUrl, StrEq(kTestUrl)),
363 ResultOf(GetDetailsOpenTime, Eq(0)))));
365 } else {
366 // No file on disk, so expect that the callback is invoked with null.
367 EXPECT_CALL(details_getter, OnDownloadDetails(IsNull()));
370 // Fire in the hole!
371 manager_.GetDownloadDetails(&profile_, details_getter.GetCallback());
373 // Shutdown the download manager, if relevant.
374 if (early_shutdown_)
375 ShutdownDownloadManager();
377 // Allow the read task and the response callback to run.
378 RunAllTasks();
380 // Shutdown the download manager, if relevant.
381 ShutdownDownloadManager();
384 INSTANTIATE_TEST_CASE_P(
385 DownloadMetadataManager,
386 GetDetailsTest,
387 testing::Combine(
388 testing::Values("absent", "present"),
389 testing::Values("not_managed", "managed"),
390 testing::Values("not_created", "created", "opened", "removed"),
391 testing::Values("waiting", "loaded"),
392 testing::Values("normal_shutdown", "early_shutdown")));
394 // A parameterized test that exercises SetRequest. The parameters dictate the
395 // exact state of affairs leading up to the call as follows:
396 // 0: the state of the DownloadMetadata file for the test profile:
397 // "absent": no file is present.
398 // "this": the file corresponds to the item being updated.
399 // "other": the file correponds to a different item.
400 // "unknown": the file corresponds to an item that has not been created.
401 // 1: if "pending", an operation is applied to the item being updated prior to
402 // the call.
403 // 2: if "pending", an operation is applied to a different item prior to the
404 // call.
405 // 3: if "loaded", the task to load the DownloadMetadata file is allowed to
406 // complete.
407 // 4: if "set", the call to SetRequest contains a new request; otherwise it
408 // does not, leading to removal of metadata.
409 class SetRequestTest
410 : public DownloadMetadataManagerTestBase,
411 public ::testing::WithParamInterface<testing::tuple<const char*,
412 const char*,
413 const char*,
414 const char*,
415 const char*>> {
416 protected:
417 enum MetadataFilePresent {
418 ABSENT,
419 PRESENT_FOR_THIS_ITEM,
420 PRESENT_FOR_OTHER_ITEM,
421 PRESENT_FOR_UNKNOWN_ITEM,
423 SetRequestTest()
424 : metadata_file_present_(ABSENT),
425 same_ops_(),
426 other_ops_(),
427 details_loaded_(),
428 set_request_() {}
430 void SetUp() override {
431 DownloadMetadataManagerTestBase::SetUp();
432 const std::string present(testing::get<0>(GetParam()));
433 metadata_file_present_ = (present == "absent" ? ABSENT :
434 (present == "this" ? PRESENT_FOR_THIS_ITEM :
435 (present == "other" ? PRESENT_FOR_OTHER_ITEM :
436 PRESENT_FOR_UNKNOWN_ITEM)));
437 same_ops_ = (std::string(testing::get<1>(GetParam())) == "pending");
438 other_ops_ = (std::string(testing::get<2>(GetParam())) == "pending");
439 details_loaded_ = (std::string(testing::get<3>(GetParam())) == "loaded");
440 set_request_ = (std::string(testing::get<4>(GetParam())) == "set");
443 MetadataFilePresent metadata_file_present_;
444 bool same_ops_;
445 bool other_ops_;
446 bool details_loaded_;
447 bool set_request_;
450 // Tests that DownloadMetadataManager::SetRequest works for all combinations of
451 // states.
452 TEST_P(SetRequestTest, SetRequest) {
453 // Optionally put a metadata file in the profile directory.
454 switch (metadata_file_present_) {
455 case ABSENT:
456 break;
457 case PRESENT_FOR_THIS_ITEM:
458 WriteTestMetadataFile();
459 break;
460 case PRESENT_FOR_OTHER_ITEM:
461 WriteTestMetadataFileForItem(kOtherDownloadId);
462 break;
463 case PRESENT_FOR_UNKNOWN_ITEM:
464 WriteTestMetadataFileForItem(kCrazyDowloadId);
465 break;
468 AddDownloadManager();
469 AddDownloadItems();
471 // Optionally allow the task to read the file to complete.
472 if (details_loaded_) {
473 RunAllTasks();
474 } else {
475 // Optionally add pending operations if the load is outstanding.
476 if (same_ops_)
477 test_item_->NotifyObserversDownloadOpened();
478 if (other_ops_)
479 other_item_->NotifyObserversDownloadOpened();
482 static const char kNewUrl[] = "http://blorf";
483 if (set_request_)
484 manager_.SetRequest(test_item_.get(), MakeTestRequest(kNewUrl).get());
486 // Allow the write or remove task to run.
487 RunAllTasks();
489 if (set_request_) {
490 MockDownloadDetailsGetter details_getter;
491 // Expect that the callback is invoked with details for this item.
492 EXPECT_CALL(
493 details_getter,
494 OnDownloadDetails(ResultOf(GetDetailsDownloadUrl, StrEq(kNewUrl))));
495 manager_.GetDownloadDetails(&profile_, details_getter.GetCallback());
498 // In http://crbug.com/433928, open after SetRequest(nullpr) caused a crash.
499 test_item_->NotifyObserversDownloadOpened();
501 ShutdownDownloadManager();
503 scoped_ptr<DownloadMetadata> metadata(ReadTestMetadataFile());
504 if (set_request_) {
505 // Expect that the file contains metadata for the download.
506 ASSERT_TRUE(metadata);
507 EXPECT_EQ(kTestDownloadId, metadata->download_id());
508 EXPECT_STREQ(kNewUrl, metadata->download().download().url().c_str());
509 } else if (metadata_file_present_) {
510 // Expect that the metadata file has not been overwritten.
511 ASSERT_TRUE(metadata);
512 } else {
513 // Expect that the file is not present.
514 ASSERT_FALSE(metadata);
518 INSTANTIATE_TEST_CASE_P(
519 DownloadMetadataManager,
520 SetRequestTest,
521 testing::Combine(testing::Values("absent", "this", "other", "unknown"),
522 testing::Values("none", "pending"),
523 testing::Values("none", "pending"),
524 testing::Values("waiting", "loaded"),
525 testing::Values("clear", "set")));
527 TEST_F(DownloadMetadataManagerTestBase, ActiveDownloadNoRequest) {
528 // Put some metadata on disk from a previous download.
529 WriteTestMetadataFileForItem(kOtherDownloadId);
531 AddDownloadManager();
532 AddDownloadItems();
534 // Allow everything to load into steady-state.
535 RunAllTasks();
537 // The test item is in progress.
538 ON_CALL(*test_item_, GetState())
539 .WillByDefault(Return(content::DownloadItem::IN_PROGRESS));
540 test_item_->NotifyObserversDownloadUpdated();
541 test_item_->NotifyObserversDownloadUpdated();
543 // The test item completes.
544 ON_CALL(*test_item_, GetState())
545 .WillByDefault(Return(content::DownloadItem::COMPLETE));
546 test_item_->NotifyObserversDownloadUpdated();
548 RunAllTasks();
549 ShutdownDownloadManager();
551 // Expect that the metadata file is still present.
552 scoped_ptr<DownloadMetadata> metadata(ReadTestMetadataFile());
553 ASSERT_TRUE(metadata);
554 EXPECT_EQ(kOtherDownloadId, metadata->download_id());
557 TEST_F(DownloadMetadataManagerTestBase, ActiveDownloadWithRequest) {
558 // Put some metadata on disk from a previous download.
559 WriteTestMetadataFileForItem(kOtherDownloadId);
561 AddDownloadManager();
562 AddDownloadItems();
564 // Allow everything to load into steady-state.
565 RunAllTasks();
567 // The test item is in progress.
568 ON_CALL(*test_item_, GetState())
569 .WillByDefault(Return(content::DownloadItem::IN_PROGRESS));
570 test_item_->NotifyObserversDownloadUpdated();
572 // A request is set for it.
573 static const char kNewUrl[] = "http://blorf";
574 manager_.SetRequest(test_item_.get(), MakeTestRequest(kNewUrl).get());
576 test_item_->NotifyObserversDownloadUpdated();
578 // The test item completes.
579 ON_CALL(*test_item_, GetState())
580 .WillByDefault(Return(content::DownloadItem::COMPLETE));
581 test_item_->NotifyObserversDownloadUpdated();
583 RunAllTasks();
585 MockDownloadDetailsGetter details_getter;
586 // Expect that the callback is invoked with details for this item.
587 EXPECT_CALL(
588 details_getter,
589 OnDownloadDetails(ResultOf(GetDetailsDownloadUrl, StrEq(kNewUrl))));
590 manager_.GetDownloadDetails(&profile_, details_getter.GetCallback());
592 ShutdownDownloadManager();
594 // Expect that the file contains metadata for the download.
595 scoped_ptr<DownloadMetadata> metadata(ReadTestMetadataFile());
596 ASSERT_TRUE(metadata);
597 EXPECT_EQ(kTestDownloadId, metadata->download_id());
598 EXPECT_STREQ(kNewUrl, metadata->download().download().url().c_str());
601 // Regression test for http://crbug.com/504092: open an item with id==0 when
602 // there is no metadata loaded.
603 TEST_F(DownloadMetadataManagerTestBase, OpenItemWithZeroId) {
604 AddDownloadManager();
605 AddDownloadItems();
607 // Allow everything to load into steady-state.
608 RunAllTasks();
610 // Open the zero-id item.
611 zero_item_->NotifyObserversDownloadOpened();
613 ShutdownDownloadManager();
616 } // namespace safe_browsing