1// Copyright (c) 2012 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 "base/files/file_path.h"
6#include "base/files/file_util.h"
7#include "base/files/scoped_temp_dir.h"
8#include "base/message_loop/message_loop.h"
9#include "base/prefs/pref_service.h"
10#include "base/run_loop.h"
11#include "chrome/browser/download/chrome_download_manager_delegate.h"
12#include "chrome/browser/download/download_prefs.h"
13#include "chrome/browser/download/download_target_info.h"
14#include "chrome/common/pref_names.h"
15#include "chrome/test/base/chrome_render_view_host_test_harness.h"
16#include "chrome/test/base/testing_pref_service_syncable.h"
17#include "chrome/test/base/testing_profile.h"
18#include "content/public/browser/download_interrupt_reasons.h"
19#include "content/public/browser/web_contents.h"
20#include "content/public/browser/web_contents_delegate.h"
21#include "content/public/test/mock_download_item.h"
22#include "content/public/test/mock_download_manager.h"
23#include "content/public/test/test_renderer_host.h"
24#include "content/public/test/web_contents_tester.h"
25#include "testing/gmock/include/gmock/gmock.h"
26#include "testing/gtest/include/gtest/gtest.h"
27
28using ::testing::AtMost;
29using ::testing::Invoke;
30using ::testing::Ref;
31using ::testing::Return;
32using ::testing::ReturnPointee;
33using ::testing::ReturnRef;
34using ::testing::ReturnRefOfCopy;
35using ::testing::SetArgPointee;
36using ::testing::WithArg;
37using ::testing::_;
38using content::DownloadItem;
39
40namespace {
41
42class MockWebContentsDelegate : public content::WebContentsDelegate {
43 public:
44  virtual ~MockWebContentsDelegate() {}
45};
46
47// Google Mock action that posts a task to the current message loop that invokes
48// the first argument of the mocked method as a callback. Said argument must be
49// a base::Callback<void(ParamType)>. |result| must be of |ParamType| and is
50// bound as that parameter.
51// Example:
52//   class FooClass {
53//    public:
54//     virtual void Foo(base::Callback<void(bool)> callback);
55//   };
56//   ...
57//   EXPECT_CALL(mock_fooclass_instance, Foo(callback))
58//     .WillOnce(ScheduleCallback(false));
59ACTION_P(ScheduleCallback, result) {
60  base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(arg0, result));
61}
62
63// Similar to ScheduleCallback, but binds 2 arguments.
64ACTION_P2(ScheduleCallback2, result0, result1) {
65  base::MessageLoop::current()->PostTask(
66      FROM_HERE, base::Bind(arg0, result0, result1));
67}
68
69// Subclass of the ChromeDownloadManagerDelegate that uses a mock
70// DownloadProtectionService.
71class TestChromeDownloadManagerDelegate : public ChromeDownloadManagerDelegate {
72 public:
73  explicit TestChromeDownloadManagerDelegate(Profile* profile)
74      : ChromeDownloadManagerDelegate(profile) {
75  }
76
77  virtual ~TestChromeDownloadManagerDelegate() {}
78
79  virtual safe_browsing::DownloadProtectionService*
80      GetDownloadProtectionService() OVERRIDE {
81    return NULL;
82  }
83
84  virtual void NotifyExtensions(
85      content::DownloadItem* download,
86      const base::FilePath& suggested_virtual_path,
87      const NotifyExtensionsCallback& callback) OVERRIDE {
88    callback.Run(base::FilePath(),
89                 DownloadPathReservationTracker::UNIQUIFY);
90  }
91
92  virtual void ReserveVirtualPath(
93      content::DownloadItem* download,
94      const base::FilePath& virtual_path,
95      bool create_directory,
96      DownloadPathReservationTracker::FilenameConflictAction conflict_action,
97      const DownloadPathReservationTracker::ReservedPathCallback& callback)
98      OVERRIDE {
99    // Pretend the path reservation succeeded without any change to
100    // |target_path|.
101    base::MessageLoop::current()->PostTask(
102        FROM_HERE, base::Bind(callback, virtual_path, true));
103  }
104
105  virtual void PromptUserForDownloadPath(
106      DownloadItem* download,
107      const base::FilePath& suggested_path,
108      const DownloadTargetDeterminerDelegate::FileSelectedCallback& callback)
109      OVERRIDE {
110    base::FilePath return_path = MockPromptUserForDownloadPath(download,
111                                                               suggested_path,
112                                                               callback);
113    callback.Run(return_path);
114  }
115
116  MOCK_METHOD3(
117      MockPromptUserForDownloadPath,
118      base::FilePath(
119          content::DownloadItem*,
120          const base::FilePath&,
121          const DownloadTargetDeterminerDelegate::FileSelectedCallback&));
122};
123
124class ChromeDownloadManagerDelegateTest
125    : public ChromeRenderViewHostTestHarness {
126 public:
127  ChromeDownloadManagerDelegateTest();
128
129  // ::testing::Test
130  virtual void SetUp() OVERRIDE;
131  virtual void TearDown() OVERRIDE;
132
133  // Verifies and clears test expectations for |delegate_| and
134  // |download_manager_|.
135  void VerifyAndClearExpectations();
136
137  // Creates MockDownloadItem and sets up default expectations.
138  content::MockDownloadItem* CreateActiveDownloadItem(int32 id);
139
140  // Given the relative path |path|, returns the full path under the temporary
141  // downloads directory.
142  base::FilePath GetPathInDownloadDir(const char* path);
143
144  // Set the kDownloadDefaultDirectory user preference to |path|.
145  void SetDefaultDownloadPath(const base::FilePath& path);
146
147  void DetermineDownloadTarget(DownloadItem* download,
148                               DownloadTargetInfo* result);
149
150  // Invokes ChromeDownloadManagerDelegate::CheckForFileExistence and waits for
151  // the asynchronous callback. The result passed into
152  // content::CheckForFileExistenceCallback is the return value from this
153  // method.
154  bool CheckForFileExistence(DownloadItem* download);
155
156  const base::FilePath& default_download_path() const;
157  TestChromeDownloadManagerDelegate* delegate();
158  content::MockDownloadManager* download_manager();
159  DownloadPrefs* download_prefs();
160
161 private:
162  TestingPrefServiceSyncable* pref_service_;
163  base::ScopedTempDir test_download_dir_;
164  scoped_ptr<content::MockDownloadManager> download_manager_;
165  scoped_ptr<TestChromeDownloadManagerDelegate> delegate_;
166  MockWebContentsDelegate web_contents_delegate_;
167};
168
169ChromeDownloadManagerDelegateTest::ChromeDownloadManagerDelegateTest()
170    : download_manager_(new ::testing::NiceMock<content::MockDownloadManager>) {
171}
172
173void ChromeDownloadManagerDelegateTest::SetUp() {
174  ChromeRenderViewHostTestHarness::SetUp();
175
176  CHECK(profile());
177  delegate_.reset(new TestChromeDownloadManagerDelegate(profile()));
178  delegate_->SetDownloadManager(download_manager_.get());
179  pref_service_ = profile()->GetTestingPrefService();
180  web_contents()->SetDelegate(&web_contents_delegate_);
181
182  ASSERT_TRUE(test_download_dir_.CreateUniqueTempDir());
183  SetDefaultDownloadPath(test_download_dir_.path());
184}
185
186void ChromeDownloadManagerDelegateTest::TearDown() {
187  base::RunLoop().RunUntilIdle();
188  delegate_->Shutdown();
189  ChromeRenderViewHostTestHarness::TearDown();
190}
191
192void ChromeDownloadManagerDelegateTest::VerifyAndClearExpectations() {
193  ::testing::Mock::VerifyAndClearExpectations(delegate_.get());
194}
195
196content::MockDownloadItem*
197    ChromeDownloadManagerDelegateTest::CreateActiveDownloadItem(int32 id) {
198  content::MockDownloadItem* item =
199      new ::testing::NiceMock<content::MockDownloadItem>();
200  ON_CALL(*item, GetBrowserContext())
201      .WillByDefault(Return(profile()));
202  ON_CALL(*item, GetDangerType())
203      .WillByDefault(Return(content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS));
204  ON_CALL(*item, GetForcedFilePath())
205      .WillByDefault(ReturnRefOfCopy(base::FilePath()));
206  ON_CALL(*item, GetFullPath())
207      .WillByDefault(ReturnRefOfCopy(base::FilePath()));
208  ON_CALL(*item, GetHash())
209      .WillByDefault(ReturnRefOfCopy(std::string()));
210  ON_CALL(*item, GetId())
211      .WillByDefault(Return(id));
212  ON_CALL(*item, GetLastReason())
213      .WillByDefault(Return(content::DOWNLOAD_INTERRUPT_REASON_NONE));
214  ON_CALL(*item, GetReferrerUrl())
215      .WillByDefault(ReturnRefOfCopy(GURL()));
216  ON_CALL(*item, GetState())
217      .WillByDefault(Return(DownloadItem::IN_PROGRESS));
218  ON_CALL(*item, GetTargetFilePath())
219      .WillByDefault(ReturnRefOfCopy(base::FilePath()));
220  ON_CALL(*item, GetTransitionType())
221      .WillByDefault(Return(ui::PAGE_TRANSITION_LINK));
222  ON_CALL(*item, GetWebContents())
223      .WillByDefault(Return(web_contents()));
224  ON_CALL(*item, HasUserGesture())
225      .WillByDefault(Return(false));
226  ON_CALL(*item, IsDangerous())
227      .WillByDefault(Return(false));
228  ON_CALL(*item, IsTemporary())
229      .WillByDefault(Return(false));
230  EXPECT_CALL(*download_manager_, GetDownload(id))
231      .WillRepeatedly(Return(item));
232  return item;
233}
234
235base::FilePath ChromeDownloadManagerDelegateTest::GetPathInDownloadDir(
236    const char* relative_path) {
237  base::FilePath full_path =
238      test_download_dir_.path().AppendASCII(relative_path);
239  return full_path.NormalizePathSeparators();
240}
241
242void ChromeDownloadManagerDelegateTest::SetDefaultDownloadPath(
243    const base::FilePath& path) {
244  pref_service_->SetFilePath(prefs::kDownloadDefaultDirectory, path);
245  pref_service_->SetFilePath(prefs::kSaveFileDefaultDirectory, path);
246}
247
248void StoreDownloadTargetInfo(const base::Closure& closure,
249                         DownloadTargetInfo* target_info,
250                         const base::FilePath& target_path,
251                         DownloadItem::TargetDisposition target_disposition,
252                         content::DownloadDangerType danger_type,
253                         const base::FilePath& intermediate_path) {
254  target_info->target_path = target_path;
255  target_info->target_disposition = target_disposition;
256  target_info->danger_type = danger_type;
257  target_info->intermediate_path = intermediate_path;
258  closure.Run();
259}
260
261void ChromeDownloadManagerDelegateTest::DetermineDownloadTarget(
262    DownloadItem* download_item,
263    DownloadTargetInfo* result) {
264  base::RunLoop loop_runner;
265  delegate()->DetermineDownloadTarget(
266      download_item,
267      base::Bind(&StoreDownloadTargetInfo, loop_runner.QuitClosure(), result));
268  loop_runner.Run();
269}
270
271void StoreBoolAndRunClosure(const base::Closure& closure,
272                            bool* result_storage,
273                            bool result) {
274  *result_storage = result;
275  closure.Run();
276}
277
278bool ChromeDownloadManagerDelegateTest::CheckForFileExistence(
279    DownloadItem* download_item) {
280  base::RunLoop loop_runner;
281  bool result = false;
282  delegate()->CheckForFileExistence(
283      download_item,
284      base::Bind(&StoreBoolAndRunClosure, loop_runner.QuitClosure(), &result));
285  loop_runner.Run();
286  return result;
287}
288
289const base::FilePath& ChromeDownloadManagerDelegateTest::default_download_path()
290    const {
291  return test_download_dir_.path();
292}
293
294TestChromeDownloadManagerDelegate*
295    ChromeDownloadManagerDelegateTest::delegate() {
296  return delegate_.get();
297}
298
299content::MockDownloadManager*
300    ChromeDownloadManagerDelegateTest::download_manager() {
301  return download_manager_.get();
302}
303
304DownloadPrefs* ChromeDownloadManagerDelegateTest::download_prefs() {
305  return delegate_->download_prefs();
306}
307
308}  // namespace
309
310TEST_F(ChromeDownloadManagerDelegateTest, StartDownload_LastSavePath) {
311  GURL download_url("http://example.com/foo.txt");
312
313  scoped_ptr<content::MockDownloadItem> save_as_download(
314      CreateActiveDownloadItem(0));
315  EXPECT_CALL(*save_as_download, GetURL())
316      .Times(::testing::AnyNumber())
317      .WillRepeatedly(ReturnRef(download_url));
318  EXPECT_CALL(*save_as_download, GetTargetDisposition())
319      .Times(::testing::AnyNumber())
320      .WillRepeatedly(Return(DownloadItem::TARGET_DISPOSITION_PROMPT));
321
322  scoped_ptr<content::MockDownloadItem> automatic_download(
323      CreateActiveDownloadItem(1));
324  EXPECT_CALL(*automatic_download, GetURL())
325      .Times(::testing::AnyNumber())
326      .WillRepeatedly(ReturnRef(download_url));
327  EXPECT_CALL(*automatic_download, GetTargetDisposition())
328      .Times(::testing::AnyNumber())
329      .WillRepeatedly(Return(DownloadItem::TARGET_DISPOSITION_OVERWRITE));
330
331  {
332    // When the prompt is displayed for the first download, the user selects a
333    // path in a different directory.
334    DownloadTargetInfo result;
335    base::FilePath expected_prompt_path(GetPathInDownloadDir("foo.txt"));
336    base::FilePath user_selected_path(GetPathInDownloadDir("bar/baz.txt"));
337    EXPECT_CALL(*delegate(),
338                MockPromptUserForDownloadPath(save_as_download.get(),
339                                              expected_prompt_path, _))
340        .WillOnce(Return(user_selected_path));
341    DetermineDownloadTarget(save_as_download.get(), &result);
342    EXPECT_EQ(user_selected_path, result.target_path);
343    VerifyAndClearExpectations();
344  }
345
346  {
347    // The prompt path for the second download is the user selected directroy
348    // from the previous download.
349    DownloadTargetInfo result;
350    base::FilePath expected_prompt_path(GetPathInDownloadDir("bar/foo.txt"));
351    EXPECT_CALL(*delegate(),
352                MockPromptUserForDownloadPath(save_as_download.get(),
353                                              expected_prompt_path, _))
354        .WillOnce(Return(base::FilePath()));
355    DetermineDownloadTarget(save_as_download.get(), &result);
356    VerifyAndClearExpectations();
357  }
358
359  {
360    // Start an automatic download. This one should get the default download
361    // path since the last download path only affects Save As downloads.
362    DownloadTargetInfo result;
363    base::FilePath expected_path(GetPathInDownloadDir("foo.txt"));
364    DetermineDownloadTarget(automatic_download.get(), &result);
365    EXPECT_EQ(expected_path, result.target_path);
366    VerifyAndClearExpectations();
367  }
368
369  {
370    // The prompt path for the next download should be the default.
371    download_prefs()->SetSaveFilePath(download_prefs()->DownloadPath());
372    DownloadTargetInfo result;
373    base::FilePath expected_prompt_path(GetPathInDownloadDir("foo.txt"));
374    EXPECT_CALL(*delegate(),
375                MockPromptUserForDownloadPath(save_as_download.get(),
376                                              expected_prompt_path, _))
377        .WillOnce(Return(base::FilePath()));
378    DetermineDownloadTarget(save_as_download.get(), &result);
379    VerifyAndClearExpectations();
380  }
381}
382
383TEST_F(ChromeDownloadManagerDelegateTest, CheckForFileExistence) {
384  const char kData[] = "helloworld";
385  const size_t kDataLength = sizeof(kData) - 1;
386  base::FilePath existing_path = default_download_path().AppendASCII("foo");
387  base::FilePath non_existent_path =
388      default_download_path().AppendASCII("bar");
389  base::WriteFile(existing_path, kData, kDataLength);
390
391  scoped_ptr<content::MockDownloadItem> download_item(
392      CreateActiveDownloadItem(1));
393  EXPECT_CALL(*download_item, GetTargetFilePath())
394      .WillRepeatedly(ReturnRef(existing_path));
395  EXPECT_TRUE(CheckForFileExistence(download_item.get()));
396
397  download_item.reset(CreateActiveDownloadItem(1));
398  EXPECT_CALL(*download_item, GetTargetFilePath())
399      .WillRepeatedly(ReturnRef(non_existent_path));
400  EXPECT_FALSE(CheckForFileExistence(download_item.get()));
401}
402