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.
5 #include "chrome/browser/download/download_item_model.h"
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"
26 using content::DownloadItem
;
27 using ::testing::Mock
;
28 using ::testing::NiceMock
;
29 using ::testing::Return
;
30 using ::testing::ReturnRefOfCopy
;
31 using ::testing::SetArgPointee
;
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
46 const size_t kInterruptReasonCount
= ARRAYSIZE_UNSAFE(kInterruptReasonCounter
);
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");
52 const base::FilePath::CharType kDefaultDisplayFileName
[] =
53 FILE_PATH_LITERAL("foo.bar");
55 // Default URL for a mock download item in DownloadItemModelTest.
56 const char kDefaultURL
[] = "http://example.com/foo.bar";
58 class DownloadItemModelTest
: public testing::Test
{
60 DownloadItemModelTest()
63 virtual ~DownloadItemModelTest() {
67 // Sets up defaults for the download item and sets |model_| to a new
68 // DownloadItemModel that uses the mock download item.
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())
87 Return(DownloadItem::TARGET_DISPOSITION_OVERWRITE
));
88 ON_CALL(item_
, IsPaused()).WillByDefault(Return(false));
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
));
100 content::MockDownloadItem
& item() {
104 DownloadItemModel
& model() {
109 NiceMock
<content::MockDownloadItem
> item_
;
110 DownloadItemModel model_
;
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
{
120 content::DownloadInterruptReason reason
;
122 // Expected status string. This will include the progress as well.
123 const char* expected_status
;
125 { content::DOWNLOAD_INTERRUPT_REASON_NONE
,
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_NETWORK_INVALID_REQUEST
,
156 "Failed - Network error" },
157 { content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED
,
158 "Failed - Server problem" },
159 { content::DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE
,
160 "Failed - Download error" },
161 { content::DOWNLOAD_INTERRUPT_REASON_SERVER_PRECONDITION
,
162 "Failed - Download error" },
163 { content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT
,
164 "Failed - No file" },
165 { content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED
,
167 { content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN
,
168 "Failed - Shutdown" },
169 { content::DOWNLOAD_INTERRUPT_REASON_CRASH
,
172 COMPILE_ASSERT(kInterruptReasonCount
== ARRAYSIZE_UNSAFE(kTestCases
),
173 interrupt_reason_mismatch
);
175 SetupDownloadItemDefaults();
176 for (unsigned i
= 0; i
< ARRAYSIZE_UNSAFE(kTestCases
); ++i
) {
177 const TestCase
& test_case
= kTestCases
[i
];
178 SetupInterruptedDownloadItem(test_case
.reason
);
179 EXPECT_STREQ(test_case
.expected_status
,
180 base::UTF16ToUTF8(model().GetStatusText()).c_str());
184 // Note: This test is currently skipped on Android. See http://crbug.com/139398
185 TEST_F(DownloadItemModelTest
, InterruptTooltip
) {
186 // Test that we have the correct interrupt tooltip for downloads that are in
187 // the INTERRUPTED state.
188 const struct TestCase
{
190 content::DownloadInterruptReason reason
;
192 // Expected tooltip text. The tooltip text for interrupted downloads
193 // typically consist of two lines. One for the filename and one for the
194 // interrupt reason. The returned string contains a newline.
195 const char* expected_tooltip
;
197 { content::DOWNLOAD_INTERRUPT_REASON_NONE
,
199 { content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED
,
200 "foo.bar\nDownload error" },
201 { content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED
,
202 "foo.bar\nInsufficient permissions" },
203 { content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE
,
204 "foo.bar\nDisk full" },
205 { content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG
,
206 "foo.bar\nPath too long" },
207 { content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE
,
208 "foo.bar\nFile too large" },
209 { content::DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED
,
210 "foo.bar\nVirus detected" },
211 { content::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED
,
212 "foo.bar\nBlocked" },
213 { content::DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED
,
214 "foo.bar\nVirus scan failed" },
215 { content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT
,
216 "foo.bar\nFile truncated" },
217 { content::DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR
,
218 "foo.bar\nSystem busy" },
219 { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED
,
220 "foo.bar\nNetwork error" },
221 { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT
,
222 "foo.bar\nNetwork timeout" },
223 { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED
,
224 "foo.bar\nNetwork disconnected" },
225 { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN
,
226 "foo.bar\nServer unavailable" },
227 { content::DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST
,
228 "foo.bar\nNetwork error" },
229 { content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED
,
230 "foo.bar\nServer problem" },
231 { content::DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE
,
232 "foo.bar\nDownload error" },
233 { content::DOWNLOAD_INTERRUPT_REASON_SERVER_PRECONDITION
,
234 "foo.bar\nDownload error" },
235 { content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT
,
236 "foo.bar\nNo file" },
237 { content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED
,
239 { content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN
,
240 "foo.bar\nShutdown" },
241 { content::DOWNLOAD_INTERRUPT_REASON_CRASH
,
244 COMPILE_ASSERT(kInterruptReasonCount
== ARRAYSIZE_UNSAFE(kTestCases
),
245 interrupt_reason_mismatch
);
247 // Large tooltip width. Should be large enough to accommodate the entire
248 // tooltip without truncation.
249 const int kLargeTooltipWidth
= 1000;
251 // Small tooltip width. Small enough to require truncation of most
252 // tooltips. Used to test eliding logic.
253 const int kSmallTooltipWidth
= 40;
255 const gfx::FontList
& font_list
=
256 ui::ResourceBundle::GetSharedInstance().GetFontList(
257 ui::ResourceBundle::BaseFont
);
258 SetupDownloadItemDefaults();
259 for (unsigned i
= 0; i
< ARRAYSIZE_UNSAFE(kTestCases
); ++i
) {
260 const TestCase
& test_case
= kTestCases
[i
];
261 SetupInterruptedDownloadItem(test_case
.reason
);
263 // GetTooltipText() elides the tooltip so that the text would fit within a
264 // given width. The following test would fail if kLargeTooltipWidth isn't
265 // large enough to accomodate all the strings.
267 test_case
.expected_tooltip
,
268 base::UTF16ToUTF8(model().GetTooltipText(font_list
,
269 kLargeTooltipWidth
)).c_str());
271 // Check that if the width is small, the returned tooltip only contains
272 // lines of the given width or smaller.
273 std::vector
<base::string16
> lines
;
274 base::string16 truncated_tooltip
=
275 model().GetTooltipText(font_list
, kSmallTooltipWidth
);
276 Tokenize(truncated_tooltip
, base::ASCIIToUTF16("\n"), &lines
);
277 for (unsigned i
= 0; i
< lines
.size(); ++i
)
278 EXPECT_GE(kSmallTooltipWidth
, gfx::GetStringWidth(lines
[i
], font_list
));
282 TEST_F(DownloadItemModelTest
, InProgressStatus
) {
283 const struct TestCase
{
284 int64 received_bytes
; // Return value of GetReceivedBytes().
285 int64 total_bytes
; // Return value of GetTotalBytes().
286 bool time_remaining_known
; // If TimeRemaining() is known.
287 bool open_when_complete
; // GetOpenWhenComplete().
288 bool is_paused
; // IsPaused().
289 const char* expected_status
; // Expected status text.
291 // These are all the valid combinations of the above fields for a download
292 // that is in IN_PROGRESS state. Go through all of them and check the return
293 // value of DownloadItemModel::GetStatusText(). The point isn't to lock down
294 // the status strings, but to make sure we end up with something sane for
295 // all the circumstances we care about.
297 // For GetReceivedBytes()/GetTotalBytes(), we only check whether each is
298 // non-zero. In addition, if |total_bytes| is zero, then
299 // |time_remaining_known| is also false.
301 // .-- .TimeRemaining() is known.
302 // | .-- .GetOpenWhenComplete()
303 // | | .---- .IsPaused()
304 { 0, 0, false, false, false, "Starting..." },
305 { 1, 0, false, false, false, "1 B" },
306 { 0, 2, false, false, false, "Starting..." },
307 { 1, 2, false, false, false, "1/2 B" },
308 { 0, 2, true, false, false, "0/2 B, 10 secs left" },
309 { 1, 2, true, false, false, "1/2 B, 10 secs left" },
310 { 0, 0, false, true, false, "Opening when complete" },
311 { 1, 0, false, true, false, "Opening when complete" },
312 { 0, 2, false, true, false, "Opening when complete" },
313 { 1, 2, false, true, false, "Opening when complete" },
314 { 0, 2, true, true, false, "Opening in 10 secs..." },
315 { 1, 2, true, true, false, "Opening in 10 secs..." },
316 { 0, 0, false, false, true, "0 B, Paused" },
317 { 1, 0, false, false, true, "1 B, Paused" },
318 { 0, 2, false, false, true, "0/2 B, Paused" },
319 { 1, 2, false, false, true, "1/2 B, Paused" },
320 { 0, 2, true, false, true, "0/2 B, Paused" },
321 { 1, 2, true, false, true, "1/2 B, Paused" },
322 { 0, 0, false, true, true, "0 B, Paused" },
323 { 1, 0, false, true, true, "1 B, Paused" },
324 { 0, 2, false, true, true, "0/2 B, Paused" },
325 { 1, 2, false, true, true, "1/2 B, Paused" },
326 { 0, 2, true, true, true, "0/2 B, Paused" },
327 { 1, 2, true, true, true, "1/2 B, Paused" },
330 SetupDownloadItemDefaults();
332 for (unsigned i
= 0; i
< ARRAYSIZE_UNSAFE(kTestCases
); i
++) {
333 const TestCase
& test_case
= kTestCases
[i
];
334 Mock::VerifyAndClearExpectations(&item());
335 Mock::VerifyAndClearExpectations(&model());
336 EXPECT_CALL(item(), GetReceivedBytes())
337 .WillRepeatedly(Return(test_case
.received_bytes
));
338 EXPECT_CALL(item(), GetTotalBytes())
339 .WillRepeatedly(Return(test_case
.total_bytes
));
340 EXPECT_CALL(item(), TimeRemaining(_
))
341 .WillRepeatedly(testing::DoAll(
342 testing::SetArgPointee
<0>(base::TimeDelta::FromSeconds(10)),
343 Return(test_case
.time_remaining_known
)));
344 EXPECT_CALL(item(), GetOpenWhenComplete())
345 .WillRepeatedly(Return(test_case
.open_when_complete
));
346 EXPECT_CALL(item(), IsPaused())
347 .WillRepeatedly(Return(test_case
.is_paused
));
349 EXPECT_STREQ(test_case
.expected_status
,
350 base::UTF16ToUTF8(model().GetStatusText()).c_str());
354 TEST_F(DownloadItemModelTest
, ShouldShowInShelf
) {
355 SetupDownloadItemDefaults();
357 // By default the download item should be displayable on the shelf.
358 EXPECT_TRUE(model().ShouldShowInShelf());
360 // Once explicitly set, ShouldShowInShelf() should return the explicit value.
361 model().SetShouldShowInShelf(false);
362 EXPECT_FALSE(model().ShouldShowInShelf());
364 model().SetShouldShowInShelf(true);
365 EXPECT_TRUE(model().ShouldShowInShelf());
368 TEST_F(DownloadItemModelTest
, ShouldRemoveFromShelfWhenComplete
) {
369 const struct TestCase
{
370 DownloadItem::DownloadState state
;
371 bool is_dangerous
; // Expectation for IsDangerous().
372 bool is_auto_open
; // Expectation for GetOpenWhenComplete().
373 bool auto_opened
; // Whether the download was successfully
374 // auto-opened. Expecation for GetAutoOpened().
375 bool expected_result
;
377 // All the valid combinations of state, is_dangerous, is_auto_open and
380 // .--- Is dangerous.
381 // | .--- Auto open or temporary.
382 // | | .--- Auto opened.
383 // | | | .--- Expected result.
384 { DownloadItem::IN_PROGRESS
, false, false, false, false},
385 { DownloadItem::IN_PROGRESS
, false, true , false, true },
386 { DownloadItem::IN_PROGRESS
, true , false, false, false},
387 { DownloadItem::IN_PROGRESS
, true , true , false, false},
388 { DownloadItem::COMPLETE
, false, false, false, false},
389 { DownloadItem::COMPLETE
, false, true , false, false},
390 { DownloadItem::COMPLETE
, false, false, true , true },
391 { DownloadItem::COMPLETE
, false, true , true , true },
392 { DownloadItem::CANCELLED
, false, false, false, false},
393 { DownloadItem::CANCELLED
, false, true , false, false},
394 { DownloadItem::CANCELLED
, true , false, false, false},
395 { DownloadItem::CANCELLED
, true , true , false, false},
396 { DownloadItem::INTERRUPTED
, false, false, false, false},
397 { DownloadItem::INTERRUPTED
, false, true , false, false},
398 { DownloadItem::INTERRUPTED
, true , false, false, false},
399 { DownloadItem::INTERRUPTED
, true , true , false, false}
402 SetupDownloadItemDefaults();
404 for (unsigned i
= 0; i
< ARRAYSIZE_UNSAFE(kTestCases
); i
++) {
405 const TestCase
& test_case
= kTestCases
[i
];
406 EXPECT_CALL(item(), GetOpenWhenComplete())
407 .WillRepeatedly(Return(test_case
.is_auto_open
));
408 EXPECT_CALL(item(), GetState())
409 .WillRepeatedly(Return(test_case
.state
));
410 EXPECT_CALL(item(), IsDangerous())
411 .WillRepeatedly(Return(test_case
.is_dangerous
));
412 EXPECT_CALL(item(), GetAutoOpened())
413 .WillRepeatedly(Return(test_case
.auto_opened
));
415 EXPECT_EQ(test_case
.expected_result
,
416 model().ShouldRemoveFromShelfWhenComplete())
417 << "Test case: " << i
;
418 Mock::VerifyAndClearExpectations(&item());
419 Mock::VerifyAndClearExpectations(&model());