• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "chrome/browser/download/download_item_model.h"
6 
7 #include <vector>
8 
9 #include "base/i18n/rtl.h"
10 #include "base/logging.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/strings/string16.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "content/public/test/mock_download_item.h"
16 #include "extensions/common/extension.h"
17 #include "grit/generated_resources.h"
18 #include "testing/gmock/include/gmock/gmock.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/base/resource/resource_bundle.h"
22 #include "ui/base/text/bytes_formatting.h"
23 #include "ui/gfx/font_list.h"
24 #include "ui/gfx/text_utils.h"
25 
26 using content::DownloadItem;
27 using ::testing::Mock;
28 using ::testing::NiceMock;
29 using ::testing::Return;
30 using ::testing::ReturnRefOfCopy;
31 using ::testing::SetArgPointee;
32 using ::testing::_;
33 
34 namespace {
35 
36 // Create a char array that has as many elements as there are download
37 // interrupt reasons. We can then use that in a COMPILE_ASSERT to make sure
38 // that all the interrupt reason codes are accounted for. The reason codes are
39 // unfortunately sparse, making this necessary.
40 char kInterruptReasonCounter[] = {
41   0,                                // content::DOWNLOAD_INTERRUPT_REASON_NONE
42 #define INTERRUPT_REASON(name,value) 0,
43 #include "content/public/browser/download_interrupt_reason_values.h"
44 #undef INTERRUPT_REASON
45 };
46 const size_t kInterruptReasonCount = ARRAYSIZE_UNSAFE(kInterruptReasonCounter);
47 
48 // Default target path for a mock download item in DownloadItemModelTest.
49 const base::FilePath::CharType kDefaultTargetFilePath[] =
50     FILE_PATH_LITERAL("/foo/bar/foo.bar");
51 
52 const base::FilePath::CharType kDefaultDisplayFileName[] =
53     FILE_PATH_LITERAL("foo.bar");
54 
55 // Default URL for a mock download item in DownloadItemModelTest.
56 const char kDefaultURL[] = "http://example.com/foo.bar";
57 
58 class DownloadItemModelTest : public testing::Test {
59  public:
DownloadItemModelTest()60   DownloadItemModelTest()
61       : model_(&item_) {}
62 
~DownloadItemModelTest()63   virtual ~DownloadItemModelTest() {
64   }
65 
66  protected:
67   // Sets up defaults for the download item and sets |model_| to a new
68   // DownloadItemModel that uses the mock download item.
SetupDownloadItemDefaults()69   void SetupDownloadItemDefaults() {
70     ON_CALL(item_, GetReceivedBytes()).WillByDefault(Return(1));
71     ON_CALL(item_, GetTotalBytes()).WillByDefault(Return(2));
72     ON_CALL(item_, TimeRemaining(_)).WillByDefault(Return(false));
73     ON_CALL(item_, GetMimeType()).WillByDefault(Return("text/html"));
74     ON_CALL(item_, AllDataSaved()).WillByDefault(Return(false));
75     ON_CALL(item_, GetOpenWhenComplete()).WillByDefault(Return(false));
76     ON_CALL(item_, GetFileExternallyRemoved()).WillByDefault(Return(false));
77     ON_CALL(item_, GetState())
78         .WillByDefault(Return(DownloadItem::IN_PROGRESS));
79     ON_CALL(item_, GetURL())
80         .WillByDefault(ReturnRefOfCopy(GURL(kDefaultURL)));
81     ON_CALL(item_, GetFileNameToReportUser())
82         .WillByDefault(Return(base::FilePath(kDefaultDisplayFileName)));
83     ON_CALL(item_, GetTargetFilePath())
84         .WillByDefault(ReturnRefOfCopy(base::FilePath(kDefaultTargetFilePath)));
85     ON_CALL(item_, GetTargetDisposition())
86         .WillByDefault(
87             Return(DownloadItem::TARGET_DISPOSITION_OVERWRITE));
88     ON_CALL(item_, IsPaused()).WillByDefault(Return(false));
89   }
90 
SetupInterruptedDownloadItem(content::DownloadInterruptReason reason)91   void SetupInterruptedDownloadItem(content::DownloadInterruptReason reason) {
92     EXPECT_CALL(item_, GetLastReason()).WillRepeatedly(Return(reason));
93     EXPECT_CALL(item_, GetState())
94         .WillRepeatedly(Return(
95             (reason == content::DOWNLOAD_INTERRUPT_REASON_NONE) ?
96                 DownloadItem::IN_PROGRESS :
97                 DownloadItem::INTERRUPTED));
98   }
99 
item()100   content::MockDownloadItem& item() {
101     return item_;
102   }
103 
model()104   DownloadItemModel& model() {
105     return model_;
106   }
107 
108  private:
109   NiceMock<content::MockDownloadItem> item_;
110   DownloadItemModel model_;
111 };
112 
113 }  // namespace
114 
TEST_F(DownloadItemModelTest,InterruptedStatus)115 TEST_F(DownloadItemModelTest, InterruptedStatus) {
116   // Test that we have the correct interrupt status message for downloads that
117   // are in the INTERRUPTED state.
118   const struct TestCase {
119     // The reason.
120     content::DownloadInterruptReason reason;
121 
122     // Expected status string. This will include the progress as well.
123     const char* expected_status;
124   } kTestCases[] = {
125     { content::DOWNLOAD_INTERRUPT_REASON_NONE,
126       "1/2 B" },
127     { content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED,
128       "Failed - Download error" },
129     { content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED,
130       "Failed - Insufficient permissions" },
131     { content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE,
132       "Failed - Disk full" },
133     { content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG,
134       "Failed - Path too long" },
135     { content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE,
136       "Failed - File too large" },
137     { content::DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED,
138       "Failed - Virus detected" },
139     { content::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED,
140       "Failed - Blocked" },
141     { content::DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED,
142       "Failed - Virus scan failed" },
143     { content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT,
144       "Failed - File truncated" },
145     { content::DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR,
146       "Failed - System busy" },
147     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED,
148       "Failed - Network error" },
149     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT,
150       "Failed - Network timeout" },
151     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED,
152       "Failed - Network disconnected" },
153     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN,
154       "Failed - Server unavailable" },
155     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED,
156       "Failed - Server problem" },
157     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE,
158       "Failed - Download error" },
159     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_PRECONDITION,
160       "Failed - Download error" },
161     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT,
162       "Failed - No file" },
163     { content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED,
164       "Cancelled" },
165     { content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN,
166       "Failed - Shutdown" },
167     { content::DOWNLOAD_INTERRUPT_REASON_CRASH,
168       "Failed - Crash" },
169   };
170   COMPILE_ASSERT(kInterruptReasonCount == ARRAYSIZE_UNSAFE(kTestCases),
171                  interrupt_reason_mismatch);
172 
173   SetupDownloadItemDefaults();
174   for (unsigned i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
175     const TestCase& test_case = kTestCases[i];
176     SetupInterruptedDownloadItem(test_case.reason);
177     EXPECT_STREQ(test_case.expected_status,
178                  UTF16ToUTF8(model().GetStatusText()).c_str());
179   }
180 }
181 
182 // Note: This test is currently skipped on Android. See http://crbug.com/139398
TEST_F(DownloadItemModelTest,InterruptTooltip)183 TEST_F(DownloadItemModelTest, InterruptTooltip) {
184   // Test that we have the correct interrupt tooltip for downloads that are in
185   // the INTERRUPTED state.
186   const struct TestCase {
187     // The reason.
188     content::DownloadInterruptReason reason;
189 
190     // Expected tooltip text. The tooltip text for interrupted downloads
191     // typically consist of two lines. One for the filename and one for the
192     // interrupt reason. The returned string contains a newline.
193     const char* expected_tooltip;
194   } kTestCases[] = {
195     { content::DOWNLOAD_INTERRUPT_REASON_NONE,
196       "foo.bar" },
197     { content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED,
198       "foo.bar\nDownload error" },
199     { content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED,
200       "foo.bar\nInsufficient permissions" },
201     { content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE,
202       "foo.bar\nDisk full" },
203     { content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG,
204       "foo.bar\nPath too long" },
205     { content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE,
206       "foo.bar\nFile too large" },
207     { content::DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED,
208       "foo.bar\nVirus detected" },
209     { content::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED,
210       "foo.bar\nBlocked" },
211     { content::DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED,
212       "foo.bar\nVirus scan failed" },
213     { content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT,
214       "foo.bar\nFile truncated" },
215     { content::DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR,
216       "foo.bar\nSystem busy" },
217     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED,
218       "foo.bar\nNetwork error" },
219     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT,
220       "foo.bar\nNetwork timeout" },
221     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED,
222       "foo.bar\nNetwork disconnected" },
223     { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN,
224       "foo.bar\nServer unavailable" },
225     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED,
226       "foo.bar\nServer problem" },
227     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE,
228       "foo.bar\nDownload error" },
229     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_PRECONDITION,
230       "foo.bar\nDownload error" },
231     { content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT,
232       "foo.bar\nNo file" },
233     { content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED,
234       "foo.bar" },
235     { content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN,
236       "foo.bar\nShutdown" },
237     { content::DOWNLOAD_INTERRUPT_REASON_CRASH,
238       "foo.bar\nCrash" },
239   };
240   COMPILE_ASSERT(kInterruptReasonCount == ARRAYSIZE_UNSAFE(kTestCases),
241                  interrupt_reason_mismatch);
242 
243   // Large tooltip width. Should be large enough to accommodate the entire
244   // tooltip without truncation.
245   const int kLargeTooltipWidth = 1000;
246 
247   // Small tooltip width. Small enough to require truncation of most
248   // tooltips. Used to test eliding logic.
249   const int kSmallTooltipWidth = 40;
250 
251   const gfx::FontList& font_list =
252       ui::ResourceBundle::GetSharedInstance().GetFontList(
253           ui::ResourceBundle::BaseFont);
254   SetupDownloadItemDefaults();
255   for (unsigned i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
256     const TestCase& test_case = kTestCases[i];
257     SetupInterruptedDownloadItem(test_case.reason);
258 
259     // GetTooltipText() elides the tooltip so that the text would fit within a
260     // given width. The following test would fail if kLargeTooltipWidth isn't
261     // large enough to accomodate all the strings.
262     EXPECT_STREQ(
263         test_case.expected_tooltip,
264         UTF16ToUTF8(model().GetTooltipText(font_list,
265                                            kLargeTooltipWidth)).c_str());
266 
267     // Check that if the width is small, the returned tooltip only contains
268     // lines of the given width or smaller.
269     std::vector<base::string16> lines;
270     base::string16 truncated_tooltip =
271         model().GetTooltipText(font_list, kSmallTooltipWidth);
272     Tokenize(truncated_tooltip, ASCIIToUTF16("\n"), &lines);
273     for (unsigned i = 0; i < lines.size(); ++i)
274       EXPECT_GE(kSmallTooltipWidth, gfx::GetStringWidth(lines[i], font_list));
275   }
276 }
277 
TEST_F(DownloadItemModelTest,InProgressStatus)278 TEST_F(DownloadItemModelTest, InProgressStatus) {
279   const struct TestCase {
280     int64 received_bytes;               // Return value of GetReceivedBytes().
281     int64 total_bytes;                  // Return value of GetTotalBytes().
282     bool  time_remaining_known;         // If TimeRemaining() is known.
283     bool  open_when_complete;           // GetOpenWhenComplete().
284     bool  is_paused;                    // IsPaused().
285     const char* expected_status;        // Expected status text.
286   } kTestCases[] = {
287     // These are all the valid combinations of the above fields for a download
288     // that is in IN_PROGRESS state. Go through all of them and check the return
289     // value of DownloadItemModel::GetStatusText(). The point isn't to lock down
290     // the status strings, but to make sure we end up with something sane for
291     // all the circumstances we care about.
292     //
293     // For GetReceivedBytes()/GetTotalBytes(), we only check whether each is
294     // non-zero. In addition, if |total_bytes| is zero, then
295     // |time_remaining_known| is also false.
296     //
297     //         .-- .TimeRemaining() is known.
298     //        |       .-- .GetOpenWhenComplete()
299     //        |      |      .---- .IsPaused()
300     { 0, 0, false, false, false, "Starting..." },
301     { 1, 0, false, false, false, "1 B" },
302     { 0, 2, false, false, false, "Starting..." },
303     { 1, 2, false, false, false, "1/2 B" },
304     { 0, 2, true,  false, false, "0/2 B, 10 secs left" },
305     { 1, 2, true,  false, false, "1/2 B, 10 secs left" },
306     { 0, 0, false, true,  false, "Opening when complete" },
307     { 1, 0, false, true,  false, "Opening when complete" },
308     { 0, 2, false, true,  false, "Opening when complete" },
309     { 1, 2, false, true,  false, "Opening when complete" },
310     { 0, 2, true,  true,  false, "Opening in 10 secs..." },
311     { 1, 2, true,  true,  false, "Opening in 10 secs..." },
312     { 0, 0, false, false, true,  "0 B, Paused" },
313     { 1, 0, false, false, true,  "1 B, Paused" },
314     { 0, 2, false, false, true,  "0/2 B, Paused" },
315     { 1, 2, false, false, true,  "1/2 B, Paused" },
316     { 0, 2, true,  false, true,  "0/2 B, Paused" },
317     { 1, 2, true,  false, true,  "1/2 B, Paused" },
318     { 0, 0, false, true,  true,  "0 B, Paused" },
319     { 1, 0, false, true,  true,  "1 B, Paused" },
320     { 0, 2, false, true,  true,  "0/2 B, Paused" },
321     { 1, 2, false, true,  true,  "1/2 B, Paused" },
322     { 0, 2, true,  true,  true,  "0/2 B, Paused" },
323     { 1, 2, true,  true,  true,  "1/2 B, Paused" },
324   };
325 
326   SetupDownloadItemDefaults();
327 
328   for (unsigned i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
329     const TestCase& test_case = kTestCases[i];
330     Mock::VerifyAndClearExpectations(&item());
331     Mock::VerifyAndClearExpectations(&model());
332     EXPECT_CALL(item(), GetReceivedBytes())
333         .WillRepeatedly(Return(test_case.received_bytes));
334     EXPECT_CALL(item(), GetTotalBytes())
335         .WillRepeatedly(Return(test_case.total_bytes));
336     EXPECT_CALL(item(), TimeRemaining(_))
337         .WillRepeatedly(testing::DoAll(
338             testing::SetArgPointee<0>(base::TimeDelta::FromSeconds(10)),
339             Return(test_case.time_remaining_known)));
340     EXPECT_CALL(item(), GetOpenWhenComplete())
341         .WillRepeatedly(Return(test_case.open_when_complete));
342     EXPECT_CALL(item(), IsPaused())
343         .WillRepeatedly(Return(test_case.is_paused));
344 
345     EXPECT_STREQ(test_case.expected_status,
346                  UTF16ToUTF8(model().GetStatusText()).c_str());
347   }
348 }
349 
TEST_F(DownloadItemModelTest,ShouldShowInShelf)350 TEST_F(DownloadItemModelTest, ShouldShowInShelf) {
351   SetupDownloadItemDefaults();
352 
353   // By default the download item should be displayable on the shelf.
354   EXPECT_TRUE(model().ShouldShowInShelf());
355 
356   // Once explicitly set, ShouldShowInShelf() should return the explicit value.
357   model().SetShouldShowInShelf(false);
358   EXPECT_FALSE(model().ShouldShowInShelf());
359 
360   model().SetShouldShowInShelf(true);
361   EXPECT_TRUE(model().ShouldShowInShelf());
362 }
363 
TEST_F(DownloadItemModelTest,ShouldRemoveFromShelfWhenComplete)364 TEST_F(DownloadItemModelTest, ShouldRemoveFromShelfWhenComplete) {
365   const struct TestCase {
366     DownloadItem::DownloadState state;
367     bool is_dangerous;  // Expectation for IsDangerous().
368     bool is_auto_open;  // Expectation for GetOpenWhenComplete().
369     bool auto_opened;   // Whether the download was successfully
370                         // auto-opened. Expecation for GetAutoOpened().
371     bool expected_result;
372   } kTestCases[] = {
373     // All the valid combinations of state, is_dangerous, is_auto_open and
374     // auto_opened.
375     //
376     //                              .--- Is dangerous.
377     //                             |       .--- Auto open or temporary.
378     //                             |      |      .--- Auto opened.
379     //                             |      |      |      .--- Expected result.
380     { DownloadItem::IN_PROGRESS, false, false, false, false},
381     { DownloadItem::IN_PROGRESS, false, true , false, true },
382     { DownloadItem::IN_PROGRESS, true , false, false, false},
383     { DownloadItem::IN_PROGRESS, true , true , false, false},
384     { DownloadItem::COMPLETE,    false, false, false, false},
385     { DownloadItem::COMPLETE,    false, true , false, false},
386     { DownloadItem::COMPLETE,    false, false, true , true },
387     { DownloadItem::COMPLETE,    false, true , true , true },
388     { DownloadItem::CANCELLED,   false, false, false, false},
389     { DownloadItem::CANCELLED,   false, true , false, false},
390     { DownloadItem::CANCELLED,   true , false, false, false},
391     { DownloadItem::CANCELLED,   true , true , false, false},
392     { DownloadItem::INTERRUPTED, false, false, false, false},
393     { DownloadItem::INTERRUPTED, false, true , false, false},
394     { DownloadItem::INTERRUPTED, true , false, false, false},
395     { DownloadItem::INTERRUPTED, true , true , false, false}
396   };
397 
398   SetupDownloadItemDefaults();
399 
400   for (unsigned i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
401     const TestCase& test_case = kTestCases[i];
402     EXPECT_CALL(item(), GetOpenWhenComplete())
403         .WillRepeatedly(Return(test_case.is_auto_open));
404     EXPECT_CALL(item(), GetState())
405         .WillRepeatedly(Return(test_case.state));
406     EXPECT_CALL(item(), IsDangerous())
407         .WillRepeatedly(Return(test_case.is_dangerous));
408     EXPECT_CALL(item(), GetAutoOpened())
409         .WillRepeatedly(Return(test_case.auto_opened));
410 
411     EXPECT_EQ(test_case.expected_result,
412               model().ShouldRemoveFromShelfWhenComplete())
413         << "Test case: " << i;
414     Mock::VerifyAndClearExpectations(&item());
415     Mock::VerifyAndClearExpectations(&model());
416   }
417 }
418