1// Copyright 2013 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/bind.h"
6#include "base/callback.h"
7#include "base/files/file_path.h"
8#include "base/memory/ref_counted.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/memory/weak_ptr.h"
11#include "base/observer_list.h"
12#include "chrome/browser/download/download_history.h"
13#include "chrome/browser/download/download_service.h"
14#include "chrome/browser/download/download_service_factory.h"
15#include "chrome/browser/download/download_ui_controller.h"
16#include "chrome/browser/history/download_row.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/test/base/chrome_render_view_host_test_harness.h"
19#include "content/public/test/mock_download_item.h"
20#include "content/public/test/mock_download_manager.h"
21#include "testing/gmock/include/gmock/gmock.h"
22#include "testing/gtest/include/gtest/gtest.h"
23
24using content::MockDownloadItem;
25using content::MockDownloadManager;
26using testing::AnyNumber;
27using testing::Assign;
28using testing::Return;
29using testing::ReturnRefOfCopy;
30using testing::SaveArg;
31using testing::_;
32
33namespace {
34
35// A DownloadUIController::Delegate that stores the DownloadItem* for the last
36// download that was sent to the UI.
37class TestDelegate : public DownloadUIController::Delegate {
38 public:
39  explicit TestDelegate(base::WeakPtr<content::DownloadItem*> receiver);
40  virtual ~TestDelegate() {}
41
42 private:
43  virtual void OnNewDownloadReady(content::DownloadItem* item) OVERRIDE;
44
45  base::WeakPtr<content::DownloadItem*> receiver_;
46};
47
48TestDelegate::TestDelegate(base::WeakPtr<content::DownloadItem*> receiver)
49    : receiver_(receiver) {
50}
51
52void TestDelegate::OnNewDownloadReady(content::DownloadItem* item) {
53  if (receiver_.get())
54    *receiver_ = item;
55}
56
57// A DownloadService that returns a custom DownloadHistory.
58class TestDownloadService : public DownloadService {
59 public:
60  explicit TestDownloadService(Profile* profile);
61  virtual ~TestDownloadService();
62
63  void set_download_history(scoped_ptr<DownloadHistory> download_history) {
64    download_history_.swap(download_history);
65  }
66  virtual DownloadHistory* GetDownloadHistory() OVERRIDE;
67
68 private:
69  scoped_ptr<DownloadHistory> download_history_;
70};
71
72TestDownloadService::TestDownloadService(Profile* profile)
73    : DownloadService(profile) {
74}
75
76TestDownloadService::~TestDownloadService() {
77}
78
79DownloadHistory* TestDownloadService::GetDownloadHistory() {
80  return download_history_.get();
81}
82
83// The test fixture:
84class DownloadUIControllerTest : public ChromeRenderViewHostTestHarness {
85 public:
86  DownloadUIControllerTest();
87
88 protected:
89  // testing::Test
90  virtual void SetUp() OVERRIDE;
91
92  // Returns a TestDelegate. Invoking OnNewDownloadReady on the returned
93  // delegate results in the DownloadItem* being stored in |notified_item_|.
94  scoped_ptr<DownloadUIController::Delegate> GetTestDelegate();
95
96  MockDownloadManager* manager() { return manager_.get(); }
97
98  // Returns the DownloadManager::Observer registered by a test case. This is
99  // the DownloadUIController's observer for all current test cases.
100  content::DownloadManager::Observer* manager_observer() {
101    return manager_observer_;
102  }
103
104  // The most recent DownloadItem that was passed into OnNewDownloadReady().
105  content::DownloadItem* notified_item() { return notified_item_; }
106
107  // DownloadHistory performs a query of existing downloads when it is first
108  // instantiated. This method returns the completion callback for that query.
109  // It can be used to inject history downloads.
110  const HistoryService::DownloadQueryCallback& history_query_callback() const {
111    return history_adapter_->download_query_callback_;
112  }
113
114  // DownloadManager::Observer registered by DownloadHistory.
115  content::DownloadManager::Observer* download_history_manager_observer() {
116    return download_history_manager_observer_;
117  }
118
119  scoped_ptr<MockDownloadItem> CreateMockInProgressDownload();
120
121 private:
122  // A private history adapter that stores the DownloadQueryCallback when
123  // QueryDownloads is called.
124  class HistoryAdapter : public DownloadHistory::HistoryAdapter {
125   public:
126    HistoryAdapter() : DownloadHistory::HistoryAdapter(NULL) {}
127    HistoryService::DownloadQueryCallback download_query_callback_;
128
129   private:
130    virtual void QueryDownloads(
131        const HistoryService::DownloadQueryCallback& callback) OVERRIDE {
132      download_query_callback_ = callback;
133    }
134  };
135
136  // Constructs and returns a TestDownloadService.
137  static KeyedService* TestingDownloadServiceFactory(
138      content::BrowserContext* browser_context);
139
140  scoped_ptr<MockDownloadManager> manager_;
141  content::DownloadManager::Observer* download_history_manager_observer_;
142  content::DownloadManager::Observer* manager_observer_;
143  content::DownloadItem* notified_item_;
144  base::WeakPtrFactory<content::DownloadItem*> notified_item_receiver_factory_;
145
146  HistoryAdapter* history_adapter_;
147};
148
149// static
150KeyedService* DownloadUIControllerTest::TestingDownloadServiceFactory(
151    content::BrowserContext* browser_context) {
152  return new TestDownloadService(Profile::FromBrowserContext(browser_context));
153}
154
155DownloadUIControllerTest::DownloadUIControllerTest()
156    : download_history_manager_observer_(NULL),
157      manager_observer_(NULL),
158      notified_item_(NULL),
159      notified_item_receiver_factory_(&notified_item_) {
160}
161
162void DownloadUIControllerTest::SetUp() {
163  ChromeRenderViewHostTestHarness::SetUp();
164
165  manager_.reset(new testing::StrictMock<MockDownloadManager>());
166  EXPECT_CALL(*manager_, AddObserver(_))
167      .WillOnce(SaveArg<0>(&download_history_manager_observer_));
168  EXPECT_CALL(*manager_,
169              RemoveObserver(testing::Eq(
170                  testing::ByRef(download_history_manager_observer_))))
171      .WillOnce(testing::Assign(
172          &download_history_manager_observer_,
173          static_cast<content::DownloadManager::Observer*>(NULL)));
174  EXPECT_CALL(*manager_, GetAllDownloads(_)).Times(AnyNumber());
175
176  scoped_ptr<HistoryAdapter> history_adapter(new HistoryAdapter);
177  history_adapter_ = history_adapter.get();
178  scoped_ptr<DownloadHistory> download_history(new DownloadHistory(
179      manager_.get(),
180      history_adapter.PassAs<DownloadHistory::HistoryAdapter>()));
181  ASSERT_TRUE(download_history_manager_observer_);
182
183  EXPECT_CALL(*manager_, AddObserver(_))
184      .WillOnce(SaveArg<0>(&manager_observer_));
185  EXPECT_CALL(*manager_,
186              RemoveObserver(testing::Eq(testing::ByRef(manager_observer_))))
187      .WillOnce(testing::Assign(
188          &manager_observer_,
189          static_cast<content::DownloadManager::Observer*>(NULL)));
190  TestDownloadService* download_service = static_cast<TestDownloadService*>(
191      DownloadServiceFactory::GetInstance()->SetTestingFactoryAndUse(
192          browser_context(), &TestingDownloadServiceFactory));
193  ASSERT_TRUE(download_service);
194  download_service->set_download_history(download_history.Pass());
195}
196
197scoped_ptr<MockDownloadItem>
198DownloadUIControllerTest::CreateMockInProgressDownload() {
199  scoped_ptr<MockDownloadItem> item(
200      new testing::StrictMock<MockDownloadItem>());
201  EXPECT_CALL(*item, GetBrowserContext())
202      .WillRepeatedly(Return(browser_context()));
203  EXPECT_CALL(*item, GetId()).WillRepeatedly(Return(1));
204  EXPECT_CALL(*item, GetTargetFilePath()).WillRepeatedly(
205      ReturnRefOfCopy(base::FilePath(FILE_PATH_LITERAL("foo"))));
206  EXPECT_CALL(*item, GetFullPath()).WillRepeatedly(
207      ReturnRefOfCopy(base::FilePath(FILE_PATH_LITERAL("foo"))));
208  EXPECT_CALL(*item, GetState())
209      .WillRepeatedly(Return(content::DownloadItem::IN_PROGRESS));
210  EXPECT_CALL(*item, GetUrlChain())
211      .WillRepeatedly(testing::ReturnRefOfCopy(std::vector<GURL>()));
212  EXPECT_CALL(*item, GetReferrerUrl())
213      .WillRepeatedly(testing::ReturnRefOfCopy(GURL()));
214  EXPECT_CALL(*item, GetStartTime()).WillRepeatedly(Return(base::Time()));
215  EXPECT_CALL(*item, GetEndTime()).WillRepeatedly(Return(base::Time()));
216  EXPECT_CALL(*item, GetETag()).WillRepeatedly(ReturnRefOfCopy(std::string()));
217  EXPECT_CALL(*item, GetLastModifiedTime())
218      .WillRepeatedly(ReturnRefOfCopy(std::string()));
219  EXPECT_CALL(*item, GetDangerType())
220      .WillRepeatedly(Return(content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS));
221  EXPECT_CALL(*item, GetLastReason())
222      .WillRepeatedly(Return(content::DOWNLOAD_INTERRUPT_REASON_NONE));
223  EXPECT_CALL(*item, GetReceivedBytes()).WillRepeatedly(Return(0));
224  EXPECT_CALL(*item, GetTotalBytes()).WillRepeatedly(Return(0));
225  EXPECT_CALL(*item, GetTargetDisposition()).WillRepeatedly(
226      Return(content::DownloadItem::TARGET_DISPOSITION_OVERWRITE));
227  EXPECT_CALL(*item, GetOpened()).WillRepeatedly(Return(false));
228  EXPECT_CALL(*item, GetMimeType()).WillRepeatedly(Return(std::string()));
229  EXPECT_CALL(*item, GetURL()).WillRepeatedly(testing::ReturnRefOfCopy(GURL()));
230  EXPECT_CALL(*item, IsTemporary()).WillRepeatedly(Return(false));
231  return item.Pass();
232}
233
234scoped_ptr<DownloadUIController::Delegate>
235DownloadUIControllerTest::GetTestDelegate() {
236  scoped_ptr<DownloadUIController::Delegate> delegate(
237      new TestDelegate(notified_item_receiver_factory_.GetWeakPtr()));
238  return delegate.Pass();
239}
240
241// New downloads should be presented to the UI when GetTargetFilePath() returns
242// a non-empty path.  I.e. once the download target has been determined.
243TEST_F(DownloadUIControllerTest, DownloadUIController_NotifyBasic) {
244  scoped_ptr<MockDownloadItem> item(CreateMockInProgressDownload());
245  DownloadUIController controller(manager(), GetTestDelegate());
246  EXPECT_CALL(*item, GetTargetFilePath())
247      .WillOnce(ReturnRefOfCopy(base::FilePath()));
248
249  ASSERT_TRUE(manager_observer());
250  manager_observer()->OnDownloadCreated(manager(), item.get());
251
252  // The destination for the download hasn't been determined yet. It should not
253  // be displayed.
254  EXPECT_FALSE(notified_item());
255
256  // Once the destination has been determined, then it should be displayed.
257  EXPECT_CALL(*item, GetTargetFilePath())
258      .WillOnce(ReturnRefOfCopy(base::FilePath(FILE_PATH_LITERAL("foo"))));
259  item->NotifyObserversDownloadUpdated();
260
261  EXPECT_EQ(static_cast<content::DownloadItem*>(item.get()), notified_item());
262}
263
264// A download that's created in an interrupted state should also be displayed.
265TEST_F(DownloadUIControllerTest, DownloadUIController_NotifyBasic_Interrupted) {
266  scoped_ptr<MockDownloadItem> item = CreateMockInProgressDownload();
267  DownloadUIController controller(manager(), GetTestDelegate());
268  EXPECT_CALL(*item, GetState())
269      .WillRepeatedly(Return(content::DownloadItem::INTERRUPTED));
270
271  ASSERT_TRUE(manager_observer());
272  manager_observer()->OnDownloadCreated(manager(), item.get());
273  EXPECT_EQ(static_cast<content::DownloadItem*>(item.get()), notified_item());
274}
275
276// Downloads that have a target path on creation and are in the IN_PROGRESS
277// state should be displayed in the UI immediately without requiring an
278// additional OnDownloadUpdated() notification.
279TEST_F(DownloadUIControllerTest, DownloadUIController_NotifyReadyOnCreate) {
280  scoped_ptr<MockDownloadItem> item(CreateMockInProgressDownload());
281  DownloadUIController controller(manager(), GetTestDelegate());
282
283  ASSERT_TRUE(manager_observer());
284  manager_observer()->OnDownloadCreated(manager(), item.get());
285  EXPECT_EQ(static_cast<content::DownloadItem*>(item.get()), notified_item());
286}
287
288// The UI shouldn't be notified of downloads that were restored from history.
289TEST_F(DownloadUIControllerTest, DownloadUIController_HistoryDownload) {
290  DownloadUIController controller(manager(), GetTestDelegate());
291  // DownloadHistory should already have been created. It performs a query of
292  // existing downloads upon creation. We'll use the callback to inject a
293  // history download.
294  ASSERT_FALSE(history_query_callback().is_null());
295
296  // download_history_manager_observer is the DownloadManager::Observer
297  // registered by the DownloadHistory. DownloadHistory relies on the
298  // OnDownloadCreated notification to mark a download as having been restored
299  // from history.
300  ASSERT_TRUE(download_history_manager_observer());
301
302  scoped_ptr<std::vector<history::DownloadRow> > history_downloads;
303  history_downloads.reset(new std::vector<history::DownloadRow>());
304  history_downloads->push_back(history::DownloadRow());
305  history_downloads->front().id = 1;
306
307  std::vector<GURL> url_chain;
308  GURL url;
309  scoped_ptr<MockDownloadItem> item = CreateMockInProgressDownload();
310
311  EXPECT_CALL(*item, GetOriginalMimeType());
312  EXPECT_CALL(*manager(), CheckForHistoryFilesRemoval());
313
314  {
315    testing::InSequence s;
316    testing::MockFunction<void()> mock_function;
317    // DownloadHistory will immediately try to create a download using the info
318    // we push through the query callback. When DownloadManager::CreateDownload
319    // is called, we need to first invoke the OnDownloadCreated callback for
320    // DownloadHistory before returning the DownloadItem since that's the
321    // sequence of events expected by DownloadHistory.
322    base::Closure history_on_created_callback =
323        base::Bind(&content::DownloadManager::Observer::OnDownloadCreated,
324                   base::Unretained(download_history_manager_observer()),
325                   manager(),
326                   item.get());
327    EXPECT_CALL(*manager(), MockCreateDownloadItem(_)).WillOnce(
328        testing::DoAll(testing::InvokeWithoutArgs(&history_on_created_callback,
329                                                  &base::Closure::Run),
330                       Return(item.get())));
331    EXPECT_CALL(mock_function, Call());
332
333    history_query_callback().Run(history_downloads.Pass());
334    mock_function.Call();
335  }
336
337  // Now pass along the notification to the OnDownloadCreated observer from
338  // DownloadUIController. It should ignore the download since it's marked as
339  // being restored from history.
340  ASSERT_TRUE(manager_observer());
341  manager_observer()->OnDownloadCreated(manager(), item.get());
342
343  // Finally, the expectation we've been waiting for:
344  EXPECT_FALSE(notified_item());
345}
346
347} // namespace
348