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