Fix crash in download metadata manager when opening a download.
[chromium-blink-merge.git] / chrome / browser / safe_browsing / incident_reporting / download_metadata_manager_unittest.cc
blob538a7dacf6b6a81ecf0d1adbf9db6ea95d39d007
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;
37 namespace safe_browsing {
39 namespace {
41 const uint32_t kTestDownloadId = 47;
42 const uint32_t kOtherDownloadId = 48;
43 const uint32_t kCrazyDowloadId = 655;
44 const int64 kTestDownloadTimeMsec = 84;
45 const char kTestUrl[] = "http://test.test/foo";
46 const uint64_t kTestDownloadLength = 1000;
47 const double kTestDownloadEndTimeMs = 1413514824057;
49 // A utility class suitable for mocking that exposes a
50 // GetDownloadDetailsCallback.
51 class DownloadDetailsGetter {
52 public:
53 virtual ~DownloadDetailsGetter() {}
54 virtual void OnDownloadDetails(
55 ClientIncidentReport_DownloadDetails* details) = 0;
56 DownloadMetadataManager::GetDownloadDetailsCallback GetCallback() {
57 return base::Bind(&DownloadDetailsGetter::DownloadDetailsCallback,
58 base::Unretained(this));
61 private:
62 void DownloadDetailsCallback(
63 scoped_ptr<ClientIncidentReport_DownloadDetails> details) {
64 OnDownloadDetails(details.get());
68 // A mock DownloadDetailsGetter.
69 class MockDownloadDetailsGetter : public DownloadDetailsGetter {
70 public:
71 MOCK_METHOD1(OnDownloadDetails, void(ClientIncidentReport_DownloadDetails*));
74 // A mock DownloadMetadataManager that can be used to map a BrowserContext to
75 // a DownloadManager.
76 class MockDownloadMetadataManager : public DownloadMetadataManager {
77 public:
78 MockDownloadMetadataManager(
79 const scoped_refptr<base::SequencedTaskRunner>& task_runner)
80 : DownloadMetadataManager(task_runner) {}
82 MOCK_METHOD1(GetDownloadManagerForBrowserContext,
83 content::DownloadManager*(content::BrowserContext*));
86 // A helper function that returns the download URL from a DownloadDetails.
87 const std::string& GetDetailsDownloadUrl(
88 const ClientIncidentReport_DownloadDetails* details) {
89 return details->download().url();
92 // A helper function that returns the open time from a DownloadDetails.
93 int64_t GetDetailsOpenTime(
94 const ClientIncidentReport_DownloadDetails* details) {
95 return details->open_time_msec();
98 } // namespace
100 // The basis upon which unit tests of the DownloadMetadataManager are built.
101 class DownloadMetadataManagerTestBase : public ::testing::Test {
102 protected:
103 // Sets up a DownloadMetadataManager that will run tasks on the main test
104 // thread.
105 DownloadMetadataManagerTestBase()
106 : manager_(scoped_refptr<base::SequencedTaskRunner>(
107 base::ThreadTaskRunnerHandle::Get())),
108 download_manager_(),
109 dm_observer_() {}
111 // Returns the path to the test profile's DownloadMetadata file.
112 base::FilePath GetMetadataPath() const {
113 return profile_.GetPath().Append(FILE_PATH_LITERAL("DownloadMetadata"));
116 // Returns a new ClientDownloadRequest for the given download URL.
117 static scoped_ptr<ClientDownloadRequest> MakeTestRequest(const char* url) {
118 scoped_ptr<ClientDownloadRequest> request(new ClientDownloadRequest());
119 request->set_url(url);
120 request->mutable_digests();
121 request->set_length(kTestDownloadLength);
122 return request.Pass();
125 // Returns a new DownloadMetdata for the given download id.
126 static scoped_ptr<DownloadMetadata> GetTestMetadata(uint32_t download_id) {
127 scoped_ptr<DownloadMetadata> metadata(new DownloadMetadata());
128 metadata->set_download_id(download_id);
129 ClientIncidentReport_DownloadDetails* details =
130 metadata->mutable_download();
131 details->set_download_time_msec(kTestDownloadTimeMsec);
132 details->set_allocated_download(MakeTestRequest(kTestUrl).release());
133 return metadata.Pass();
136 // Writes a test DownloadMetadata file for the given download id to the
137 // test profile directory.
138 void WriteTestMetadataFileForItem(uint32_t download_id) {
139 std::string data;
140 ASSERT_TRUE(GetTestMetadata(download_id)->SerializeToString(&data));
141 ASSERT_TRUE(base::WriteFile(GetMetadataPath(), data.data(), data.size()));
144 // Writes a test DownloadMetadata file for kTestDownloadId to the test profile
145 // directory.
146 void WriteTestMetadataFile() {
147 WriteTestMetadataFileForItem(kTestDownloadId);
150 // Returns the DownloadMetadata read from the test profile's directory.
151 scoped_ptr<DownloadMetadata> ReadTestMetadataFile() const {
152 std::string data;
153 if (!base::ReadFileToString(GetMetadataPath(), &data))
154 return scoped_ptr<DownloadMetadata>();
155 scoped_ptr<DownloadMetadata> result(new DownloadMetadata);
156 EXPECT_TRUE(result->ParseFromString(data));
157 return result.Pass();
160 // Runs all tasks posted to the test thread's message loop.
161 void RunAllTasks() { base::MessageLoop::current()->RunUntilIdle(); }
163 // Adds a DownloadManager for the test profile. The DownloadMetadataManager's
164 // observer is stashed for later use. Only call once per call to
165 // ShutdownDownloadManager.
166 void AddDownloadManager() {
167 ASSERT_EQ(nullptr, dm_observer_);
168 // Shove the manager into the browser context.
169 ON_CALL(download_manager_, GetBrowserContext())
170 .WillByDefault(Return(&profile_));
171 ON_CALL(manager_, GetDownloadManagerForBrowserContext(Eq(&profile_)))
172 .WillByDefault(Return(&download_manager_));
173 // Capture the metadata manager's observer on the download manager.
174 EXPECT_CALL(download_manager_, AddObserver(&manager_))
175 .WillOnce(SaveArg<0>(&dm_observer_));
176 manager_.AddDownloadManager(&download_manager_);
179 // Shuts down the DownloadManager. Safe to call any number of times.
180 void ShutdownDownloadManager() {
181 if (dm_observer_) {
182 dm_observer_->ManagerGoingDown(&download_manager_);
183 // Note: these calls may result in "Uninteresting mock function call"
184 // warnings as a result of MockDownloadItem invoking observers in its
185 // dtor. This happens after the NiceMock wrapper has removed its niceness
186 // hook. These can safely be ignored, as they are entirely expected. The
187 // values specified by ON_CALL invocations in AddDownloadItems are
188 // returned as desired.
189 other_item_.reset();
190 test_item_.reset();
191 dm_observer_ = nullptr;
195 // Adds two test DownloadItems to the DownloadManager.
196 void AddDownloadItems() {
197 ASSERT_NE(nullptr, dm_observer_);
198 // Add the item under test.
199 test_item_.reset(new NiceMock<content::MockDownloadItem>);
200 ON_CALL(*test_item_, GetId())
201 .WillByDefault(Return(kTestDownloadId));
202 ON_CALL(*test_item_, GetBrowserContext())
203 .WillByDefault(Return(&profile_));
204 ON_CALL(*test_item_, GetEndTime())
205 .WillByDefault(Return(base::Time::FromJsTime(kTestDownloadEndTimeMs)));
206 dm_observer_->OnDownloadCreated(&download_manager_, test_item_.get());
208 // Add another item.
209 other_item_.reset(new NiceMock<content::MockDownloadItem>);
210 ON_CALL(*other_item_, GetId())
211 .WillByDefault(Return(kOtherDownloadId));
212 ON_CALL(*other_item_, GetBrowserContext())
213 .WillByDefault(Return(&profile_));
214 ON_CALL(*test_item_, GetEndTime())
215 .WillByDefault(Return(base::Time::FromJsTime(kTestDownloadEndTimeMs)));
216 dm_observer_->OnDownloadCreated(&download_manager_, other_item_.get());
219 content::TestBrowserThreadBundle thread_bundle_;
220 NiceMock<MockDownloadMetadataManager> manager_;
221 TestingProfile profile_;
222 NiceMock<content::MockDownloadManager> download_manager_;
223 scoped_ptr<content::MockDownloadItem> test_item_;
224 scoped_ptr<content::MockDownloadItem> other_item_;
225 content::DownloadManager::Observer* dm_observer_;
228 // A parameterized test that exercises GetDownloadDetails. The parameters
229 // dictate the exact state of affairs leading up to the call as follows:
230 // 0: if "present", the profile has a pre-existing DownloadMetadata file.
231 // 1: if "managed", the profile's DownloadManager has been created.
232 // 2: the state of the DownloadItem prior to the call:
233 // "not_created": the DownloadItem has not been created.
234 // "created": the DownloadItem has been created.
235 // "opened": the DownloadItem has been opened.
236 // "removed": the DownloadItem has been removed.
237 // 3: if "loaded", the task to load the DownloadMetadata file is allowed to
238 // complete.
239 // 4: if "early_shutdown", the DownloadManager is shut down before the callback
240 // is allowed to complete.
241 class GetDetailsTest
242 : public DownloadMetadataManagerTestBase,
243 public ::testing::WithParamInterface<testing::tuple<const char*,
244 const char*,
245 const char*,
246 const char*,
247 const char*>> {
248 protected:
249 enum DownloadItemAction {
250 NOT_CREATED,
251 CREATED,
252 OPENED,
253 REMOVED,
255 GetDetailsTest()
256 : metadata_file_present_(),
257 manager_added_(),
258 item_action_(NOT_CREATED),
259 details_loaded_(),
260 early_shutdown_() {}
262 void SetUp() override {
263 DownloadMetadataManagerTestBase::SetUp();
264 metadata_file_present_ =
265 (std::string(testing::get<0>(GetParam())) == "present");
266 manager_added_ = (std::string(testing::get<1>(GetParam())) == "managed");
267 const std::string item_action(testing::get<2>(GetParam()));
268 item_action_ = (item_action == "not_created" ? NOT_CREATED :
269 (item_action == "created" ? CREATED :
270 (item_action == "opened" ? OPENED : REMOVED)));
271 details_loaded_ = (std::string(testing::get<3>(GetParam())) == "loaded");
272 early_shutdown_ =
273 (std::string(testing::get<4>(GetParam())) == "early_shutdown");
275 // Fixup combinations that don't make sense.
276 if (!manager_added_)
277 item_action_ = NOT_CREATED;
280 bool metadata_file_present_;
281 bool manager_added_;
282 DownloadItemAction item_action_;
283 bool details_loaded_;
284 bool early_shutdown_;
287 // Tests that DownloadMetadataManager::GetDownloadDetails works for all
288 // combinations of states.
289 TEST_P(GetDetailsTest, GetDownloadDetails) {
290 // Optionally put a metadata file in the profile directory.
291 if (metadata_file_present_)
292 WriteTestMetadataFile();
294 // Optionally add a download manager for the profile.
295 if (manager_added_)
296 AddDownloadManager();
298 // Optionally create download items and perform actions on the one under test.
299 if (item_action_ != NOT_CREATED)
300 AddDownloadItems();
301 if (item_action_ == OPENED)
302 test_item_->NotifyObserversDownloadOpened();
303 else if (item_action_ == REMOVED)
304 test_item_->NotifyObserversDownloadRemoved();
306 // Optionally allow the task to read the file to complete.
307 if (details_loaded_)
308 RunAllTasks();
310 MockDownloadDetailsGetter details_getter;
311 if (metadata_file_present_ && item_action_ != REMOVED) {
312 // The file is present, so expect that the callback is invoked with the
313 // details of the test download data written by WriteTestMetadataFile.
314 if (item_action_ == OPENED) {
315 EXPECT_CALL(details_getter,
316 OnDownloadDetails(
317 AllOf(ResultOf(GetDetailsDownloadUrl, StrEq(kTestUrl)),
318 ResultOf(GetDetailsOpenTime, Ne(0)))));
319 } else {
320 EXPECT_CALL(details_getter,
321 OnDownloadDetails(
322 AllOf(ResultOf(GetDetailsDownloadUrl, StrEq(kTestUrl)),
323 ResultOf(GetDetailsOpenTime, Eq(0)))));
325 } else {
326 // No file on disk, so expect that the callback is invoked with null.
327 EXPECT_CALL(details_getter, OnDownloadDetails(IsNull()));
330 // Fire in the hole!
331 manager_.GetDownloadDetails(&profile_, details_getter.GetCallback());
333 // Shutdown the download manager, if relevant.
334 if (early_shutdown_)
335 ShutdownDownloadManager();
337 // Allow the read task and the response callback to run.
338 RunAllTasks();
340 // Shutdown the download manager, if relevant.
341 ShutdownDownloadManager();
344 INSTANTIATE_TEST_CASE_P(
345 DownloadMetadataManager,
346 GetDetailsTest,
347 testing::Combine(
348 testing::Values("absent", "present"),
349 testing::Values("not_managed", "managed"),
350 testing::Values("not_created", "created", "opened", "removed"),
351 testing::Values("waiting", "loaded"),
352 testing::Values("normal_shutdown", "early_shutdown")));
354 // A parameterized test that exercises SetRequest. The parameters dictate the
355 // exact state of affairs leading up to the call as follows:
356 // 0: the state of the DownloadMetadata file for the test profile:
357 // "absent": no file is present.
358 // "this": the file corresponds to the item being updated.
359 // "other": the file correponds to a different item.
360 // "unknown": the file corresponds to an item that has not been created.
361 // 1: if "pending", an operation is applied to the item being updated prior to
362 // the call.
363 // 2: if "pending", an operation is applied to a different item prior to the
364 // call.
365 // 3: if "loaded", the task to load the DownloadMetadata file is allowed to
366 // complete.
367 // 4: if "set", the call to SetRequest contains a new request; otherwise it
368 // does not, leading to removal of metadata.
369 class SetRequestTest
370 : public DownloadMetadataManagerTestBase,
371 public ::testing::WithParamInterface<testing::tuple<const char*,
372 const char*,
373 const char*,
374 const char*,
375 const char*>> {
376 protected:
377 enum MetadataFilePresent {
378 ABSENT,
379 PRESENT_FOR_THIS_ITEM,
380 PRESENT_FOR_OTHER_ITEM,
381 PRESENT_FOR_UNKNOWN_ITEM,
383 SetRequestTest()
384 : metadata_file_present_(ABSENT),
385 same_ops_(),
386 other_ops_(),
387 details_loaded_(),
388 set_request_() {}
390 void SetUp() override {
391 DownloadMetadataManagerTestBase::SetUp();
392 const std::string present(testing::get<0>(GetParam()));
393 metadata_file_present_ = (present == "absent" ? ABSENT :
394 (present == "this" ? PRESENT_FOR_THIS_ITEM :
395 (present == "other" ? PRESENT_FOR_OTHER_ITEM :
396 PRESENT_FOR_UNKNOWN_ITEM)));
397 same_ops_ = (std::string(testing::get<1>(GetParam())) == "pending");
398 other_ops_ = (std::string(testing::get<2>(GetParam())) == "pending");
399 details_loaded_ = (std::string(testing::get<3>(GetParam())) == "loaded");
400 set_request_ = (std::string(testing::get<4>(GetParam())) == "set");
403 MetadataFilePresent metadata_file_present_;
404 bool same_ops_;
405 bool other_ops_;
406 bool details_loaded_;
407 bool set_request_;
410 // Tests that DownloadMetadataManager::SetRequest works for all combinations of
411 // states.
412 TEST_P(SetRequestTest, SetRequest) {
413 // Optionally put a metadata file in the profile directory.
414 switch (metadata_file_present_) {
415 case ABSENT:
416 break;
417 case PRESENT_FOR_THIS_ITEM:
418 WriteTestMetadataFile();
419 break;
420 case PRESENT_FOR_OTHER_ITEM:
421 WriteTestMetadataFileForItem(kOtherDownloadId);
422 break;
423 case PRESENT_FOR_UNKNOWN_ITEM:
424 WriteTestMetadataFileForItem(kCrazyDowloadId);
425 break;
428 AddDownloadManager();
429 AddDownloadItems();
431 // Optionally allow the task to read the file to complete.
432 if (details_loaded_) {
433 RunAllTasks();
434 } else {
435 // Optionally add pending operations if the load is outstanding.
436 if (same_ops_)
437 test_item_->NotifyObserversDownloadOpened();
438 if (other_ops_)
439 other_item_->NotifyObserversDownloadOpened();
442 static const char kNewUrl[] = "http://blorf";
443 scoped_ptr<ClientDownloadRequest> request;
444 if (set_request_)
445 request = MakeTestRequest(kNewUrl).Pass();
446 else
447 request.reset();
448 manager_.SetRequest(test_item_.get(), request.get());
450 // Allow the write or remove task to run.
451 RunAllTasks();
453 MockDownloadDetailsGetter details_getter;
454 if (set_request_) {
455 // Expect that the callback is invoked with details for this item.
456 EXPECT_CALL(
457 details_getter,
458 OnDownloadDetails(ResultOf(GetDetailsDownloadUrl, StrEq(kNewUrl))));
459 } else {
460 // Expect that the callback is invoked with null to clear stale metadata.
461 EXPECT_CALL(details_getter, OnDownloadDetails(IsNull()));
463 manager_.GetDownloadDetails(&profile_, details_getter.GetCallback());
465 ShutdownDownloadManager();
467 scoped_ptr<DownloadMetadata> metadata(ReadTestMetadataFile());
468 if (set_request_) {
469 // Expect that the file contains metadata for the download.
470 ASSERT_TRUE(metadata);
471 EXPECT_EQ(kTestDownloadId, metadata->download_id());
472 EXPECT_STREQ(kNewUrl, metadata->download().download().url().c_str());
473 } else {
474 // Expect that the file is not present.
475 ASSERT_FALSE(metadata);
479 INSTANTIATE_TEST_CASE_P(
480 DownloadMetadataManager,
481 SetRequestTest,
482 testing::Combine(testing::Values("absent", "this", "other", "unknown"),
483 testing::Values("none", "pending"),
484 testing::Values("none", "pending"),
485 testing::Values("waiting", "loaded"),
486 testing::Values("clear", "set")));
488 class DownloadMetadataManagerTest : public DownloadMetadataManagerTestBase {
489 protected:
492 // Test that that opening an item when there is no corresponding metadata works.
493 TEST_F(DownloadMetadataManagerTest, OpenDownloadNoMetadata) {
494 // Add a download manager and some items.
495 WriteTestMetadataFile();
496 AddDownloadManager();
497 AddDownloadItems();
499 // Allow the metadata manager to discover that no metadata is available.
500 RunAllTasks();
502 // Clear metadata.
503 manager_.SetRequest(test_item_.get(), nullptr);
505 // Open an item.
506 test_item_->NotifyObserversDownloadOpened();
508 // Shut down.
509 ShutdownDownloadManager();
512 } // namespace safe_browsing