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