1 // Copyright 2019 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/offline_item_utils.h"
6 
7 #include <memory>
8 #include <utility>
9 #include <vector>
10 
11 #include "components/download/public/common/download_utils.h"
12 #include "components/download/public/common/mock_download_item.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14 
15 using ContentId = offline_items_collection::ContentId;
16 using OfflineItem = offline_items_collection::OfflineItem;
17 using OfflineItemFilter = offline_items_collection::OfflineItemFilter;
18 using OfflineItemState = offline_items_collection::OfflineItemState;
19 using OfflineItemProgressUnit =
20     offline_items_collection::OfflineItemProgressUnit;
21 using OfflineItemSchedule = offline_items_collection::OfflineItemSchedule;
22 using FailState = offline_items_collection::FailState;
23 using PendingState = offline_items_collection::PendingState;
24 using DownloadItem = download::DownloadItem;
25 using DownloadSchedule = download::DownloadSchedule;
26 
27 using ::testing::_;
28 using ::testing::Return;
29 using ::testing::ReturnRefOfCopy;
30 
31 namespace {
32 
33 const char kNameSpace[] = "LEGACY_DOWNLOAD";
34 
35 // TODO(https://crbug.com/1042727): Fix test GURL scoping and remove this getter
36 // function.
TestUrl()37 GURL TestUrl() {
38   return GURL("http://www.example.com");
39 }
TestOriginalUrl()40 GURL TestOriginalUrl() {
41   return GURL("http://www.exampleoriginalurl.com");
42 }
43 
44 }  // namespace
45 
46 class OfflineItemUtilsTest : public testing::Test {
47  public:
48   OfflineItemUtilsTest() = default;
49   ~OfflineItemUtilsTest() override = default;
50 
51  protected:
52   std::unique_ptr<download::MockDownloadItem> CreateDownloadItem(
53       const std::string& guid,
54       const base::FilePath& file_path,
55       const base::FilePath& file_name,
56       const std::string& mime_type,
57       DownloadItem::DownloadState state,
58       bool is_paused,
59       bool is_dangerous,
60       const base::Time& creation_time,
61       const base::Time& last_accessed_time,
62       int64_t received_bytes,
63       int64_t total_bytes,
64       download::DownloadInterruptReason interrupt_reason);
65 
66   std::unique_ptr<download::MockDownloadItem> CreateDownloadItem(
67       DownloadItem::DownloadState state,
68       bool is_paused,
69       download::DownloadInterruptReason interrupt_reason);
70 
IsDownloadDone(DownloadItem * item)71   bool IsDownloadDone(DownloadItem* item) {
72     return download::IsDownloadDone(item->GetURL(), item->GetState(),
73                                     item->GetLastReason());
74   }
75 };
76 
77 std::unique_ptr<download::MockDownloadItem>
CreateDownloadItem(const std::string & guid,const base::FilePath & file_path,const base::FilePath & file_name,const std::string & mime_type,DownloadItem::DownloadState state,bool is_paused,bool is_dangerous,const base::Time & creation_time,const base::Time & last_access_time,int64_t received_bytes,int64_t total_bytes,download::DownloadInterruptReason interrupt_reason)78 OfflineItemUtilsTest::CreateDownloadItem(
79     const std::string& guid,
80     const base::FilePath& file_path,
81     const base::FilePath& file_name,
82     const std::string& mime_type,
83     DownloadItem::DownloadState state,
84     bool is_paused,
85     bool is_dangerous,
86     const base::Time& creation_time,
87     const base::Time& last_access_time,
88     int64_t received_bytes,
89     int64_t total_bytes,
90     download::DownloadInterruptReason interrupt_reason) {
91   std::unique_ptr<download::MockDownloadItem> item(
92       new ::testing::NiceMock<download::MockDownloadItem>());
93   ON_CALL(*item, GetURL()).WillByDefault(ReturnRefOfCopy(TestUrl()));
94   ON_CALL(*item, GetTabUrl()).WillByDefault(ReturnRefOfCopy(TestUrl()));
95   ON_CALL(*item, GetOriginalUrl())
96       .WillByDefault(ReturnRefOfCopy(TestOriginalUrl()));
97   ON_CALL(*item, GetDangerType())
98       .WillByDefault(Return(download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS));
99   ON_CALL(*item, GetId()).WillByDefault(Return(0));
100   ON_CALL(*item, GetLastReason()).WillByDefault(Return(interrupt_reason));
101   ON_CALL(*item, GetState()).WillByDefault(Return(state));
102   ON_CALL(*item, GetTargetFilePath()).WillByDefault(ReturnRefOfCopy(file_path));
103   ON_CALL(*item, GetFileNameToReportUser()).WillByDefault(Return(file_name));
104   ON_CALL(*item, GetTransitionType())
105       .WillByDefault(Return(ui::PAGE_TRANSITION_LINK));
106   ON_CALL(*item, IsDangerous()).WillByDefault(Return(is_dangerous));
107   ON_CALL(*item, IsPaused()).WillByDefault(Return(is_paused));
108   ON_CALL(*item, GetGuid()).WillByDefault(ReturnRefOfCopy(guid));
109   ON_CALL(*item, GetMimeType()).WillByDefault(Return(mime_type));
110   ON_CALL(*item, GetStartTime()).WillByDefault(Return(creation_time));
111   ON_CALL(*item, GetLastAccessTime()).WillByDefault(Return(last_access_time));
112   ON_CALL(*item, GetReceivedBytes()).WillByDefault(Return(received_bytes));
113   ON_CALL(*item, GetTotalBytes()).WillByDefault(Return(total_bytes));
114   ON_CALL(*item, IsDone()).WillByDefault(Return(IsDownloadDone(item.get())));
115   ON_CALL(*item, GetDownloadSchedule())
116       .WillByDefault(ReturnRefOfCopy(base::Optional<DownloadSchedule>()));
117   return item;
118 }
119 
120 std::unique_ptr<download::MockDownloadItem>
CreateDownloadItem(DownloadItem::DownloadState state,bool is_paused,download::DownloadInterruptReason interrupt_reason)121 OfflineItemUtilsTest::CreateDownloadItem(
122     DownloadItem::DownloadState state,
123     bool is_paused,
124     download::DownloadInterruptReason interrupt_reason) {
125   std::string guid = "test_guid";
126   base::FilePath file_path(FILE_PATH_LITERAL("/tmp/example_file_path"));
127   base::FilePath file_name(FILE_PATH_LITERAL("example_file_path"));
128   std::string mime_type = "text/html";
129   return CreateDownloadItem(guid, file_path, file_name, mime_type, state,
130                             is_paused, false, base::Time(), base::Time(), 10,
131                             100, interrupt_reason);
132 }
133 
TEST_F(OfflineItemUtilsTest,BasicConversions)134 TEST_F(OfflineItemUtilsTest, BasicConversions) {
135   std::string guid = "test_guid";
136   base::FilePath file_path(FILE_PATH_LITERAL("/tmp/example_file_path"));
137   base::FilePath file_name(FILE_PATH_LITERAL("image.png"));
138   std::string mime_type = "image/png";
139   base::Time creation_time = base::Time::Now();
140   base::Time completion_time = base::Time::Now();
141   base::Time last_access_time = base::Time::Now();
142   download::DownloadInterruptReason interrupt_reason =
143       download::DOWNLOAD_INTERRUPT_REASON_NONE;
144   bool is_transient = true;
145   bool is_accelerated = true;
146   bool externally_removed = true;
147   bool is_openable = true;
148   bool is_resumable = true;
149   bool allow_metered = true;
150   int64_t time_remaining_ms = 10000;
151   bool is_dangerous = true;
152   int64_t total_bytes = 1000;
153   int64_t received_bytes = 10;
154   std::unique_ptr<download::MockDownloadItem> download = CreateDownloadItem(
155       guid, file_path, file_name, mime_type, DownloadItem::COMPLETE, false,
156       is_dangerous, creation_time, last_access_time, 0, 0, interrupt_reason);
157 
158   ON_CALL(*download, IsTransient()).WillByDefault(Return(is_transient));
159   ON_CALL(*download, IsParallelDownload())
160       .WillByDefault(Return(is_accelerated));
161   ON_CALL(*download, GetFileExternallyRemoved())
162       .WillByDefault(Return(externally_removed));
163   ON_CALL(*download, CanOpenDownload()).WillByDefault(Return(is_openable));
164   ON_CALL(*download, CanResume()).WillByDefault(Return(is_resumable));
165   ON_CALL(*download, AllowMetered()).WillByDefault(Return(allow_metered));
166   ON_CALL(*download, GetReceivedBytes()).WillByDefault(Return(received_bytes));
167   ON_CALL(*download, GetTotalBytes()).WillByDefault(Return(total_bytes));
168   ON_CALL(*download, GetEndTime()).WillByDefault(Return(completion_time));
169 
170   ON_CALL(*download, TimeRemaining(_))
171       .WillByDefault(testing::DoAll(
172           testing::SetArgPointee<0>(
173               base::TimeDelta::FromMilliseconds(time_remaining_ms)),
174           Return(true)));
175   ON_CALL(*download, IsDangerous()).WillByDefault(Return(is_dangerous));
176 
177   OfflineItem offline_item =
178       OfflineItemUtils::CreateOfflineItem(kNameSpace, download.get());
179 
180   EXPECT_EQ(ContentId(kNameSpace, guid), offline_item.id);
181   EXPECT_EQ(file_name.AsUTF8Unsafe(), offline_item.title);
182   EXPECT_EQ(file_name.AsUTF8Unsafe(), offline_item.description);
183   EXPECT_EQ(OfflineItemFilter::FILTER_IMAGE, offline_item.filter);
184   EXPECT_EQ(is_transient, offline_item.is_transient);
185   EXPECT_FALSE(offline_item.is_suggested);
186   EXPECT_EQ(is_accelerated, offline_item.is_accelerated);
187   EXPECT_FALSE(offline_item.promote_origin);
188   EXPECT_TRUE(offline_item.can_rename);
189 
190   EXPECT_EQ(total_bytes, offline_item.total_size_bytes);
191   EXPECT_EQ(externally_removed, offline_item.externally_removed);
192   EXPECT_EQ(creation_time, offline_item.creation_time);
193   EXPECT_EQ(completion_time, offline_item.completion_time);
194   EXPECT_EQ(last_access_time, offline_item.last_accessed_time);
195   EXPECT_EQ(is_openable, offline_item.is_openable);
196   EXPECT_EQ(file_path, offline_item.file_path);
197   EXPECT_EQ(mime_type, offline_item.mime_type);
198 
199   EXPECT_EQ(TestUrl(), offline_item.page_url);
200   EXPECT_EQ(TestOriginalUrl(), offline_item.original_url);
201   EXPECT_FALSE(offline_item.is_off_the_record);
202   EXPECT_EQ("", offline_item.attribution);
203 
204   EXPECT_EQ(OfflineItemState::COMPLETE, offline_item.state);
205   EXPECT_EQ(FailState::NO_FAILURE, offline_item.fail_state);
206   EXPECT_EQ(PendingState::NOT_PENDING, offline_item.pending_state);
207   EXPECT_EQ(is_resumable, offline_item.is_resumable);
208   EXPECT_EQ(allow_metered, offline_item.allow_metered);
209   EXPECT_EQ(received_bytes, offline_item.received_bytes);
210   EXPECT_EQ(received_bytes, offline_item.progress.value);
211   ASSERT_TRUE(offline_item.progress.max.has_value());
212   EXPECT_EQ(total_bytes, offline_item.progress.max.value());
213   EXPECT_EQ(OfflineItemProgressUnit::BYTES, offline_item.progress.unit);
214   EXPECT_EQ(time_remaining_ms, offline_item.time_remaining_ms);
215   EXPECT_EQ(is_dangerous, offline_item.is_dangerous);
216 }
217 
TEST_F(OfflineItemUtilsTest,StateConversions)218 TEST_F(OfflineItemUtilsTest, StateConversions) {
219   // in-progress
220   std::unique_ptr<download::MockDownloadItem> download1 =
221       CreateDownloadItem(DownloadItem::IN_PROGRESS, false,
222                          download::DOWNLOAD_INTERRUPT_REASON_NONE);
223 
224   // cancelled
225   std::unique_ptr<download::MockDownloadItem> download2 = CreateDownloadItem(
226       DownloadItem::CANCELLED, false, download::DOWNLOAD_INTERRUPT_REASON_NONE);
227 
228   // complete
229   std::unique_ptr<download::MockDownloadItem> download3 = CreateDownloadItem(
230       DownloadItem::COMPLETE, false, download::DOWNLOAD_INTERRUPT_REASON_NONE);
231 
232   // interrupted, but auto-resumable
233   std::unique_ptr<download::MockDownloadItem> download4 =
234       CreateDownloadItem(DownloadItem::INTERRUPTED, false,
235                          download::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT);
236 
237   // paused
238   std::unique_ptr<download::MockDownloadItem> download5 =
239       CreateDownloadItem(DownloadItem::IN_PROGRESS, true,
240                          download::DOWNLOAD_INTERRUPT_REASON_NONE);
241 
242   // paused, but interrupted
243   std::unique_ptr<download::MockDownloadItem> download6 =
244       CreateDownloadItem(DownloadItem::INTERRUPTED, true,
245                          download::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT);
246 
247   // interrupted, but invalid resumption mode
248   std::unique_ptr<download::MockDownloadItem> download7 = CreateDownloadItem(
249       DownloadItem::INTERRUPTED, false,
250       download::DOWNLOAD_INTERRUPT_REASON_FILE_SAME_AS_SOURCE);
251 
252   // interrupted, not auto-resumable
253   std::unique_ptr<download::MockDownloadItem> download8 =
254       CreateDownloadItem(DownloadItem::INTERRUPTED, false,
255                          download::DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE);
256 
257   // interrupted, should be auto-resumable, but max retry count reached
258   std::unique_ptr<download::MockDownloadItem> download9 =
259       CreateDownloadItem(DownloadItem::INTERRUPTED, false,
260                          download::DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE);
261   ON_CALL(*download9, GetAutoResumeCount()).WillByDefault(Return(10));
262 
263   // interrupted, should be auto-resumable, but dangerous
264   std::unique_ptr<download::MockDownloadItem> download10 =
265       CreateDownloadItem(DownloadItem::INTERRUPTED, false,
266                          download::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT);
267   ON_CALL(*download10, IsDangerous()).WillByDefault(Return(true));
268 
269   OfflineItem offline_item1 =
270       OfflineItemUtils::CreateOfflineItem(kNameSpace, download1.get());
271   EXPECT_EQ(OfflineItemState::IN_PROGRESS, offline_item1.state);
272 
273   OfflineItem offline_item2 =
274       OfflineItemUtils::CreateOfflineItem(kNameSpace, download2.get());
275   EXPECT_EQ(OfflineItemState::CANCELLED, offline_item2.state);
276 
277   OfflineItem offline_item3 =
278       OfflineItemUtils::CreateOfflineItem(kNameSpace, download3.get());
279   EXPECT_EQ(OfflineItemState::COMPLETE, offline_item3.state);
280 
281   OfflineItem offline_item4 =
282       OfflineItemUtils::CreateOfflineItem(kNameSpace, download4.get());
283   EXPECT_EQ(OfflineItemState::PENDING, offline_item4.state);
284 
285   OfflineItem offline_item5 =
286       OfflineItemUtils::CreateOfflineItem(kNameSpace, download5.get());
287   EXPECT_EQ(OfflineItemState::PAUSED, offline_item5.state);
288 
289   OfflineItem offline_item6 =
290       OfflineItemUtils::CreateOfflineItem(kNameSpace, download6.get());
291   EXPECT_EQ(OfflineItemState::PAUSED, offline_item6.state);
292 
293   OfflineItem offline_item7 =
294       OfflineItemUtils::CreateOfflineItem(kNameSpace, download7.get());
295   EXPECT_EQ(OfflineItemState::FAILED, offline_item7.state);
296 
297   OfflineItem offline_item8 =
298       OfflineItemUtils::CreateOfflineItem(kNameSpace, download8.get());
299   EXPECT_EQ(OfflineItemState::INTERRUPTED, offline_item8.state);
300 
301   OfflineItem offline_item9 =
302       OfflineItemUtils::CreateOfflineItem(kNameSpace, download9.get());
303   EXPECT_EQ(OfflineItemState::PAUSED, offline_item9.state);
304 
305   OfflineItem offline_item10 =
306       OfflineItemUtils::CreateOfflineItem(kNameSpace, download10.get());
307   EXPECT_EQ(OfflineItemState::INTERRUPTED, offline_item10.state);
308 }
309 
TEST_F(OfflineItemUtilsTest,MimeTypeToFilterConversion)310 TEST_F(OfflineItemUtilsTest, MimeTypeToFilterConversion) {
311   std::string mime_type[5] = {"text/html", "image/png", "video/webm",
312                               "audio/aac", "application/octet-stream"};
313   OfflineItemFilter filter[5] = {
314       OfflineItemFilter::FILTER_DOCUMENT, OfflineItemFilter::FILTER_IMAGE,
315       OfflineItemFilter::FILTER_VIDEO, OfflineItemFilter::FILTER_AUDIO,
316       OfflineItemFilter::FILTER_OTHER};
317 
318   for (int i = 0; i < 5; i++) {
319     std::unique_ptr<download::MockDownloadItem> download =
320         CreateDownloadItem(DownloadItem::COMPLETE, false,
321                            download::DOWNLOAD_INTERRUPT_REASON_NONE);
322     ON_CALL(*download, GetMimeType()).WillByDefault(Return(mime_type[i]));
323 
324     OfflineItem offline_item =
325         OfflineItemUtils::CreateOfflineItem(kNameSpace, download.get());
326 
327     EXPECT_EQ(mime_type[i], offline_item.mime_type);
328     EXPECT_EQ(filter[i], offline_item.filter);
329   }
330 }
331 
TEST_F(OfflineItemUtilsTest,PendingAndFailedStates)332 TEST_F(OfflineItemUtilsTest, PendingAndFailedStates) {
333   // interrupted, but auto-resumable
334   std::unique_ptr<download::MockDownloadItem> download1 =
335       CreateDownloadItem(DownloadItem::INTERRUPTED, false,
336                          download::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT);
337   OfflineItem offline_item1 =
338       OfflineItemUtils::CreateOfflineItem(kNameSpace, download1.get());
339   EXPECT_EQ(OfflineItemState::PENDING, offline_item1.state);
340   EXPECT_EQ(FailState::NETWORK_TIMEOUT, offline_item1.fail_state);
341   EXPECT_EQ(PendingState::PENDING_NETWORK, offline_item1.pending_state);
342 
343   // failed download: interrupted, but invalid resumption mode
344   std::unique_ptr<download::MockDownloadItem> download2 = CreateDownloadItem(
345       DownloadItem::INTERRUPTED, false,
346       download::DOWNLOAD_INTERRUPT_REASON_FILE_SAME_AS_SOURCE);
347   OfflineItem offline_item2 =
348       OfflineItemUtils::CreateOfflineItem(kNameSpace, download2.get());
349   EXPECT_EQ(OfflineItemState::FAILED, offline_item2.state);
350   EXPECT_EQ(FailState::FILE_SAME_AS_SOURCE, offline_item2.fail_state);
351   EXPECT_EQ(PendingState::NOT_PENDING, offline_item2.pending_state);
352 
353   // interrupted, not auto-resumable
354   std::unique_ptr<download::MockDownloadItem> download3 =
355       CreateDownloadItem(DownloadItem::INTERRUPTED, false,
356                          download::DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE);
357   OfflineItem offline_item3 =
358       OfflineItemUtils::CreateOfflineItem(kNameSpace, download3.get());
359   EXPECT_EQ(OfflineItemState::INTERRUPTED, offline_item3.state);
360   EXPECT_EQ(FailState::SERVER_NO_RANGE, offline_item3.fail_state);
361   EXPECT_EQ(PendingState::NOT_PENDING, offline_item3.pending_state);
362 }
363 
TEST_F(OfflineItemUtilsTest,OfflineItemSchedule)364 TEST_F(OfflineItemUtilsTest, OfflineItemSchedule) {
365   auto time = base::Time::Now();
366   std::vector<DownloadSchedule> download_schedules = {{false, time},
367                                                       {true, base::nullopt}};
368 
369   for (const auto& download_schedule : download_schedules) {
370     auto download =
371         CreateDownloadItem(DownloadItem::IN_PROGRESS, false,
372                            download::DOWNLOAD_INTERRUPT_REASON_NONE);
373     base::Optional<DownloadSchedule> copy = download_schedule;
374     ON_CALL(*download, GetDownloadSchedule())
375         .WillByDefault(ReturnRefOfCopy(copy));
376     OfflineItem offline_item =
377         OfflineItemUtils::CreateOfflineItem(kNameSpace, download.get());
378     auto offline_item_schedule = base::make_optional<OfflineItemSchedule>(
379         download_schedule.only_on_wifi(), download_schedule.start_time());
380     EXPECT_EQ(offline_item.schedule, offline_item.schedule);
381   }
382 }
383