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