Index: components/ntp_snippets/content_suggestions_service_unittest.cc |
diff --git a/components/ntp_snippets/content_suggestions_service_unittest.cc b/components/ntp_snippets/content_suggestions_service_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6d5d85b50b0b7c483d134371fe6c99d62bde398d |
--- /dev/null |
+++ b/components/ntp_snippets/content_suggestions_service_unittest.cc |
@@ -0,0 +1,427 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "components/ntp_snippets/content_suggestions_service.h" |
+ |
+#include <memory> |
+#include <vector> |
+ |
+#include "base/bind.h" |
+#include "base/macros.h" |
+#include "base/strings/string_number_conversions.h" |
+#include "components/ntp_snippets/content_suggestion.h" |
+#include "components/ntp_snippets/content_suggestions_category_status.h" |
+#include "components/ntp_snippets/content_suggestions_provider.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+#include "ui/gfx/image/image.h" |
+ |
+using testing::Eq; |
+using testing::IsNull; |
+using testing::NotNull; |
+using testing::IsEmpty; |
+using testing::ElementsAre; |
+using testing::Property; |
+using testing::Const; |
+using testing::Mock; |
+using testing::ByRef; |
+using testing::_; |
+ |
+namespace ntp_snippets { |
+ |
+namespace { |
+ |
+// Returns a suggestion instance for testing. |
+ContentSuggestion CreateSuggestion(int number) { |
+ return ContentSuggestion( |
+ base::IntToString(number), |
+ GURL("http://testsuggestion/" + base::IntToString(number))); |
+} |
+ |
+std::vector<ContentSuggestion> CreateSuggestions(std::vector<int> numbers) { |
+ std::vector<ContentSuggestion> result; |
+ for (int number : numbers) { |
+ result.emplace_back(CreateSuggestion(number)); |
+ } |
+ return result; |
+} |
+ |
+class MockProvider : public ContentSuggestionsProvider { |
+ public: |
+ MockProvider(ContentSuggestionsCategory provided_category) |
+ : MockProvider( |
+ std::vector<ContentSuggestionsCategory>({provided_category})){}; |
+ |
+ MockProvider(std::vector<ContentSuggestionsCategory> provided_categories) |
+ : ContentSuggestionsProvider(provided_categories), observer_(nullptr) { |
+ for (ContentSuggestionsCategory category : provided_categories) { |
+ statuses_[category] = ContentSuggestionsCategoryStatus::AVAILABLE; |
+ } |
+ } |
+ |
+ Observer* observer() { return observer_; } |
+ |
+ void SetObserver(Observer* observer) override { observer_ = observer; } |
+ |
+ ContentSuggestionsCategoryStatus GetCategoryStatus( |
+ ContentSuggestionsCategory category) { |
+ return statuses_[category]; |
+ } |
+ |
+ void FireSuggestionsChanged(ContentSuggestionsCategory category, |
+ std::vector<int> numbers) { |
+ observer_->OnNewSuggestions(category, CreateSuggestions(numbers)); |
+ } |
+ |
+ void FireCategoryStatusChanged(ContentSuggestionsCategory category, |
+ ContentSuggestionsCategoryStatus new_status) { |
+ statuses_[category] = new_status; |
+ observer_->OnCategoryStatusChanged(category, new_status); |
+ } |
+ |
+ void FireShutdown() { |
+ observer_->OnProviderShutdown(this); |
+ observer_ = nullptr; |
+ } |
+ |
+ MOCK_METHOD0(ClearCachedSuggestionsForDebugging, void()); |
+ MOCK_METHOD0(ClearDiscardedSuggestionsForDebugging, void()); |
+ MOCK_METHOD1(DiscardSuggestion, void(const std::string& suggestion_id)); |
+ MOCK_METHOD2(FetchSuggestionImage, |
+ void(const std::string& suggestion_id, |
+ const ImageFetchedCallback& callback)); |
+ |
+ private: |
+ Observer* observer_; |
+ std::map<ContentSuggestionsCategory, ContentSuggestionsCategoryStatus> |
+ statuses_; |
+}; |
+ |
+class MockServiceObserver : public ContentSuggestionsService::Observer { |
+ public: |
+ MOCK_METHOD0(OnNewSuggestions, void()); |
+ MOCK_METHOD2(OnCategoryStatusChanged, |
+ void(ContentSuggestionsCategory changed_category, |
+ ContentSuggestionsCategoryStatus new_status)); |
+ MOCK_METHOD0(ContentSuggestionsServiceShutdown, void()); |
+ ~MockServiceObserver() override {} |
+}; |
+ |
+} // namespace |
+ |
+class ContentSuggestionsServiceTest : public testing::Test { |
+ public: |
+ ContentSuggestionsServiceTest() {} |
+ |
+ void SetUp() override { |
+ CreateContentSuggestionsService(ContentSuggestionsService::State::ENABLED); |
+ } |
+ |
+ void TearDown() override { |
+ service_->Shutdown(); |
+ service_.reset(); |
+ } |
+ |
+ // Verifies that exactly the suggestions with the given |numbers| are |
+ // returned by the service for the given |category|. |
+ void ExpectThatSuggestionsAre(ContentSuggestionsCategory category, |
+ std::vector<int> numbers) { |
+ std::vector<ContentSuggestionsCategory> categories = |
+ service()->GetCategories(); |
+ auto position = std::find(categories.begin(), categories.end(), category); |
+ if (!numbers.empty()) { |
+ EXPECT_NE(categories.end(), position); |
+ } |
+ |
+ for (const auto& suggestion : |
+ service()->GetSuggestionsForCategory(category)) { |
+ int id; |
+ ASSERT_TRUE(base::StringToInt(suggestion.id(), &id)); |
+ auto position = std::find(numbers.begin(), numbers.end(), id); |
+ if (position == numbers.end()) { |
+ ADD_FAILURE() << "Unexpected suggestion with ID " << id; |
+ } else { |
+ numbers.erase(position); |
+ } |
+ } |
+ for (int number : numbers) { |
+ ADD_FAILURE() << "Suggestion number " << number |
+ << " not present, though expected"; |
+ } |
+ } |
+ |
+ const std::map<ContentSuggestionsCategory, ContentSuggestionsProvider*>& |
+ providers() { |
+ return service()->providers_; |
+ } |
+ |
+ MOCK_METHOD2(OnImageFetched, |
+ void(const std::string& suggestion_id, const gfx::Image&)); |
+ |
+ protected: |
+ void CreateContentSuggestionsService( |
+ ContentSuggestionsService::State enabled) { |
+ ASSERT_FALSE(service_); |
+ service_.reset(new ContentSuggestionsService(enabled)); |
+ } |
+ |
+ ContentSuggestionsService* service() { return service_.get(); } |
+ |
+ private: |
+ std::unique_ptr<ContentSuggestionsService> service_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(ContentSuggestionsServiceTest); |
+}; |
+ |
+class ContentSuggestionsServiceDisabledTest |
+ : public ContentSuggestionsServiceTest { |
+ public: |
+ void SetUp() override { |
+ CreateContentSuggestionsService(ContentSuggestionsService::State::DISABLED); |
+ } |
+}; |
+ |
+TEST_F(ContentSuggestionsServiceTest, ShouldRegisterProvidersAndShutdown) { |
+ EXPECT_THAT(service()->state(), |
+ Eq(ContentSuggestionsService::State::ENABLED)); |
+ MockProvider provider1(ContentSuggestionsCategory::ARTICLES); |
+ MockProvider provider2(ContentSuggestionsCategory::OFFLINE_PAGES); |
+ ASSERT_THAT(provider1.observer(), IsNull()); |
+ ASSERT_THAT(provider2.observer(), IsNull()); |
+ ASSERT_THAT(providers(), IsEmpty()); |
+ EXPECT_THAT(service()->GetCategories(), IsEmpty()); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::ARTICLES), |
+ Eq(ContentSuggestionsCategoryStatus::NOT_PROVIDED)); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::OFFLINE_PAGES), |
+ Eq(ContentSuggestionsCategoryStatus::NOT_PROVIDED)); |
+ |
+ service()->RegisterProvider(&provider1); |
+ EXPECT_THAT(provider1.observer(), NotNull()); |
+ EXPECT_THAT(providers().count(ContentSuggestionsCategory::OFFLINE_PAGES), |
+ Eq(0ul)); |
+ EXPECT_THAT(providers().at(ContentSuggestionsCategory::ARTICLES), |
+ Eq(&provider1)); |
+ EXPECT_THAT(providers().size(), Eq(1ul)); |
+ EXPECT_THAT(service()->GetCategories(), |
+ ElementsAre(ContentSuggestionsCategory::ARTICLES)); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::ARTICLES), |
+ Eq(ContentSuggestionsCategoryStatus::AVAILABLE)); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::OFFLINE_PAGES), |
+ Eq(ContentSuggestionsCategoryStatus::NOT_PROVIDED)); |
+ |
+ service()->RegisterProvider(&provider2); |
+ EXPECT_THAT(provider1.observer(), NotNull()); |
+ EXPECT_THAT(provider2.observer(), NotNull()); |
+ EXPECT_THAT(providers().at(ContentSuggestionsCategory::ARTICLES), |
+ Eq(&provider1)); |
+ EXPECT_THAT(providers().at(ContentSuggestionsCategory::OFFLINE_PAGES), |
+ Eq(&provider2)); |
+ EXPECT_THAT(providers().size(), Eq(2ul)); |
+ EXPECT_THAT(service()->GetCategories(), |
+ ElementsAre(ContentSuggestionsCategory::ARTICLES, |
+ ContentSuggestionsCategory::OFFLINE_PAGES)); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::ARTICLES), |
+ Eq(ContentSuggestionsCategoryStatus::AVAILABLE)); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::OFFLINE_PAGES), |
+ Eq(ContentSuggestionsCategoryStatus::AVAILABLE)); |
+ |
+ provider1.FireShutdown(); |
+ EXPECT_THAT(providers().count(ContentSuggestionsCategory::ARTICLES), Eq(0ul)); |
+ EXPECT_THAT(providers().at(ContentSuggestionsCategory::OFFLINE_PAGES), |
+ Eq(&provider2)); |
+ EXPECT_THAT(providers().size(), Eq(1ul)); |
+ EXPECT_THAT(service()->GetCategories(), |
+ ElementsAre(ContentSuggestionsCategory::OFFLINE_PAGES)); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::ARTICLES), |
+ ContentSuggestionsCategoryStatus::NOT_PROVIDED); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::OFFLINE_PAGES), |
+ ContentSuggestionsCategoryStatus::AVAILABLE); |
+ |
+ provider2.FireShutdown(); |
+ EXPECT_THAT(providers(), IsEmpty()); |
+ EXPECT_THAT(service()->GetCategories(), IsEmpty()); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::ARTICLES), |
+ Eq(ContentSuggestionsCategoryStatus::NOT_PROVIDED)); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::OFFLINE_PAGES), |
+ Eq(ContentSuggestionsCategoryStatus::NOT_PROVIDED)); |
+} |
+ |
+TEST_F(ContentSuggestionsServiceDisabledTest, ShouldDoNothingWhenDisabled) { |
+ EXPECT_THAT(service()->state(), |
+ Eq(ContentSuggestionsService::State::DISABLED)); |
+ MockProvider provider1(ContentSuggestionsCategory::ARTICLES); |
+ service()->RegisterProvider(&provider1); |
+ EXPECT_THAT(providers(), IsEmpty()); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::ARTICLES), |
+ Eq(ContentSuggestionsCategoryStatus:: |
+ ALL_SUGGESTIONS_EXPLICITLY_DISABLED)); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::OFFLINE_PAGES), |
+ Eq(ContentSuggestionsCategoryStatus:: |
+ ALL_SUGGESTIONS_EXPLICITLY_DISABLED)); |
+ EXPECT_THAT(service()->GetCategories(), IsEmpty()); |
+ EXPECT_THAT(service()->GetSuggestionsForCategory( |
+ ContentSuggestionsCategory::ARTICLES), |
+ IsEmpty()); |
+} |
+ |
+TEST_F(ContentSuggestionsServiceTest, ShouldRedirectFetchSuggestionImage) { |
+ MockProvider provider1(ContentSuggestionsCategory::ARTICLES); |
+ MockProvider provider2(ContentSuggestionsCategory::OFFLINE_PAGES); |
+ service()->RegisterProvider(&provider1); |
+ service()->RegisterProvider(&provider2); |
+ |
+ provider1.FireSuggestionsChanged(ContentSuggestionsCategory::ARTICLES, {1}); |
+ std::string suggestion_id = CreateSuggestion(1).id(); |
+ |
+ EXPECT_CALL(provider1, FetchSuggestionImage(suggestion_id, _)).Times(1); |
+ EXPECT_CALL(provider2, FetchSuggestionImage(_, _)).Times(0); |
+ service()->FetchSuggestionImage( |
+ suggestion_id, base::Bind(&ContentSuggestionsServiceTest::OnImageFetched, |
+ base::Unretained(this))); |
+ provider1.FireShutdown(); |
+ provider2.FireShutdown(); |
+} |
+ |
+TEST_F(ContentSuggestionsServiceTest, |
+ ShouldCallbackEmptyImageForUnavailableProvider) { |
+ std::string suggestion_id = "TestID"; |
+ EXPECT_CALL(*this, OnImageFetched(suggestion_id, |
+ Property(&gfx::Image::IsEmpty, Eq(true)))); |
+ service()->FetchSuggestionImage( |
+ suggestion_id, base::Bind(&ContentSuggestionsServiceTest::OnImageFetched, |
+ base::Unretained(this))); |
+} |
+ |
+TEST_F(ContentSuggestionsServiceTest, ShouldRedirectDiscardSuggestion) { |
+ MockProvider provider1(ContentSuggestionsCategory::ARTICLES); |
+ MockProvider provider2(ContentSuggestionsCategory::OFFLINE_PAGES); |
+ service()->RegisterProvider(&provider1); |
+ service()->RegisterProvider(&provider2); |
+ |
+ provider2.FireSuggestionsChanged(ContentSuggestionsCategory::OFFLINE_PAGES, |
+ {11}); |
+ std::string suggestion_id = CreateSuggestion(11).id(); |
+ |
+ EXPECT_CALL(provider1, DiscardSuggestion(_)).Times(0); |
+ EXPECT_CALL(provider2, DiscardSuggestion(suggestion_id)).Times(1); |
+ service()->DiscardSuggestion(suggestion_id); |
+ provider1.FireShutdown(); |
+ provider2.FireShutdown(); |
+} |
+ |
+TEST_F(ContentSuggestionsServiceTest, ShouldForwardSuggestions) { |
+ // Create and register providers |
+ MockProvider provider1(ContentSuggestionsCategory::ARTICLES); |
+ MockProvider provider2(ContentSuggestionsCategory::OFFLINE_PAGES); |
+ service()->RegisterProvider(&provider1); |
+ service()->RegisterProvider(&provider2); |
+ EXPECT_THAT(providers().at(ContentSuggestionsCategory::ARTICLES), |
+ Eq(&provider1)); |
+ EXPECT_THAT(providers().at(ContentSuggestionsCategory::OFFLINE_PAGES), |
+ Eq(&provider2)); |
+ |
+ // Create and register observer |
+ MockServiceObserver observer; |
+ service()->AddObserver(&observer); |
+ |
+ // Send suggestions 1 and 2 |
+ EXPECT_CALL(observer, OnNewSuggestions()).Times(1); |
+ provider1.FireSuggestionsChanged(ContentSuggestionsCategory::ARTICLES, |
+ {1, 2}); |
+ ExpectThatSuggestionsAre(ContentSuggestionsCategory::ARTICLES, {1, 2}); |
+ Mock::VerifyAndClearExpectations(&observer); |
+ |
+ // Send them again, make sure they're not reported twice |
+ EXPECT_CALL(observer, OnNewSuggestions()).Times(1); |
+ provider1.FireSuggestionsChanged(ContentSuggestionsCategory::ARTICLES, |
+ {1, 2}); |
+ ExpectThatSuggestionsAre(ContentSuggestionsCategory::ARTICLES, {1, 2}); |
+ ExpectThatSuggestionsAre(ContentSuggestionsCategory::OFFLINE_PAGES, |
+ std::vector<int>()); |
+ Mock::VerifyAndClearExpectations(&observer); |
+ |
+ // Send suggestions 13 and 14 |
+ EXPECT_CALL(observer, OnNewSuggestions()).Times(1); |
+ provider2.FireSuggestionsChanged(ContentSuggestionsCategory::OFFLINE_PAGES, |
+ {13, 14}); |
+ ExpectThatSuggestionsAre(ContentSuggestionsCategory::ARTICLES, {1, 2}); |
+ ExpectThatSuggestionsAre(ContentSuggestionsCategory::OFFLINE_PAGES, {13, 14}); |
+ Mock::VerifyAndClearExpectations(&observer); |
+ |
+ // Send suggestion 1 only |
+ EXPECT_CALL(observer, OnNewSuggestions()).Times(1); |
+ provider1.FireSuggestionsChanged(ContentSuggestionsCategory::ARTICLES, {1}); |
+ ExpectThatSuggestionsAre(ContentSuggestionsCategory::ARTICLES, {1}); |
+ ExpectThatSuggestionsAre(ContentSuggestionsCategory::OFFLINE_PAGES, {13, 14}); |
+ Mock::VerifyAndClearExpectations(&observer); |
+ |
+ // provider2 reports OFFLINE_PAGEs as unavailable |
+ EXPECT_CALL( |
+ observer, |
+ OnCategoryStatusChanged( |
+ ContentSuggestionsCategory::OFFLINE_PAGES, |
+ ContentSuggestionsCategoryStatus::CATEGORY_EXPLICITLY_DISABLED)) |
+ .Times(1); |
+ provider2.FireCategoryStatusChanged( |
+ ContentSuggestionsCategory::OFFLINE_PAGES, |
+ ContentSuggestionsCategoryStatus::CATEGORY_EXPLICITLY_DISABLED); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::ARTICLES), |
+ Eq(ContentSuggestionsCategoryStatus::AVAILABLE)); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::OFFLINE_PAGES), |
+ Eq(ContentSuggestionsCategoryStatus::CATEGORY_EXPLICITLY_DISABLED)); |
+ ExpectThatSuggestionsAre(ContentSuggestionsCategory::ARTICLES, {1}); |
+ ExpectThatSuggestionsAre(ContentSuggestionsCategory::OFFLINE_PAGES, |
+ std::vector<int>()); |
+ Mock::VerifyAndClearExpectations(&observer); |
+ |
+ // Let provider1 shut down |
+ EXPECT_CALL(observer, OnCategoryStatusChanged( |
+ ContentSuggestionsCategory::ARTICLES, |
+ ContentSuggestionsCategoryStatus::NOT_PROVIDED)) |
+ .Times(1); |
+ provider1.FireShutdown(); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::ARTICLES), |
+ Eq(ContentSuggestionsCategoryStatus::NOT_PROVIDED)); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::OFFLINE_PAGES), |
+ Eq(ContentSuggestionsCategoryStatus::CATEGORY_EXPLICITLY_DISABLED)); |
+ ExpectThatSuggestionsAre(ContentSuggestionsCategory::ARTICLES, |
+ std::vector<int>()); |
+ ExpectThatSuggestionsAre(ContentSuggestionsCategory::OFFLINE_PAGES, |
+ std::vector<int>()); |
+ Mock::VerifyAndClearExpectations(&observer); |
+ |
+ // Let provider2 shut down |
+ provider2.FireShutdown(); |
+ EXPECT_TRUE(providers().empty()); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::ARTICLES), |
+ Eq(ContentSuggestionsCategoryStatus::NOT_PROVIDED)); |
+ EXPECT_THAT( |
+ service()->GetCategoryStatus(ContentSuggestionsCategory::OFFLINE_PAGES), |
+ Eq(ContentSuggestionsCategoryStatus::NOT_PROVIDED)); |
+ |
+ // Shutdown the service |
+ EXPECT_CALL(observer, ContentSuggestionsServiceShutdown()); |
+ service()->Shutdown(); |
+ service()->RemoveObserver(&observer); |
+ // The service will receive two Shutdown() calls. |
+} |
+ |
+} // namespace ntp_snippets |