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