1 // Copyright 2017 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 "components/ntp_snippets/remote/prefetched_pages_tracker_impl.h"
6 
7 #include <utility>
8 
9 #include "base/bind.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/test/mock_callback.h"
13 #include "components/ntp_snippets/offline_pages/offline_pages_test_utils.h"
14 #include "components/offline_pages/core/client_namespace_constants.h"
15 #include "components/offline_pages/core/stub_offline_page_model.h"
16 #include "testing/gmock/include/gmock/gmock.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 
19 using ntp_snippets::test::FakeOfflinePageModel;
20 using offline_pages::MultipleOfflinePageItemCallback;
21 using offline_pages::OfflinePageItem;
22 using offline_pages::OfflinePageModel;
23 using offline_pages::PageCriteria;
24 using testing::_;
25 using testing::Eq;
26 using testing::SaveArg;
27 using testing::StrictMock;
28 
29 namespace ntp_snippets {
30 
31 namespace {
32 
33 const int64_t kSystemDownloadId = 0;
34 
35 class MockOfflinePageModel : public offline_pages::StubOfflinePageModel {
36  public:
37   ~MockOfflinePageModel() override = default;
38 
39   MOCK_METHOD2(GetPagesWithCriteria,
40                void(const PageCriteria& criteria,
41                     MultipleOfflinePageItemCallback callback));
42 };
43 
CreateOfflinePageItem(const GURL & url,const std::string & name_space)44 OfflinePageItem CreateOfflinePageItem(const GURL& url,
45                                       const std::string& name_space) {
46   static int id = 0;
47   ++id;
48   return OfflinePageItem(
49       url, id, offline_pages::ClientId(name_space, base::NumberToString(id)),
50       base::FilePath::FromUTF8Unsafe(
51           base::StringPrintf("some/folder/%d.mhtml", id)),
52       0, base::Time::Now());
53 }
54 
55 }  // namespace
56 
57 class PrefetchedPagesTrackerImplTest : public ::testing::Test {
58  public:
59   PrefetchedPagesTrackerImplTest() = default;
60 
fake_offline_page_model()61   FakeOfflinePageModel* fake_offline_page_model() {
62     return &fake_offline_page_model_;
63   }
64 
mock_offline_page_model()65   MockOfflinePageModel* mock_offline_page_model() {
66     return &mock_offline_page_model_;
67   }
68 
69  private:
70   FakeOfflinePageModel fake_offline_page_model_;
71   StrictMock<MockOfflinePageModel> mock_offline_page_model_;
72   DISALLOW_COPY_AND_ASSIGN(PrefetchedPagesTrackerImplTest);
73 };
74 
TEST_F(PrefetchedPagesTrackerImplTest,ShouldRetrievePrefetchedEarlierSuggestionsOnInitialize)75 TEST_F(PrefetchedPagesTrackerImplTest,
76        ShouldRetrievePrefetchedEarlierSuggestionsOnInitialize) {
77   (*fake_offline_page_model()->mutable_items()) = {
78       CreateOfflinePageItem(GURL("http://prefetched.com"),
79                             offline_pages::kSuggestedArticlesNamespace)};
80   PrefetchedPagesTrackerImpl tracker(fake_offline_page_model());
81   tracker.Initialize(base::BindOnce([] {}));
82 
83   ASSERT_FALSE(
84       tracker.PrefetchedOfflinePageExists(GURL("http://not_added_url.com")));
85   EXPECT_TRUE(
86       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
87 }
88 
TEST_F(PrefetchedPagesTrackerImplTest,ShouldAddNewPrefetchedPagesWhenNotified)89 TEST_F(PrefetchedPagesTrackerImplTest,
90        ShouldAddNewPrefetchedPagesWhenNotified) {
91   fake_offline_page_model()->mutable_items()->clear();
92   PrefetchedPagesTrackerImpl tracker(fake_offline_page_model());
93   tracker.Initialize(base::BindOnce([] {}));
94 
95   ASSERT_FALSE(
96       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
97   tracker.OfflinePageAdded(
98       fake_offline_page_model(),
99       CreateOfflinePageItem(GURL("http://prefetched.com"),
100                             offline_pages::kSuggestedArticlesNamespace));
101   EXPECT_TRUE(
102       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
103 }
104 
TEST_F(PrefetchedPagesTrackerImplTest,ShouldIgnoreOtherTypesOfOfflinePagesWhenNotified)105 TEST_F(PrefetchedPagesTrackerImplTest,
106        ShouldIgnoreOtherTypesOfOfflinePagesWhenNotified) {
107   fake_offline_page_model()->mutable_items()->clear();
108   PrefetchedPagesTrackerImpl tracker(fake_offline_page_model());
109   tracker.Initialize(base::BindOnce([] {}));
110 
111   ASSERT_FALSE(tracker.PrefetchedOfflinePageExists(
112       GURL("http://manually_downloaded.com")));
113   tracker.OfflinePageAdded(
114       fake_offline_page_model(),
115       CreateOfflinePageItem(GURL("http://manually_downloaded.com"),
116                             offline_pages::kNTPSuggestionsNamespace));
117   EXPECT_FALSE(tracker.PrefetchedOfflinePageExists(
118       GURL("http://manually_downloaded.com")));
119 }
120 
TEST_F(PrefetchedPagesTrackerImplTest,ShouldIgnoreOtherTypesOfOfflinePagesOnStartup)121 TEST_F(PrefetchedPagesTrackerImplTest,
122        ShouldIgnoreOtherTypesOfOfflinePagesOnStartup) {
123   (*fake_offline_page_model()->mutable_items()) = {
124       CreateOfflinePageItem(GURL("http://manually_downloaded.com"),
125                             offline_pages::kNTPSuggestionsNamespace)};
126   PrefetchedPagesTrackerImpl tracker(fake_offline_page_model());
127   tracker.Initialize(base::BindOnce([] {}));
128 
129   ASSERT_FALSE(tracker.PrefetchedOfflinePageExists(
130       GURL("http://manually_downloaded.com")));
131   EXPECT_FALSE(tracker.PrefetchedOfflinePageExists(
132       GURL("http://manually_downloaded.com")));
133 }
134 
TEST_F(PrefetchedPagesTrackerImplTest,ShouldDeletePrefetchedURLWhenNotified)135 TEST_F(PrefetchedPagesTrackerImplTest, ShouldDeletePrefetchedURLWhenNotified) {
136   const OfflinePageItem item =
137       CreateOfflinePageItem(GURL("http://prefetched.com"),
138                             offline_pages::kSuggestedArticlesNamespace);
139   (*fake_offline_page_model()->mutable_items()) = {item};
140   PrefetchedPagesTrackerImpl tracker(fake_offline_page_model());
141   tracker.Initialize(base::BindOnce([] {}));
142 
143   ASSERT_TRUE(
144       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
145   OfflinePageItem deleted_item = item;
146   deleted_item.system_download_id = kSystemDownloadId;
147   tracker.OfflinePageDeleted(deleted_item);
148   EXPECT_FALSE(
149       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
150 }
151 
TEST_F(PrefetchedPagesTrackerImplTest,ShouldIgnoreDeletionOfOtherTypeOfflinePagesWhenNotified)152 TEST_F(PrefetchedPagesTrackerImplTest,
153        ShouldIgnoreDeletionOfOtherTypeOfflinePagesWhenNotified) {
154   const OfflinePageItem prefetched_item =
155       CreateOfflinePageItem(GURL("http://prefetched.com"),
156                             offline_pages::kSuggestedArticlesNamespace);
157   // The URL is intentionally the same.
158   const OfflinePageItem manually_downloaded_item = CreateOfflinePageItem(
159       GURL("http://prefetched.com"), offline_pages::kNTPSuggestionsNamespace);
160   (*fake_offline_page_model()->mutable_items()) = {prefetched_item,
161                                                    manually_downloaded_item};
162   PrefetchedPagesTrackerImpl tracker(fake_offline_page_model());
163   tracker.Initialize(base::BindOnce([] {}));
164 
165   ASSERT_TRUE(
166       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
167   OfflinePageItem deleted_item = manually_downloaded_item;
168   deleted_item.system_download_id = kSystemDownloadId;
169   tracker.OfflinePageDeleted(deleted_item);
170   EXPECT_TRUE(
171       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
172 }
173 
TEST_F(PrefetchedPagesTrackerImplTest,ShouldReportAsNotInitializedBeforeReceivedArticles)174 TEST_F(PrefetchedPagesTrackerImplTest,
175        ShouldReportAsNotInitializedBeforeReceivedArticles) {
176   EXPECT_CALL(*mock_offline_page_model(), GetPagesWithCriteria(_, _));
177   PrefetchedPagesTrackerImpl tracker(mock_offline_page_model());
178   tracker.Initialize(base::BindOnce([] {}));
179   EXPECT_FALSE(tracker.IsInitialized());
180 }
181 
TEST_F(PrefetchedPagesTrackerImplTest,ShouldReportAsInitializedAfterInitialization)182 TEST_F(PrefetchedPagesTrackerImplTest,
183        ShouldReportAsInitializedAfterInitialization) {
184   MultipleOfflinePageItemCallback offline_pages_callback;
185   EXPECT_CALL(*mock_offline_page_model(), GetPagesWithCriteria(_, _))
186       .WillOnce(
187           [&](const PageCriteria&, MultipleOfflinePageItemCallback callback) {
188             offline_pages_callback = std::move(callback);
189           });
190   PrefetchedPagesTrackerImpl tracker(mock_offline_page_model());
191   tracker.Initialize(base::BindOnce([] {}));
192 
193   ASSERT_FALSE(tracker.IsInitialized());
194   std::move(offline_pages_callback).Run(std::vector<OfflinePageItem>());
195   EXPECT_TRUE(tracker.IsInitialized());
196 }
197 
TEST_F(PrefetchedPagesTrackerImplTest,ShouldCallCallbackAfterInitialization)198 TEST_F(PrefetchedPagesTrackerImplTest, ShouldCallCallbackAfterInitialization) {
199   MultipleOfflinePageItemCallback offline_pages_callback;
200   EXPECT_CALL(*mock_offline_page_model(), GetPagesWithCriteria(_, _))
201       .WillOnce(
202           [&](const PageCriteria&, MultipleOfflinePageItemCallback callback) {
203             offline_pages_callback = std::move(callback);
204           });
205   PrefetchedPagesTrackerImpl tracker(mock_offline_page_model());
206 
207   base::MockCallback<base::OnceCallback<void()>>
208       mock_initialization_completed_callback;
209   tracker.Initialize(mock_initialization_completed_callback.Get());
210   EXPECT_CALL(mock_initialization_completed_callback, Run());
211   std::move(offline_pages_callback).Run(std::vector<OfflinePageItem>());
212 }
213 
TEST_F(PrefetchedPagesTrackerImplTest,ShouldCallMultipleCallbacksAfterInitialization)214 TEST_F(PrefetchedPagesTrackerImplTest,
215        ShouldCallMultipleCallbacksAfterInitialization) {
216   MultipleOfflinePageItemCallback offline_pages_callback;
217   EXPECT_CALL(*mock_offline_page_model(), GetPagesWithCriteria(_, _))
218       .WillOnce(
219           [&](const PageCriteria&, MultipleOfflinePageItemCallback callback) {
220             offline_pages_callback = std::move(callback);
221           });
222   PrefetchedPagesTrackerImpl tracker(mock_offline_page_model());
223 
224   base::MockCallback<base::OnceCallback<void()>>
225       first_mock_initialization_completed_callback,
226       second_mock_initialization_completed_callback;
227   tracker.Initialize(first_mock_initialization_completed_callback.Get());
228   tracker.Initialize(second_mock_initialization_completed_callback.Get());
229   EXPECT_CALL(first_mock_initialization_completed_callback, Run());
230   EXPECT_CALL(second_mock_initialization_completed_callback, Run());
231   std::move(offline_pages_callback).Run(std::vector<OfflinePageItem>());
232 }
233 
TEST_F(PrefetchedPagesTrackerImplTest,ShouldCallCallbackImmediatelyIfAlreadyInitialiazed)234 TEST_F(PrefetchedPagesTrackerImplTest,
235        ShouldCallCallbackImmediatelyIfAlreadyInitialiazed) {
236   MultipleOfflinePageItemCallback offline_pages_callback;
237   EXPECT_CALL(*mock_offline_page_model(), GetPagesWithCriteria(_, _))
238       .WillOnce(
239           [&](const PageCriteria&, MultipleOfflinePageItemCallback callback) {
240             offline_pages_callback = std::move(callback);
241           });
242   PrefetchedPagesTrackerImpl tracker(mock_offline_page_model());
243   tracker.Initialize(base::BindOnce([] {}));
244 
245   std::move(offline_pages_callback).Run(std::vector<OfflinePageItem>());
246 
247   base::MockCallback<base::OnceCallback<void()>>
248       mock_initialization_completed_callback;
249   EXPECT_CALL(mock_initialization_completed_callback, Run());
250   tracker.Initialize(mock_initialization_completed_callback.Get());
251 }
252 
TEST_F(PrefetchedPagesTrackerImplTest,ShouldKeepPrefetchedURLAfterDuplicatePageDeleted)253 TEST_F(PrefetchedPagesTrackerImplTest,
254        ShouldKeepPrefetchedURLAfterDuplicatePageDeleted) {
255   const OfflinePageItem first_item =
256       CreateOfflinePageItem(GURL("http://prefetched.com"),
257                             offline_pages::kSuggestedArticlesNamespace);
258   const OfflinePageItem second_item =
259       CreateOfflinePageItem(GURL("http://prefetched.com"),
260                             offline_pages::kSuggestedArticlesNamespace);
261   (*fake_offline_page_model()->mutable_items()) = {first_item, second_item};
262   PrefetchedPagesTrackerImpl tracker(fake_offline_page_model());
263   tracker.Initialize(base::BindOnce([] {}));
264 
265   ASSERT_TRUE(
266       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
267 
268   OfflinePageItem deleted_item = first_item;
269   deleted_item.system_download_id = kSystemDownloadId;
270   tracker.OfflinePageDeleted(deleted_item);
271 
272   // Only one offline page (out of two) has been removed, the remaining one
273   // should be reported here.
274   EXPECT_TRUE(
275       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
276 }
277 
TEST_F(PrefetchedPagesTrackerImplTest,ShouldDeletePrefetchedURLAfterAllItsPagesAreDeleted)278 TEST_F(PrefetchedPagesTrackerImplTest,
279        ShouldDeletePrefetchedURLAfterAllItsPagesAreDeleted) {
280   const OfflinePageItem first_item =
281       CreateOfflinePageItem(GURL("http://prefetched.com"),
282                             offline_pages::kSuggestedArticlesNamespace);
283   const OfflinePageItem second_item =
284       CreateOfflinePageItem(GURL("http://prefetched.com"),
285                             offline_pages::kSuggestedArticlesNamespace);
286   (*fake_offline_page_model()->mutable_items()) = {first_item, second_item};
287   PrefetchedPagesTrackerImpl tracker(fake_offline_page_model());
288   tracker.Initialize(base::BindOnce([] {}));
289 
290   ASSERT_TRUE(
291       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
292 
293   OfflinePageItem first_deleted_item = first_item;
294   first_deleted_item.system_download_id = kSystemDownloadId;
295   tracker.OfflinePageDeleted(first_deleted_item);
296 
297   ASSERT_TRUE(
298       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
299 
300   OfflinePageItem second_deleted_item = second_item;
301   second_deleted_item.system_download_id = kSystemDownloadId;
302   tracker.OfflinePageDeleted(second_deleted_item);
303 
304   // All offline pages have been removed, their absence should be reported here.
305   EXPECT_FALSE(
306       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
307 }
308 
309 }  // namespace ntp_snippets
310