1 // Copyright 2016 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/content_suggestions_service.h"
6
7 #include <memory>
8 #include <utility>
9 #include <vector>
10
11 #include "base/bind.h"
12 #include "base/macros.h"
13 #include "base/run_loop.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/test/mock_callback.h"
16 #include "base/test/task_environment.h"
17 #include "base/time/default_clock.h"
18 #include "components/feed/core/shared_prefs/pref_names.h"
19 #include "components/ntp_snippets/category_info.h"
20 #include "components/ntp_snippets/category_rankers/constant_category_ranker.h"
21 #include "components/ntp_snippets/category_rankers/fake_category_ranker.h"
22 #include "components/ntp_snippets/category_rankers/mock_category_ranker.h"
23 #include "components/ntp_snippets/category_status.h"
24 #include "components/ntp_snippets/content_suggestion.h"
25 #include "components/ntp_snippets/content_suggestions_provider.h"
26 #include "components/ntp_snippets/mock_content_suggestions_provider.h"
27 #include "components/ntp_snippets/remote/remote_suggestions_provider_impl.h"
28 #include "components/ntp_snippets/user_classifier.h"
29 #include "components/prefs/testing_pref_service.h"
30 #include "testing/gmock/include/gmock/gmock.h"
31 #include "testing/gtest/include/gtest/gtest.h"
32 #include "ui/gfx/image/image.h"
33
34 using testing::_;
35 using testing::ElementsAre;
36 using testing::Eq;
37 using testing::InvokeWithoutArgs;
38 using testing::IsEmpty;
39 using testing::Mock;
40 using testing::Property;
41 using testing::Return;
42 using testing::SizeIs;
43 using testing::StrictMock;
44 using testing::UnorderedElementsAre;
45
46 namespace ntp_snippets {
47
48 namespace {
49
50 class MockServiceObserver : public ContentSuggestionsService::Observer {
51 public:
52 MockServiceObserver() = default;
53 ~MockServiceObserver() override = default;
54
55 MOCK_METHOD1(OnNewSuggestions, void(Category category));
56 MOCK_METHOD2(OnCategoryStatusChanged,
57 void(Category changed_category, CategoryStatus new_status));
58 MOCK_METHOD1(OnSuggestionInvalidated,
59 void(const ContentSuggestion::ID& suggestion_id));
60 MOCK_METHOD0(OnFullRefreshRequired, void());
61 MOCK_METHOD0(ContentSuggestionsServiceShutdown, void());
62
63 private:
64 DISALLOW_COPY_AND_ASSIGN(MockServiceObserver);
65 };
66
67 } // namespace
68
69 class ContentSuggestionsServiceTest : public testing::Test {
70 public:
ContentSuggestionsServiceTest()71 ContentSuggestionsServiceTest()
72 : pref_service_(std::make_unique<TestingPrefServiceSimple>()),
73 category_ranker_(std::make_unique<ConstantCategoryRanker>()) {}
74
SetUp()75 void SetUp() override {
76 RegisterPrefs();
77 CreateContentSuggestionsService(ContentSuggestionsService::State::ENABLED);
78 }
79
TearDown()80 void TearDown() override {
81 service_->Shutdown();
82 service_.reset();
83 }
84
85 // Verifies that exactly the suggestions with the given |numbers| are
86 // returned by the service for the given |category|.
ExpectThatSuggestionsAre(Category category,std::vector<int> numbers)87 void ExpectThatSuggestionsAre(Category category, std::vector<int> numbers) {
88 std::vector<Category> categories = service()->GetCategories();
89 auto position = std::find(categories.begin(), categories.end(), category);
90 if (!numbers.empty()) {
91 EXPECT_NE(categories.end(), position);
92 }
93
94 for (const auto& suggestion :
95 service()->GetSuggestionsForCategory(category)) {
96 std::string id_within_category = suggestion.id().id_within_category();
97 int id;
98 ASSERT_TRUE(base::StringToInt(id_within_category, &id));
99 auto position = std::find(numbers.begin(), numbers.end(), id);
100 if (position == numbers.end()) {
101 ADD_FAILURE() << "Unexpected suggestion with ID " << id;
102 } else {
103 numbers.erase(position);
104 }
105 }
106 for (int number : numbers) {
107 ADD_FAILURE() << "Suggestion number " << number
108 << " not present, though expected";
109 }
110 }
111
112 const std::map<Category, ContentSuggestionsProvider*, Category::CompareByID>&
providers()113 providers() {
114 return service()->providers_by_category_;
115 }
116
117 const std::map<Category, ContentSuggestionsProvider*, Category::CompareByID>&
dismissed_providers()118 dismissed_providers() {
119 return service()->dismissed_providers_by_category_;
120 }
121
MakeRegisteredMockProvider(Category provided_category)122 MockContentSuggestionsProvider* MakeRegisteredMockProvider(
123 Category provided_category) {
124 return MakeRegisteredMockProvider(
125 std::vector<Category>({provided_category}));
126 }
127
MakeRegisteredMockProvider(const std::vector<Category> & provided_categories)128 MockContentSuggestionsProvider* MakeRegisteredMockProvider(
129 const std::vector<Category>& provided_categories) {
130 auto provider =
131 std::make_unique<testing::StrictMock<MockContentSuggestionsProvider>>(
132 service(), provided_categories);
133 MockContentSuggestionsProvider* result = provider.get();
134 service()->RegisterProvider(std::move(provider));
135 return result;
136 }
137
SetCategoryRanker(std::unique_ptr<CategoryRanker> category_ranker)138 void SetCategoryRanker(std::unique_ptr<CategoryRanker> category_ranker) {
139 category_ranker_ = std::move(category_ranker);
140 }
141
142 MOCK_METHOD1(OnImageFetched, void(const gfx::Image&));
143
144 protected:
RegisterPrefs()145 void RegisterPrefs() {
146 ContentSuggestionsService::RegisterProfilePrefs(pref_service_->registry());
147 RemoteSuggestionsProviderImpl::RegisterProfilePrefs(
148 pref_service_->registry());
149 feed::prefs::RegisterFeedSharedProfilePrefs(pref_service_->registry());
150 UserClassifier::RegisterProfilePrefs(pref_service_->registry());
151 }
152
CreateContentSuggestionsService(ContentSuggestionsService::State enabled)153 void CreateContentSuggestionsService(
154 ContentSuggestionsService::State enabled) {
155 ASSERT_FALSE(service_);
156
157 // TODO(jkrcal): Replace by a mock.
158 auto user_classifier = std::make_unique<UserClassifier>(
159 pref_service_.get(), base::DefaultClock::GetInstance());
160
161 service_ = std::make_unique<ContentSuggestionsService>(
162 enabled, /*identity_manager=*/nullptr, /*history_service=*/nullptr,
163 /*large_icon_service=*/nullptr, pref_service_.get(),
164 std::move(category_ranker_), std::move(user_classifier),
165 /*scheduler=*/nullptr);
166 }
167
ResetService()168 void ResetService() {
169 service_->Shutdown();
170 service_.reset();
171 CreateContentSuggestionsService(ContentSuggestionsService::State::ENABLED);
172 }
173
service()174 ContentSuggestionsService* service() { return service_.get(); }
175
176 // Returns a suggestion instance for testing.
CreateSuggestion(Category category,int number)177 ContentSuggestion CreateSuggestion(Category category, int number) {
178 return ContentSuggestion(
179 category, base::NumberToString(number),
180 GURL("http://testsuggestion/" + base::NumberToString(number)));
181 }
182
CreateSuggestions(Category category,const std::vector<int> & numbers)183 std::vector<ContentSuggestion> CreateSuggestions(
184 Category category,
185 const std::vector<int>& numbers) {
186 std::vector<ContentSuggestion> result;
187 for (int number : numbers) {
188 result.push_back(CreateSuggestion(category, number));
189 }
190 return result;
191 }
192
193 private:
194 std::unique_ptr<ContentSuggestionsService> service_;
195 std::unique_ptr<TestingPrefServiceSimple> pref_service_;
196 std::unique_ptr<CategoryRanker> category_ranker_;
197
198 DISALLOW_COPY_AND_ASSIGN(ContentSuggestionsServiceTest);
199 };
200
201 class ContentSuggestionsServiceDisabledTest
202 : public ContentSuggestionsServiceTest {
203 public:
SetUp()204 void SetUp() override {
205 RegisterPrefs();
206 CreateContentSuggestionsService(ContentSuggestionsService::State::DISABLED);
207 }
208 };
209
TEST_F(ContentSuggestionsServiceTest,ShouldRegisterProviders)210 TEST_F(ContentSuggestionsServiceTest, ShouldRegisterProviders) {
211 EXPECT_THAT(service()->state(),
212 Eq(ContentSuggestionsService::State::ENABLED));
213 Category articles_category =
214 Category::FromKnownCategory(KnownCategories::ARTICLES);
215 ASSERT_THAT(providers(), IsEmpty());
216 EXPECT_THAT(service()->GetCategories(), IsEmpty());
217 EXPECT_THAT(service()->GetCategoryStatus(articles_category),
218 Eq(CategoryStatus::NOT_PROVIDED));
219
220 MockContentSuggestionsProvider* provider1 =
221 MakeRegisteredMockProvider(articles_category);
222 provider1->FireCategoryStatusChangedWithCurrentStatus(articles_category);
223 ASSERT_THAT(providers().count(articles_category), Eq(1ul));
224 EXPECT_THAT(providers().at(articles_category), Eq(provider1));
225 EXPECT_THAT(providers().size(), Eq(1ul));
226 EXPECT_THAT(service()->GetCategories(),
227 UnorderedElementsAre(articles_category));
228 EXPECT_THAT(service()->GetCategoryStatus(articles_category),
229 Eq(CategoryStatus::AVAILABLE));
230 }
231
TEST_F(ContentSuggestionsServiceDisabledTest,ShouldDoNothingWhenDisabled)232 TEST_F(ContentSuggestionsServiceDisabledTest, ShouldDoNothingWhenDisabled) {
233 Category articles_category =
234 Category::FromKnownCategory(KnownCategories::ARTICLES);
235 EXPECT_THAT(service()->state(),
236 Eq(ContentSuggestionsService::State::DISABLED));
237 EXPECT_THAT(providers(), IsEmpty());
238 EXPECT_THAT(service()->GetCategoryStatus(articles_category),
239 Eq(CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED));
240 EXPECT_THAT(service()->GetCategories(), IsEmpty());
241 EXPECT_THAT(service()->GetSuggestionsForCategory(articles_category),
242 IsEmpty());
243 }
244
TEST_F(ContentSuggestionsServiceTest,ShouldRedirectFetchSuggestionImage)245 TEST_F(ContentSuggestionsServiceTest, ShouldRedirectFetchSuggestionImage) {
246 Category articles_category =
247 Category::FromKnownCategory(KnownCategories::ARTICLES);
248 MockContentSuggestionsProvider* provider1 =
249 MakeRegisteredMockProvider(articles_category);
250
251 provider1->FireSuggestionsChanged(articles_category,
252 CreateSuggestions(articles_category, {1}));
253 ContentSuggestion::ID suggestion_id(articles_category, "1");
254
255 EXPECT_CALL(*provider1, FetchSuggestionImageMock(suggestion_id, _));
256 service()->FetchSuggestionImage(
257 suggestion_id,
258 base::BindOnce(&ContentSuggestionsServiceTest::OnImageFetched,
259 base::Unretained(this)));
260 }
261
TEST_F(ContentSuggestionsServiceTest,ShouldCallbackEmptyImageForUnavailableProvider)262 TEST_F(ContentSuggestionsServiceTest,
263 ShouldCallbackEmptyImageForUnavailableProvider) {
264 base::test::SingleThreadTaskEnvironment task_environment;
265
266 base::RunLoop run_loop;
267 // Assuming there will never be a category with the id below.
268 ContentSuggestion::ID suggestion_id(Category::FromIDValue(21563), "TestID");
269 EXPECT_CALL(*this, OnImageFetched(Property(&gfx::Image::IsEmpty, Eq(true))))
270 .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
271 service()->FetchSuggestionImage(
272 suggestion_id,
273 base::BindOnce(&ContentSuggestionsServiceTest::OnImageFetched,
274 base::Unretained(this)));
275 run_loop.Run();
276 }
277
TEST_F(ContentSuggestionsServiceTest,ShouldRedirectSuggestionInvalidated)278 TEST_F(ContentSuggestionsServiceTest, ShouldRedirectSuggestionInvalidated) {
279 Category articles_category =
280 Category::FromKnownCategory(KnownCategories::ARTICLES);
281
282 MockContentSuggestionsProvider* provider =
283 MakeRegisteredMockProvider(articles_category);
284 MockServiceObserver observer;
285 service()->AddObserver(&observer);
286
287 provider->FireSuggestionsChanged(
288 articles_category, CreateSuggestions(articles_category, {11, 12, 13}));
289 ExpectThatSuggestionsAre(articles_category, {11, 12, 13});
290
291 ContentSuggestion::ID suggestion_id(articles_category, "12");
292 EXPECT_CALL(observer, OnSuggestionInvalidated(suggestion_id));
293 provider->FireSuggestionInvalidated(suggestion_id);
294 ExpectThatSuggestionsAre(articles_category, {11, 13});
295 Mock::VerifyAndClearExpectations(&observer);
296
297 // Unknown IDs must be forwarded (though no change happens to the service's
298 // internal data structures) because previously opened UIs, which can still
299 // show the invalidated suggestion, must be notified.
300 ContentSuggestion::ID unknown_id(articles_category, "1234");
301 EXPECT_CALL(observer, OnSuggestionInvalidated(unknown_id));
302 provider->FireSuggestionInvalidated(unknown_id);
303 ExpectThatSuggestionsAre(articles_category, {11, 13});
304 Mock::VerifyAndClearExpectations(&observer);
305
306 service()->RemoveObserver(&observer);
307 }
308
TEST_F(ContentSuggestionsServiceTest,ShouldForwardSuggestions)309 TEST_F(ContentSuggestionsServiceTest, ShouldForwardSuggestions) {
310 Category articles_category =
311 Category::FromKnownCategory(KnownCategories::ARTICLES);
312
313 // Create and register providers
314 MockContentSuggestionsProvider* provider1 =
315 MakeRegisteredMockProvider(articles_category);
316 provider1->FireCategoryStatusChangedWithCurrentStatus(articles_category);
317 ASSERT_THAT(providers().count(articles_category), Eq(1ul));
318 EXPECT_THAT(providers().at(articles_category), Eq(provider1));
319
320 // Create and register observer
321 MockServiceObserver observer;
322 service()->AddObserver(&observer);
323
324 // Send suggestions 1 and 2
325 EXPECT_CALL(observer, OnNewSuggestions(articles_category));
326 provider1->FireSuggestionsChanged(
327 articles_category, CreateSuggestions(articles_category, {1, 2}));
328 ExpectThatSuggestionsAre(articles_category, {1, 2});
329 Mock::VerifyAndClearExpectations(&observer);
330
331 // Send them again, make sure they're not reported twice
332 EXPECT_CALL(observer, OnNewSuggestions(articles_category));
333 provider1->FireSuggestionsChanged(
334 articles_category, CreateSuggestions(articles_category, {1, 2}));
335 ExpectThatSuggestionsAre(articles_category, {1, 2});
336 Mock::VerifyAndClearExpectations(&observer);
337
338 // Send suggestion 1 only
339 EXPECT_CALL(observer, OnNewSuggestions(articles_category));
340 provider1->FireSuggestionsChanged(articles_category,
341 CreateSuggestions(articles_category, {1}));
342 ExpectThatSuggestionsAre(articles_category, {1});
343 Mock::VerifyAndClearExpectations(&observer);
344
345 // Shutdown the service
346 EXPECT_CALL(observer, ContentSuggestionsServiceShutdown());
347 service()->Shutdown();
348 service()->RemoveObserver(&observer);
349 // The service will receive two Shutdown() calls.
350 }
351
TEST_F(ContentSuggestionsServiceTest,ShouldNotReturnCategoryInfoForNonexistentCategory)352 TEST_F(ContentSuggestionsServiceTest,
353 ShouldNotReturnCategoryInfoForNonexistentCategory) {
354 Category category =
355 Category::FromKnownCategory(KnownCategories::READING_LIST);
356 base::Optional<CategoryInfo> result = service()->GetCategoryInfo(category);
357 EXPECT_FALSE(result.has_value());
358 }
359
TEST_F(ContentSuggestionsServiceTest,ShouldReturnCategoryInfo)360 TEST_F(ContentSuggestionsServiceTest, ShouldReturnCategoryInfo) {
361 Category category =
362 Category::FromKnownCategory(KnownCategories::READING_LIST);
363 MockContentSuggestionsProvider* provider =
364 MakeRegisteredMockProvider(category);
365 provider->FireCategoryStatusChangedWithCurrentStatus(category);
366 base::Optional<CategoryInfo> result = service()->GetCategoryInfo(category);
367 ASSERT_TRUE(result.has_value());
368 CategoryInfo expected = provider->GetCategoryInfo(category);
369 const CategoryInfo& actual = result.value();
370 EXPECT_THAT(expected.title(), Eq(actual.title()));
371 EXPECT_THAT(expected.card_layout(), Eq(actual.card_layout()));
372 EXPECT_THAT(expected.additional_action(), Eq(actual.additional_action()));
373 }
374
TEST_F(ContentSuggestionsServiceTest,ShouldRegisterNewCategoryOnNewSuggestions)375 TEST_F(ContentSuggestionsServiceTest,
376 ShouldRegisterNewCategoryOnNewSuggestions) {
377 Category category =
378 Category::FromKnownCategory(KnownCategories::READING_LIST);
379 MockContentSuggestionsProvider* provider =
380 MakeRegisteredMockProvider(category);
381 provider->FireCategoryStatusChangedWithCurrentStatus(category);
382 MockServiceObserver observer;
383 service()->AddObserver(&observer);
384
385 // Provider starts providing |new_category| without calling
386 // |OnCategoryStatusChanged|. This is supported for now until further
387 // reconsideration.
388 Category new_category =
389 Category::FromKnownCategory(KnownCategories::ARTICLES);
390 provider->SetProvidedCategories(
391 std::vector<Category>({category, new_category}));
392
393 EXPECT_CALL(observer, OnNewSuggestions(new_category));
394 EXPECT_CALL(observer,
395 OnCategoryStatusChanged(new_category, CategoryStatus::AVAILABLE));
396 provider->FireSuggestionsChanged(new_category,
397 CreateSuggestions(new_category, {1, 2}));
398
399 ExpectThatSuggestionsAre(new_category, {1, 2});
400 ASSERT_THAT(providers().count(category), Eq(1ul));
401 EXPECT_THAT(providers().at(category), Eq(provider));
402 EXPECT_THAT(service()->GetCategoryStatus(category),
403 Eq(CategoryStatus::AVAILABLE));
404 ASSERT_THAT(providers().count(new_category), Eq(1ul));
405 EXPECT_THAT(providers().at(new_category), Eq(provider));
406 EXPECT_THAT(service()->GetCategoryStatus(new_category),
407 Eq(CategoryStatus::AVAILABLE));
408
409 service()->RemoveObserver(&observer);
410 }
411
TEST_F(ContentSuggestionsServiceTest,ShouldRegisterNewCategoryOnCategoryStatusChanged)412 TEST_F(ContentSuggestionsServiceTest,
413 ShouldRegisterNewCategoryOnCategoryStatusChanged) {
414 Category category =
415 Category::FromKnownCategory(KnownCategories::READING_LIST);
416 MockContentSuggestionsProvider* provider =
417 MakeRegisteredMockProvider(category);
418 provider->FireCategoryStatusChangedWithCurrentStatus(category);
419 MockServiceObserver observer;
420 service()->AddObserver(&observer);
421
422 // Provider starts providing |new_category| and calls
423 // |OnCategoryStatusChanged|, but the category is not yet available.
424 Category new_category =
425 Category::FromKnownCategory(KnownCategories::ARTICLES);
426 provider->SetProvidedCategories(
427 std::vector<Category>({category, new_category}));
428 EXPECT_CALL(observer, OnCategoryStatusChanged(new_category,
429 CategoryStatus::INITIALIZING));
430 provider->FireCategoryStatusChanged(new_category,
431 CategoryStatus::INITIALIZING);
432
433 ASSERT_THAT(providers().count(new_category), Eq(1ul));
434 EXPECT_THAT(providers().at(new_category), Eq(provider));
435 ExpectThatSuggestionsAre(new_category, std::vector<int>());
436 EXPECT_THAT(service()->GetCategoryStatus(new_category),
437 Eq(CategoryStatus::INITIALIZING));
438 EXPECT_THAT(service()->GetCategories(),
439 UnorderedElementsAre(category, new_category));
440
441 service()->RemoveObserver(&observer);
442 }
443
TEST_F(ContentSuggestionsServiceTest,ShouldRemoveCategoryWhenNotProvided)444 TEST_F(ContentSuggestionsServiceTest, ShouldRemoveCategoryWhenNotProvided) {
445 Category category =
446 Category::FromKnownCategory(KnownCategories::READING_LIST);
447 MockContentSuggestionsProvider* provider =
448 MakeRegisteredMockProvider(category);
449 MockServiceObserver observer;
450 service()->AddObserver(&observer);
451
452 provider->FireSuggestionsChanged(category,
453 CreateSuggestions(category, {1, 2}));
454 ExpectThatSuggestionsAre(category, {1, 2});
455
456 EXPECT_CALL(observer,
457 OnCategoryStatusChanged(category, CategoryStatus::NOT_PROVIDED));
458 provider->FireCategoryStatusChanged(category, CategoryStatus::NOT_PROVIDED);
459
460 EXPECT_THAT(service()->GetCategoryStatus(category),
461 Eq(CategoryStatus::NOT_PROVIDED));
462 EXPECT_TRUE(service()->GetCategories().empty());
463 ExpectThatSuggestionsAre(category, std::vector<int>());
464
465 service()->RemoveObserver(&observer);
466 }
467
TEST_F(ContentSuggestionsServiceTest,ShouldForwardClearHistoryToCategoryRanker)468 TEST_F(ContentSuggestionsServiceTest,
469 ShouldForwardClearHistoryToCategoryRanker) {
470 auto mock_ranker = std::make_unique<MockCategoryRanker>();
471 MockCategoryRanker* raw_mock_ranker = mock_ranker.get();
472 SetCategoryRanker(std::move(mock_ranker));
473
474 // The service is recreated to pick up the new ranker.
475 ResetService();
476
477 base::Time begin = base::Time::FromTimeT(123),
478 end = base::Time::FromTimeT(456);
479 EXPECT_CALL(*raw_mock_ranker, ClearHistory(begin, end));
480 base::RepeatingCallback<bool(const GURL& url)> filter;
481 service()->ClearHistory(begin, end, filter);
482 }
483
TEST_F(ContentSuggestionsServiceTest,ShouldForwardFetch)484 TEST_F(ContentSuggestionsServiceTest, ShouldForwardFetch) {
485 Category category = Category::FromKnownCategory(KnownCategories::ARTICLES);
486 std::set<std::string> known_suggestions;
487 MockContentSuggestionsProvider* provider =
488 MakeRegisteredMockProvider(category);
489 provider->FireCategoryStatusChangedWithCurrentStatus(category);
490 EXPECT_CALL(*provider, FetchMock(category, known_suggestions, _));
491 service()->Fetch(category, known_suggestions, FetchDoneCallback());
492 }
493
TEST_F(ContentSuggestionsServiceTest,DismissAndRestoreCategory)494 TEST_F(ContentSuggestionsServiceTest, DismissAndRestoreCategory) {
495 // Register a category with one suggestion.
496 Category category = Category::FromKnownCategory(KnownCategories::ARTICLES);
497 MockContentSuggestionsProvider* provider =
498 MakeRegisteredMockProvider(category);
499 provider->FireCategoryStatusChangedWithCurrentStatus(category);
500 provider->FireSuggestionsChanged(category, CreateSuggestions(category, {42}));
501
502 EXPECT_THAT(service()->GetCategories(), UnorderedElementsAre(category));
503 EXPECT_THAT(service()->GetCategoryStatus(category),
504 Eq(CategoryStatus::AVAILABLE));
505 ExpectThatSuggestionsAre(category, {42});
506 EXPECT_THAT(providers().count(category), Eq(1ul));
507 EXPECT_THAT(dismissed_providers(), IsEmpty());
508
509 // Dismissing the category clears the suggestions for it.
510 service()->DismissCategory(category);
511
512 EXPECT_THAT(service()->GetCategories(), IsEmpty());
513 EXPECT_THAT(service()->GetCategoryStatus(category),
514 Eq(CategoryStatus::NOT_PROVIDED));
515 EXPECT_THAT(service()->GetSuggestionsForCategory(category), IsEmpty());
516 EXPECT_THAT(providers(), IsEmpty());
517 EXPECT_THAT(dismissed_providers().count(category), Eq(1ul));
518
519 // Restoring the dismissed category makes it available again but it is still
520 // empty.
521 service()->RestoreDismissedCategories();
522
523 EXPECT_THAT(service()->GetCategories(), UnorderedElementsAre(category));
524 EXPECT_THAT(service()->GetCategoryStatus(category),
525 Eq(CategoryStatus::AVAILABLE));
526 EXPECT_THAT(service()->GetSuggestionsForCategory(category), IsEmpty());
527 EXPECT_THAT(providers().count(category), Eq(1ul));
528 EXPECT_THAT(dismissed_providers(), IsEmpty());
529 }
530
TEST_F(ContentSuggestionsServiceTest,ShouldRestoreDismissedCategories)531 TEST_F(ContentSuggestionsServiceTest, ShouldRestoreDismissedCategories) {
532 // Create and register provider.
533 Category category1 = Category::FromIDValue(1);
534 Category category2 = Category::FromIDValue(2);
535
536 // Setup and verify initial state.
537 MockContentSuggestionsProvider* provider =
538 MakeRegisteredMockProvider({category1, category2});
539 provider->FireCategoryStatusChangedWithCurrentStatus(category1);
540 provider->FireCategoryStatusChangedWithCurrentStatus(category2);
541
542 ASSERT_THAT(service()->GetCategoryStatus(category1),
543 Eq(CategoryStatus::AVAILABLE));
544 ASSERT_THAT(service()->GetCategoryStatus(category2),
545 Eq(CategoryStatus::AVAILABLE));
546
547 // Dismiss all the categories. None should be provided now.
548 service()->DismissCategory(category1);
549 service()->DismissCategory(category2);
550
551 ASSERT_THAT(service()->GetCategoryStatus(category1),
552 Eq(CategoryStatus::NOT_PROVIDED));
553 ASSERT_THAT(service()->GetCategoryStatus(category2),
554 Eq(CategoryStatus::NOT_PROVIDED));
555
556 // Receiving a status change notification should not change anything.
557 provider->FireCategoryStatusChanged(category1, CategoryStatus::AVAILABLE);
558
559 EXPECT_THAT(service()->GetCategoryStatus(category1),
560 Eq(CategoryStatus::NOT_PROVIDED));
561 EXPECT_THAT(service()->GetCategoryStatus(category2),
562 Eq(CategoryStatus::NOT_PROVIDED));
563
564 // Receiving a notification without suggestions should not change anything.
565 provider->FireSuggestionsChanged(category1, std::vector<ContentSuggestion>());
566
567 EXPECT_THAT(service()->GetCategoryStatus(category1),
568 Eq(CategoryStatus::NOT_PROVIDED));
569 EXPECT_THAT(service()->GetCategoryStatus(category2),
570 Eq(CategoryStatus::NOT_PROVIDED));
571
572 // Receiving suggestions should make the notified category available.
573 provider->FireSuggestionsChanged(category1,
574 CreateSuggestions(category1, {1, 2}));
575
576 EXPECT_THAT(service()->GetCategoryStatus(category1),
577 Eq(CategoryStatus::AVAILABLE));
578 EXPECT_THAT(service()->GetCategoryStatus(category2),
579 Eq(CategoryStatus::NOT_PROVIDED));
580 }
581
TEST_F(ContentSuggestionsServiceTest,ShouldRestoreDismissalsFromPrefs)582 TEST_F(ContentSuggestionsServiceTest, ShouldRestoreDismissalsFromPrefs) {
583 // Register a category with one suggestion.
584 Category category = Category::FromKnownCategory(KnownCategories::ARTICLES);
585 MockContentSuggestionsProvider* provider =
586 MakeRegisteredMockProvider(category);
587 provider->FireCategoryStatusChangedWithCurrentStatus(category);
588
589 // For a regular initialisation, the category is not dismissed.
590 ASSERT_FALSE(service()->IsCategoryDismissed(category));
591
592 // Dismiss the category.
593 service()->DismissCategory(category);
594 ASSERT_TRUE(service()->IsCategoryDismissed(category));
595
596 // Simulate a Chrome restart. The category should still be dismissed.
597 ResetService();
598 EXPECT_TRUE(service()->IsCategoryDismissed(category));
599
600 // Ensure that the provider registered at initialisation is used after
601 // restoration.
602 provider = MakeRegisteredMockProvider(category);
603 provider->FireCategoryStatusChangedWithCurrentStatus(category);
604 EXPECT_TRUE(service()->IsCategoryDismissed(category));
605
606 service()->RestoreDismissedCategories();
607 EXPECT_FALSE(service()->IsCategoryDismissed(category));
608 EXPECT_THAT(providers().find(category)->second, Eq(provider));
609 }
610
TEST_F(ContentSuggestionsServiceTest,ShouldReturnCategoriesInOrderToDisplay)611 TEST_F(ContentSuggestionsServiceTest, ShouldReturnCategoriesInOrderToDisplay) {
612 const Category first_category = Category::FromRemoteCategory(1);
613 const Category second_category = Category::FromRemoteCategory(2);
614
615 auto fake_ranker = std::make_unique<FakeCategoryRanker>();
616 FakeCategoryRanker* raw_fake_ranker = fake_ranker.get();
617 SetCategoryRanker(std::move(fake_ranker));
618
619 raw_fake_ranker->SetOrder({first_category, second_category});
620
621 // The service is recreated to pick up the new ranker.
622 ResetService();
623
624 MockContentSuggestionsProvider* provider =
625 MakeRegisteredMockProvider({first_category, second_category});
626 provider->FireCategoryStatusChangedWithCurrentStatus(first_category);
627 provider->FireCategoryStatusChangedWithCurrentStatus(second_category);
628
629 EXPECT_THAT(service()->GetCategories(),
630 ElementsAre(first_category, second_category));
631
632 // The order to display (in the ranker) changes.
633 raw_fake_ranker->SetOrder({second_category, first_category});
634
635 // Categories order should reflect the new order.
636 EXPECT_THAT(service()->GetCategories(),
637 ElementsAre(second_category, first_category));
638 }
639
TEST_F(ContentSuggestionsServiceTest,ShouldForwardDismissedCategoryToCategoryRanker)640 TEST_F(ContentSuggestionsServiceTest,
641 ShouldForwardDismissedCategoryToCategoryRanker) {
642 auto mock_ranker = std::make_unique<MockCategoryRanker>();
643 MockCategoryRanker* raw_mock_ranker = mock_ranker.get();
644 SetCategoryRanker(std::move(mock_ranker));
645
646 // The service is recreated to pick up the new ranker.
647 ResetService();
648
649 Category category = Category::FromKnownCategory(KnownCategories::ARTICLES);
650 MockContentSuggestionsProvider* provider =
651 MakeRegisteredMockProvider(category);
652 provider->FireCategoryStatusChangedWithCurrentStatus(category);
653
654 EXPECT_CALL(*raw_mock_ranker, OnCategoryDismissed(category));
655 service()->DismissCategory(category);
656 }
657
658 } // namespace ntp_snippets
659