| Index: components/ntp_snippets/remote/remote_suggestions_provider_unittest.cc
|
| diff --git a/components/ntp_snippets/remote/remote_suggestions_provider_unittest.cc b/components/ntp_snippets/remote/remote_suggestions_provider_unittest.cc
|
| index 2ad2080a3c4ee90076fabf9980fd8b1616a2dd3e..fd13c5d15b050af701c793b652e23993855cc8a0 100644
|
| --- a/components/ntp_snippets/remote/remote_suggestions_provider_unittest.cc
|
| +++ b/components/ntp_snippets/remote/remote_suggestions_provider_unittest.cc
|
| @@ -26,8 +26,11 @@
|
| #include "components/image_fetcher/image_decoder.h"
|
| #include "components/image_fetcher/image_fetcher.h"
|
| #include "components/image_fetcher/image_fetcher_delegate.h"
|
| -#include "components/ntp_snippets/category_factory.h"
|
| +#include "components/ntp_snippets/category.h"
|
| #include "components/ntp_snippets/category_info.h"
|
| +#include "components/ntp_snippets/category_rankers/category_ranker.h"
|
| +#include "components/ntp_snippets/category_rankers/constant_category_ranker.h"
|
| +#include "components/ntp_snippets/category_rankers/mock_category_ranker.h"
|
| #include "components/ntp_snippets/ntp_snippets_constants.h"
|
| #include "components/ntp_snippets/pref_names.h"
|
| #include "components/ntp_snippets/remote/ntp_snippet.h"
|
| @@ -49,6 +52,7 @@
|
|
|
| using image_fetcher::ImageFetcher;
|
| using image_fetcher::ImageFetcherDelegate;
|
| +using testing::_;
|
| using testing::ElementsAre;
|
| using testing::Eq;
|
| using testing::InSequence;
|
| @@ -62,7 +66,6 @@ using testing::SaveArg;
|
| using testing::SizeIs;
|
| using testing::StartsWith;
|
| using testing::WithArgs;
|
| -using testing::_;
|
|
|
| namespace ntp_snippets {
|
|
|
| @@ -116,44 +119,66 @@ base::Time GetDefaultExpirationTime() {
|
| return base::Time::Now() + base::TimeDelta::FromHours(1);
|
| }
|
|
|
| -std::string GetTestJson(const std::vector<std::string>& snippets,
|
| - const std::string& category_title) {
|
| +std::string GetCategoryJson(const std::vector<std::string>& snippets,
|
| + int remote_category_id,
|
| + const std::string& category_title) {
|
| return base::StringPrintf(
|
| - "{\n"
|
| - " \"categories\": [{\n"
|
| - " \"id\": 1,\n"
|
| + " {\n"
|
| + " \"id\": %d,\n"
|
| " \"localizedTitle\": \"%s\",\n"
|
| " \"suggestions\": [%s]\n"
|
| - " }]\n"
|
| - "}\n",
|
| - category_title.c_str(), base::JoinString(snippets, ", ").c_str());
|
| + " }\n",
|
| + remote_category_id, category_title.c_str(),
|
| + base::JoinString(snippets, ", ").c_str());
|
| }
|
|
|
| -std::string GetTestJson(const std::vector<std::string>& snippets) {
|
| - return GetTestJson(snippets, kTestJsonDefaultCategoryTitle);
|
| +class MultiCategoryJsonBuilder {
|
| + public:
|
| + MultiCategoryJsonBuilder() {}
|
| +
|
| + MultiCategoryJsonBuilder& AddCategoryWithCustomTitle(
|
| + const std::vector<std::string>& snippets,
|
| + int remote_category_id,
|
| + const std::string& category_title) {
|
| + category_json_.push_back(
|
| + GetCategoryJson(snippets, remote_category_id, category_title));
|
| + return *this;
|
| + }
|
| +
|
| + MultiCategoryJsonBuilder& AddCategory(
|
| + const std::vector<std::string>& snippets,
|
| + int remote_category_id) {
|
| + return AddCategoryWithCustomTitle(
|
| + snippets, remote_category_id,
|
| + "Title" + base::IntToString(remote_category_id));
|
| + }
|
| +
|
| + std::string Build() {
|
| + return base::StringPrintf(
|
| + "{\n"
|
| + " \"categories\": [\n"
|
| + "%s\n"
|
| + " ]\n"
|
| + "}\n",
|
| + base::JoinString(category_json_, " ,\n").c_str());
|
| + }
|
| +
|
| + private:
|
| + std::vector<std::string> category_json_;
|
| +};
|
| +
|
| +// TODO(vitaliii): Remove these convenience functions as they do not provide
|
| +// that much value and add additional redirections obscuring the code.
|
| +std::string GetTestJson(const std::vector<std::string>& snippets,
|
| + const std::string& category_title) {
|
| + return MultiCategoryJsonBuilder()
|
| + .AddCategoryWithCustomTitle(snippets, /*remote_category_id=*/1,
|
| + category_title)
|
| + .Build();
|
| }
|
|
|
| -// TODO(tschumann): Remove the default parameter other_id. It makes the tests
|
| -// less explicit and hard to read. Also get rid of the convenience
|
| -// other_category() and unknown_category() helpers -- tests can just define
|
| -// their own.
|
| -std::string GetMultiCategoryJson(const std::vector<std::string>& articles,
|
| - const std::vector<std::string>& others,
|
| - int other_id = 2) {
|
| - return base::StringPrintf(
|
| - "{\n"
|
| - " \"categories\": [{\n"
|
| - " \"id\": 1,\n"
|
| - " \"localizedTitle\": \"Articles for You\",\n"
|
| - " \"suggestions\": [%s]\n"
|
| - " }, {\n"
|
| - " \"id\": %i,\n"
|
| - " \"localizedTitle\": \"Other Things\",\n"
|
| - " \"suggestions\": [%s]\n"
|
| - " }]\n"
|
| - "}\n",
|
| - base::JoinString(articles, ", ").c_str(), other_id,
|
| - base::JoinString(others, ", ").c_str());
|
| +std::string GetTestJson(const std::vector<std::string>& snippets) {
|
| + return GetTestJson(snippets, kTestJsonDefaultCategoryTitle);
|
| }
|
|
|
| std::string FormatTime(const base::Time& t) {
|
| @@ -401,6 +426,7 @@ class RemoteSuggestionsProviderTest : public ::testing::Test {
|
| fake_url_fetcher_factory_(
|
| /*default_factory=*/&failing_url_fetcher_factory_),
|
| test_url_(kTestContentSuggestionsServerWithAPIKey),
|
| + category_ranker_(base::MakeUnique<ConstantCategoryRanker>()),
|
| user_classifier_(/*pref_service=*/nullptr),
|
| image_fetcher_(nullptr),
|
| image_decoder_(nullptr),
|
| @@ -419,6 +445,8 @@ class RemoteSuggestionsProviderTest : public ::testing::Test {
|
| base::RunLoop().RunUntilIdle();
|
| }
|
|
|
| + // TODO(vitaliii): Rewrite this function to initialize a test class member
|
| + // instead of creating a new service.
|
| std::unique_ptr<RemoteSuggestionsProvider> MakeSnippetsService(
|
| bool set_empty_response = true) {
|
| auto service = MakeSnippetsServiceWithoutInitialization();
|
| @@ -437,9 +465,8 @@ class RemoteSuggestionsProviderTest : public ::testing::Test {
|
| std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher =
|
| base::MakeUnique<NTPSnippetsFetcher>(
|
| utils_.fake_signin_manager(), fake_token_service_.get(),
|
| - std::move(request_context_getter), utils_.pref_service(),
|
| - &category_factory_, nullptr, base::Bind(&ParseJson), kAPIKey,
|
| - &user_classifier_);
|
| + std::move(request_context_getter), utils_.pref_service(), nullptr,
|
| + base::Bind(&ParseJson), kAPIKey, &user_classifier_);
|
|
|
| utils_.fake_signin_manager()->SignIn("foo@bar.com");
|
|
|
| @@ -455,10 +482,9 @@ class RemoteSuggestionsProviderTest : public ::testing::Test {
|
| database_dir_.GetPath(), task_runner);
|
| database_ = database.get();
|
| return base::MakeUnique<RemoteSuggestionsProvider>(
|
| - observer_.get(), &category_factory_, utils_.pref_service(), "fr",
|
| + observer_.get(), utils_.pref_service(), "fr", category_ranker_.get(),
|
| &user_classifier_, &scheduler_, std::move(snippets_fetcher),
|
| - std::move(image_fetcher), std::move(image_decoder),
|
| - std::move(database),
|
| + std::move(image_fetcher), std::move(image_decoder), std::move(database),
|
| base::MakeUnique<RemoteSuggestionsStatusService>(
|
| utils_.fake_signin_manager(), utils_.pref_service()));
|
| }
|
| @@ -478,11 +504,15 @@ class RemoteSuggestionsProviderTest : public ::testing::Test {
|
| EXPECT_NE(RemoteSuggestionsProvider::State::NOT_INITED, service->state_);
|
| }
|
|
|
| - void ResetSnippetsService(
|
| - std::unique_ptr<RemoteSuggestionsProvider>* service) {
|
| + void ResetSnippetsService(std::unique_ptr<RemoteSuggestionsProvider>* service,
|
| + bool set_empty_response) {
|
| service->reset();
|
| observer_.reset();
|
| - *service = MakeSnippetsService();
|
| + *service = MakeSnippetsService(set_empty_response);
|
| + }
|
| +
|
| + void SetCategoryRanker(std::unique_ptr<CategoryRanker> category_ranker) {
|
| + category_ranker_ = std::move(category_ranker);
|
| }
|
|
|
| ContentSuggestion::ID MakeArticleID(const std::string& id_within_category) {
|
| @@ -490,17 +520,19 @@ class RemoteSuggestionsProviderTest : public ::testing::Test {
|
| }
|
|
|
| Category articles_category() {
|
| - return category_factory_.FromKnownCategory(KnownCategories::ARTICLES);
|
| + return Category::FromKnownCategory(KnownCategories::ARTICLES);
|
| }
|
|
|
| ContentSuggestion::ID MakeOtherID(const std::string& id_within_category) {
|
| return ContentSuggestion::ID(other_category(), id_within_category);
|
| }
|
|
|
| - Category other_category() { return category_factory_.FromRemoteCategory(2); }
|
| + // TODO(tschumann): Get rid of the convenience other_category() and
|
| + // unknown_category() helpers -- tests can just define their own.
|
| + Category other_category() { return Category::FromRemoteCategory(2); }
|
|
|
| Category unknown_category() {
|
| - return category_factory_.FromRemoteCategory(kUnknownRemoteCategoryId);
|
| + return Category::FromRemoteCategory(kUnknownRemoteCategoryId);
|
| }
|
|
|
| protected:
|
| @@ -553,10 +585,10 @@ class RemoteSuggestionsProviderTest : public ::testing::Test {
|
| net::FakeURLFetcherFactory fake_url_fetcher_factory_;
|
| const GURL test_url_;
|
| std::unique_ptr<OAuth2TokenService> fake_token_service_;
|
| + std::unique_ptr<CategoryRanker> category_ranker_;
|
| UserClassifier user_classifier_;
|
| NiceMock<MockScheduler> scheduler_;
|
| std::unique_ptr<FakeContentSuggestionsProviderObserver> observer_;
|
| - CategoryFactory category_factory_;
|
| NiceMock<MockImageFetcher>* image_fetcher_;
|
| FakeImageDecoder* image_decoder_;
|
|
|
| @@ -591,7 +623,7 @@ TEST_F(RemoteSuggestionsProviderTest, DontRescheduleOnStart) {
|
| Mock::VerifyAndClearExpectations(&mock_scheduler());
|
| EXPECT_CALL(mock_scheduler(), Schedule(_, _)).Times(0);
|
| EXPECT_CALL(mock_scheduler(), Unschedule()).Times(0);
|
| - ResetSnippetsService(&service);
|
| + ResetSnippetsService(&service, /*set_empty_response=*/true);
|
| }
|
|
|
| TEST_F(RemoteSuggestionsProviderTest, RescheduleAfterSuccessfulFetch) {
|
| @@ -742,11 +774,12 @@ TEST_F(RemoteSuggestionsProviderTest, CategoryTitle) {
|
| }
|
|
|
| TEST_F(RemoteSuggestionsProviderTest, MultipleCategories) {
|
| - std::string json_str(
|
| - GetMultiCategoryJson({GetSnippetN(0)}, {GetSnippetN(1)}));
|
| -
|
| auto service = MakeSnippetsService();
|
| -
|
| + std::string json_str =
|
| + MultiCategoryJsonBuilder()
|
| + .AddCategory({GetSnippetN(0)}, /*remote_category_id=*/1)
|
| + .AddCategory({GetSnippetN(1)}, /*remote_category_id=*/2)
|
| + .Build();
|
| LoadFromJSONString(service.get(), json_str);
|
|
|
| ASSERT_THAT(observer().statuses(),
|
| @@ -799,12 +832,15 @@ TEST_F(RemoteSuggestionsProviderTest, ArticleCategoryInfo) {
|
|
|
| TEST_F(RemoteSuggestionsProviderTest, ExperimentalCategoryInfo) {
|
| auto service = MakeSnippetsService();
|
| -
|
| + std::string json_str =
|
| + MultiCategoryJsonBuilder()
|
| + .AddCategory({GetSnippetN(0)}, /*remote_category_id=*/1)
|
| + .AddCategory({GetSnippetN(1)}, kUnknownRemoteCategoryId)
|
| + .Build();
|
| // Load data with multiple categories so that a new experimental category gets
|
| // registered.
|
| - LoadFromJSONString(service.get(),
|
| - GetMultiCategoryJson({GetSnippetN(0)}, {GetSnippetN(1)},
|
| - kUnknownRemoteCategoryId));
|
| + LoadFromJSONString(service.get(), json_str);
|
| +
|
| CategoryInfo info = service->GetCategoryInfo(unknown_category());
|
| EXPECT_THAT(info.has_more_action(), Eq(false));
|
| EXPECT_THAT(info.has_reload_action(), Eq(false));
|
| @@ -812,12 +848,42 @@ TEST_F(RemoteSuggestionsProviderTest, ExperimentalCategoryInfo) {
|
| EXPECT_THAT(info.show_if_empty(), Eq(false));
|
| }
|
|
|
| +TEST_F(RemoteSuggestionsProviderTest, AddRemoteCategoriesToCategoryRanker) {
|
| + auto mock_ranker = base::MakeUnique<MockCategoryRanker>();
|
| + MockCategoryRanker* raw_mock_ranker = mock_ranker.get();
|
| + SetCategoryRanker(std::move(mock_ranker));
|
| + std::string json_str =
|
| + MultiCategoryJsonBuilder()
|
| + .AddCategory({GetSnippetN(0)}, /*remote_category_id=*/11)
|
| + .AddCategory({GetSnippetN(1)}, /*remote_category_id=*/13)
|
| + .AddCategory({GetSnippetN(2)}, /*remote_category_id=*/12)
|
| + .Build();
|
| + SetUpFetchResponse(json_str);
|
| + {
|
| + // The order of categories is determined by the order in which they are
|
| + // added. Thus, the latter is tested here.
|
| + InSequence s;
|
| + EXPECT_CALL(*raw_mock_ranker,
|
| + AppendCategoryIfNecessary(Category::FromRemoteCategory(11)));
|
| + EXPECT_CALL(*raw_mock_ranker,
|
| + AppendCategoryIfNecessary(Category::FromRemoteCategory(13)));
|
| + EXPECT_CALL(*raw_mock_ranker,
|
| + AppendCategoryIfNecessary(Category::FromRemoteCategory(12)));
|
| + }
|
| + auto service = MakeSnippetsService(/*set_empty_response=*/false);
|
| +}
|
| +
|
| TEST_F(RemoteSuggestionsProviderTest, PersistCategoryInfos) {
|
| auto service = MakeSnippetsService();
|
| -
|
| - LoadFromJSONString(service.get(),
|
| - GetMultiCategoryJson({GetSnippetN(0)}, {GetSnippetN(1)},
|
| - kUnknownRemoteCategoryId));
|
| + // TODO(vitaliii): Use |articles_category()| instead of constant ID below.
|
| + std::string json_str =
|
| + MultiCategoryJsonBuilder()
|
| + .AddCategoryWithCustomTitle(
|
| + {GetSnippetN(0)}, /*remote_category_id=*/1, "Articles for You")
|
| + .AddCategoryWithCustomTitle({GetSnippetN(1)},
|
| + kUnknownRemoteCategoryId, "Other Things")
|
| + .Build();
|
| + LoadFromJSONString(service.get(), json_str);
|
|
|
| ASSERT_EQ(observer().StatusForCategory(articles_category()),
|
| CategoryStatus::AVAILABLE);
|
| @@ -830,7 +896,7 @@ TEST_F(RemoteSuggestionsProviderTest, PersistCategoryInfos) {
|
| service->GetCategoryInfo(unknown_category());
|
|
|
| // Recreate the service to simulate a Chrome restart.
|
| - ResetSnippetsService(&service);
|
| + ResetSnippetsService(&service, /*set_empty_response=*/true);
|
|
|
| // The categories should have been restored.
|
| ASSERT_NE(observer().StatusForCategory(articles_category()),
|
| @@ -852,18 +918,57 @@ TEST_F(RemoteSuggestionsProviderTest, PersistCategoryInfos) {
|
| EXPECT_EQ(info_unknown_before.title(), info_unknown_after.title());
|
| }
|
|
|
| +TEST_F(RemoteSuggestionsProviderTest, PersistRemoteCategoryOrder) {
|
| + // We create a service with a normal ranker to store the order.
|
| + std::string json_str =
|
| + MultiCategoryJsonBuilder()
|
| + .AddCategory({GetSnippetN(0)}, /*remote_category_id=*/11)
|
| + .AddCategory({GetSnippetN(1)}, /*remote_category_id=*/13)
|
| + .AddCategory({GetSnippetN(2)}, /*remote_category_id=*/12)
|
| + .Build();
|
| + SetUpFetchResponse(json_str);
|
| + auto service = MakeSnippetsService(/*set_empty_response=*/false);
|
| +
|
| + // We manually recreate the service to simulate Chrome restart and enforce a
|
| + // mock ranker. The response is cleared to ensure that the order is not
|
| + // fetched.
|
| + SetUpFetchResponse("");
|
| + auto mock_ranker = base::MakeUnique<MockCategoryRanker>();
|
| + MockCategoryRanker* raw_mock_ranker = mock_ranker.get();
|
| + SetCategoryRanker(std::move(mock_ranker));
|
| + {
|
| + // The order of categories is determined by the order in which they are
|
| + // added. Thus, the latter is tested here.
|
| + InSequence s;
|
| + // Article category always exists and, therefore, it is stored in prefs too.
|
| + EXPECT_CALL(*raw_mock_ranker,
|
| + AppendCategoryIfNecessary(articles_category()));
|
| +
|
| + EXPECT_CALL(*raw_mock_ranker,
|
| + AppendCategoryIfNecessary(Category::FromRemoteCategory(11)));
|
| + EXPECT_CALL(*raw_mock_ranker,
|
| + AppendCategoryIfNecessary(Category::FromRemoteCategory(13)));
|
| + EXPECT_CALL(*raw_mock_ranker,
|
| + AppendCategoryIfNecessary(Category::FromRemoteCategory(12)));
|
| + }
|
| + ResetSnippetsService(&service, /*set_empty_response=*/false);
|
| +}
|
| +
|
| TEST_F(RemoteSuggestionsProviderTest, PersistSuggestions) {
|
| auto service = MakeSnippetsService();
|
| -
|
| - LoadFromJSONString(service.get(),
|
| - GetMultiCategoryJson({GetSnippetN(0)}, {GetSnippetN(1)}));
|
| + std::string json_str =
|
| + MultiCategoryJsonBuilder()
|
| + .AddCategory({GetSnippetN(0)}, /*remote_category_id=*/1)
|
| + .AddCategory({GetSnippetN(2)}, /*remote_category_id=*/2)
|
| + .Build();
|
| + LoadFromJSONString(service.get(), json_str);
|
|
|
| ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
|
| SizeIs(1));
|
| ASSERT_THAT(observer().SuggestionsForCategory(other_category()), SizeIs(1));
|
|
|
| // Recreate the service to simulate a Chrome restart.
|
| - ResetSnippetsService(&service);
|
| + ResetSnippetsService(&service, /*set_empty_response=*/true);
|
|
|
| // The suggestions in both categories should have been restored.
|
| EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
|
| @@ -874,8 +979,14 @@ TEST_F(RemoteSuggestionsProviderTest, PersistSuggestions) {
|
| TEST_F(RemoteSuggestionsProviderTest, DontNotifyIfNotAvailable) {
|
| // Get some suggestions into the database.
|
| auto service = MakeSnippetsService();
|
| - LoadFromJSONString(service.get(),
|
| - GetMultiCategoryJson({GetSnippetN(0)}, {GetSnippetN(1)}));
|
| + std::string json_str =
|
| + MultiCategoryJsonBuilder()
|
| + .AddCategory({GetSnippetN(0)},
|
| + /*remote_category_id=*/1)
|
| + .AddCategory({GetSnippetN(1)}, /*remote_category_id=*/2)
|
| + .Build();
|
| + LoadFromJSONString(service.get(), json_str);
|
| +
|
| ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
|
| SizeIs(1));
|
| ASSERT_THAT(observer().SuggestionsForCategory(other_category()), SizeIs(1));
|
| @@ -886,7 +997,7 @@ TEST_F(RemoteSuggestionsProviderTest, DontNotifyIfNotAvailable) {
|
| pref_service()->SetBoolean(prefs::kEnableSnippets, false);
|
|
|
| // Recreate the service to simulate a Chrome start.
|
| - ResetSnippetsService(&service);
|
| + ResetSnippetsService(&service, /*set_empty_response=*/true);
|
|
|
| ASSERT_THAT(RemoteSuggestionsProvider::State::DISABLED, Eq(service->state_));
|
|
|
| @@ -1259,7 +1370,7 @@ TEST_F(RemoteSuggestionsProviderTest, Dismiss) {
|
| EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), IsEmpty());
|
|
|
| // The snippet should stay dismissed even after re-creating the service.
|
| - ResetSnippetsService(&service);
|
| + ResetSnippetsService(&service, /*set_empty_response=*/true);
|
| LoadFromJSONString(service.get(), json_str);
|
| EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), IsEmpty());
|
|
|
| @@ -1455,7 +1566,7 @@ TEST_F(RemoteSuggestionsProviderTest, LogNumArticlesHistogram) {
|
| // There is only a single, dismissed snippet in the database, so recreating
|
| // the service will require us to re-fetch.
|
| tester.ExpectTotalCount("NewTabPage.Snippets.NumArticlesFetched", 4);
|
| - ResetSnippetsService(&service);
|
| + ResetSnippetsService(&service, /*set_empty_response=*/true);
|
| EXPECT_EQ(observer().StatusForCategory(articles_category()),
|
| CategoryStatus::AVAILABLE);
|
| tester.ExpectTotalCount("NewTabPage.Snippets.NumArticlesFetched", 5);
|
| @@ -1469,7 +1580,7 @@ TEST_F(RemoteSuggestionsProviderTest, LogNumArticlesHistogram) {
|
| service.get(),
|
| GetTestJson({GetSnippetWithUrl("http://not-dismissed.com")}));
|
| tester.ExpectTotalCount("NewTabPage.Snippets.NumArticlesFetched", 6);
|
| - ResetSnippetsService(&service);
|
| + ResetSnippetsService(&service, /*set_empty_response=*/true);
|
| tester.ExpectTotalCount("NewTabPage.Snippets.NumArticlesFetched", 6);
|
| }
|
|
|
| @@ -1666,7 +1777,7 @@ TEST_F(RemoteSuggestionsProviderTest, ShouldClearOrphanedImagesOnRestart) {
|
| "http://something.com/pletely/unrelated")}));
|
| // The image should still be available until a restart happens.
|
| EXPECT_FALSE(FetchImage(service.get(), MakeArticleID(kSnippetUrl)).IsEmpty());
|
| - ResetSnippetsService(&service);
|
| + ResetSnippetsService(&service, /*set_empty_response=*/true);
|
| // After the restart, the image should be garbage collected.
|
| EXPECT_TRUE(FetchImage(service.get(), MakeArticleID(kSnippetUrl)).IsEmpty());
|
| }
|
|
|