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"
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
;
28 using ::testing::IsNull
;
30 using ::testing::NiceMock
;
31 using ::testing::NotNull
;
32 using ::testing::ResultOf
;
33 using ::testing::Return
;
34 using ::testing::SaveArg
;
35 using ::testing::StrEq
;
38 namespace safe_browsing
{
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
{
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));
63 void DownloadDetailsCallback(
64 scoped_ptr
<ClientIncidentReport_DownloadDetails
> details
) {
65 OnDownloadDetails(details
.get());
69 // A mock DownloadDetailsGetter.
70 class MockDownloadDetailsGetter
: public DownloadDetailsGetter
{
72 MOCK_METHOD1(OnDownloadDetails
, void(ClientIncidentReport_DownloadDetails
*));
75 // A mock DownloadMetadataManager that can be used to map a BrowserContext to
77 class MockDownloadMetadataManager
: public DownloadMetadataManager
{
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();
101 // The basis upon which unit tests of the DownloadMetadataManager are built.
102 class DownloadMetadataManagerTestBase
: public ::testing::Test
{
104 // Sets up a DownloadMetadataManager that will run tasks on the main test
106 DownloadMetadataManagerTestBase()
107 : manager_(scoped_refptr
<base::SequencedTaskRunner
>(
108 base::ThreadTaskRunnerHandle::Get())),
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
) {
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
147 void WriteTestMetadataFile() {
148 WriteTestMetadataFileForItem(kTestDownloadId
);
151 // Returns the DownloadMetadata read from the test profile's directory.
152 scoped_ptr
<DownloadMetadata
> ReadTestMetadataFile() const {
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() {
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.
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());
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(_
))
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
) {
244 downloads
->push_back(test_item_
.get());
246 downloads
->push_back(other_item_
.get());
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
275 // 4: if "early_shutdown", the DownloadManager is shut down before the callback
276 // is allowed to complete.
278 : public DownloadMetadataManagerTestBase
,
279 public ::testing::WithParamInterface
<testing::tuple
<const char*,
285 enum DownloadItemAction
{
292 : metadata_file_present_(),
294 item_action_(NOT_CREATED
),
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");
309 (std::string(testing::get
<4>(GetParam())) == "early_shutdown");
311 // Fixup combinations that don't make sense.
313 item_action_
= NOT_CREATED
;
316 bool metadata_file_present_
;
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.
332 AddDownloadManager();
334 // Optionally create download items and perform actions on the one under test.
335 if (item_action_
!= NOT_CREATED
)
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.
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
,
357 AllOf(ResultOf(GetDetailsDownloadUrl
, StrEq(kTestUrl
)),
358 ResultOf(GetDetailsOpenTime
, Ne(0)))));
360 EXPECT_CALL(details_getter
,
362 AllOf(ResultOf(GetDetailsDownloadUrl
, StrEq(kTestUrl
)),
363 ResultOf(GetDetailsOpenTime
, Eq(0)))));
366 // No file on disk, so expect that the callback is invoked with null.
367 EXPECT_CALL(details_getter
, OnDownloadDetails(IsNull()));
371 manager_
.GetDownloadDetails(&profile_
, details_getter
.GetCallback());
373 // Shutdown the download manager, if relevant.
375 ShutdownDownloadManager();
377 // Allow the read task and the response callback to run.
380 // Shutdown the download manager, if relevant.
381 ShutdownDownloadManager();
384 INSTANTIATE_TEST_CASE_P(
385 DownloadMetadataManager
,
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
403 // 2: if "pending", an operation is applied to a different item prior to the
405 // 3: if "loaded", the task to load the DownloadMetadata file is allowed to
407 // 4: if "set", the call to SetRequest contains a new request; otherwise it
408 // does not, leading to removal of metadata.
410 : public DownloadMetadataManagerTestBase
,
411 public ::testing::WithParamInterface
<testing::tuple
<const char*,
417 enum MetadataFilePresent
{
419 PRESENT_FOR_THIS_ITEM
,
420 PRESENT_FOR_OTHER_ITEM
,
421 PRESENT_FOR_UNKNOWN_ITEM
,
424 : metadata_file_present_(ABSENT
),
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_
;
446 bool details_loaded_
;
450 // Tests that DownloadMetadataManager::SetRequest works for all combinations of
452 TEST_P(SetRequestTest
, SetRequest
) {
453 // Optionally put a metadata file in the profile directory.
454 switch (metadata_file_present_
) {
457 case PRESENT_FOR_THIS_ITEM
:
458 WriteTestMetadataFile();
460 case PRESENT_FOR_OTHER_ITEM
:
461 WriteTestMetadataFileForItem(kOtherDownloadId
);
463 case PRESENT_FOR_UNKNOWN_ITEM
:
464 WriteTestMetadataFileForItem(kCrazyDowloadId
);
468 AddDownloadManager();
471 // Optionally allow the task to read the file to complete.
472 if (details_loaded_
) {
475 // Optionally add pending operations if the load is outstanding.
477 test_item_
->NotifyObserversDownloadOpened();
479 other_item_
->NotifyObserversDownloadOpened();
482 static const char kNewUrl
[] = "http://blorf";
484 manager_
.SetRequest(test_item_
.get(), MakeTestRequest(kNewUrl
).get());
486 // Allow the write or remove task to run.
490 MockDownloadDetailsGetter details_getter
;
491 // Expect that the callback is invoked with details for this item.
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());
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
);
513 // Expect that the file is not present.
514 ASSERT_FALSE(metadata
);
518 INSTANTIATE_TEST_CASE_P(
519 DownloadMetadataManager
,
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();
534 // Allow everything to load into steady-state.
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();
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();
564 // Allow everything to load into steady-state.
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();
585 MockDownloadDetailsGetter details_getter
;
586 // Expect that the callback is invoked with details for this item.
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();
607 // Allow everything to load into steady-state.
610 // Open the zero-id item.
611 zero_item_
->NotifyObserversDownloadOpened();
613 ShutdownDownloadManager();
616 } // namespace safe_browsing