blob: 95067c536f0de985497be258c4d1e7fc86c74d68 [file] [log] [blame]
// Copyright 2017 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 "chrome/browser/android/ntp/content_suggestions_notifier_service.h"
#include <memory>
#include "base/android/application_status_listener.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/histogram_tester.h"
#include "base/test/simple_test_clock.h"
#include "chrome/browser/android/ntp/content_suggestions_notifier.h"
#include "chrome/common/pref_names.h"
#include "components/ntp_snippets/category_info.h"
#include "components/ntp_snippets/category_rankers/fake_category_ranker.h"
#include "components/ntp_snippets/content_suggestions_service.h"
#include "components/ntp_snippets/logger.h"
#include "components/ntp_snippets/remote/remote_suggestion_builder.h"
#include "components/ntp_snippets/user_classifier.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::android::APPLICATION_STATE_HAS_PAUSED_ACTIVITIES;
using base::android::ApplicationState;
using ntp_snippets::Category;
using ntp_snippets::CategoryInfo;
using ntp_snippets::CategoryStatus;
using ntp_snippets::ContentSuggestion;
using ntp_snippets::ContentSuggestionsAdditionalAction;
using ntp_snippets::ContentSuggestionsCardLayout;
using ntp_snippets::ContentSuggestionsProvider;
using ntp_snippets::ContentSuggestionsService;
using ntp_snippets::DismissedSuggestionsCallback;
using ntp_snippets::FakeCategoryRanker;
using ntp_snippets::FetchDoneCallback;
using ntp_snippets::ImageFetchedCallback;
using ntp_snippets::KnownCategories;
using ntp_snippets::RemoteSuggestion;
using ntp_snippets::UserClassifier;
using ntp_snippets::test::RemoteSuggestionBuilder;
using testing::_;
using testing::InSequence;
using testing::Return;
extern ApplicationState*
g_content_suggestions_notification_application_state_for_testing;
namespace {
std::unique_ptr<sync_preferences::TestingPrefServiceSyncable>
RegisteredPrefs() {
auto prefs = std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
ContentSuggestionsService::RegisterProfilePrefs(prefs->registry());
UserClassifier::RegisterProfilePrefs(prefs->registry());
ContentSuggestionsNotifierService::RegisterProfilePrefs(prefs->registry());
return prefs;
}
class FakeContentSuggestionsService : public ContentSuggestionsService {
public:
FakeContentSuggestionsService(PrefService* prefs, base::Clock* clock)
: ContentSuggestionsService(
ContentSuggestionsService::ENABLED,
/*signin_manager=*/nullptr,
/*history_service=*/nullptr,
/*large_icon_cache=*/nullptr,
prefs,
std::make_unique<FakeCategoryRanker>(),
std::make_unique<UserClassifier>(nullptr, clock),
/*remote_suggestions_scheduler=*/nullptr,
std::make_unique<ntp_snippets::Logger>()) {}
};
class FakeArticleProvider : public ContentSuggestionsProvider {
public:
explicit FakeArticleProvider(ContentSuggestionsService* service)
: ContentSuggestionsProvider(service) {}
CategoryStatus GetCategoryStatus(Category category) override {
if (category.IsKnownCategory(KnownCategories::ARTICLES)) {
return CategoryStatus::AVAILABLE;
}
return CategoryStatus::NOT_PROVIDED;
}
CategoryInfo GetCategoryInfo(Category category) override {
DCHECK(category.IsKnownCategory(KnownCategories::ARTICLES));
CategoryInfo info(base::ASCIIToUTF16("title"),
ContentSuggestionsCardLayout::FULL_CARD,
ContentSuggestionsAdditionalAction::NONE, true,
base::ASCIIToUTF16("no suggestions"));
return info;
}
void FetchSuggestionImage(const ContentSuggestion::ID& id,
ImageFetchedCallback callback) override {
std::move(callback).Run(gfx::Image());
}
void DismissSuggestion(const ContentSuggestion::ID& id) override {
NOTIMPLEMENTED();
}
void Fetch(const Category& category,
const std::set<std::string>& known_suggestion_ids,
FetchDoneCallback callback) override {
NOTIMPLEMENTED();
}
void ClearHistory(
base::Time begin,
base::Time end,
const base::Callback<bool(const GURL& url)>& filter) override {
NOTIMPLEMENTED();
}
void ClearCachedSuggestions() override { NOTIMPLEMENTED(); }
void GetDismissedSuggestionsForDebugging(
Category category,
DismissedSuggestionsCallback callback) override {
NOTIMPLEMENTED();
}
void ClearDismissedSuggestionsForDebugging(Category category) override {
NOTIMPLEMENTED();
}
};
class MockContentSuggestionsNotifier : public ContentSuggestionsNotifier {
public:
MOCK_METHOD0(CreateNotificationChannel, void());
MOCK_METHOD7(SendNotification,
bool(const ContentSuggestion::ID& id,
const GURL& url,
const base::string16& title,
const base::string16& text,
const gfx::Image& image,
base::Time timeout_at,
int priority));
MOCK_METHOD2(HideNotification,
void(const ContentSuggestion::ID& id,
ContentSuggestionsNotificationAction why));
MOCK_METHOD1(HideAllNotifications,
void(ContentSuggestionsNotificationAction why));
MOCK_METHOD0(FlushCachedMetrics, void());
MOCK_METHOD1(RegisterChannel, bool(bool enabled));
MOCK_METHOD0(UnregisterChannel, void());
};
class ContentSuggestionsNotifierServiceTest : public ::testing::Test {
protected:
ContentSuggestionsNotifierServiceTest()
: application_state_(APPLICATION_STATE_HAS_PAUSED_ACTIVITIES),
prefs_(RegisteredPrefs()),
suggestions_(prefs_.get(), &clock_),
notifier_(new testing::StrictMock<MockContentSuggestionsNotifier>),
notifier_ownership_(notifier_),
provider_(&suggestions_) {
g_content_suggestions_notification_application_state_for_testing =
&application_state_;
}
~ContentSuggestionsNotifierServiceTest() override {
g_content_suggestions_notification_application_state_for_testing = nullptr;
}
void NewSuggestions(const std::vector<std::unique_ptr<RemoteSuggestion>>&
remote_suggestions) {
Category articles = Category::FromKnownCategory(KnownCategories::ARTICLES);
ContentSuggestionsProvider::Observer* observer = &suggestions_;
std::vector<ContentSuggestion> content_suggestions;
for (const auto& remote_suggestion : remote_suggestions) {
content_suggestions.emplace_back(
remote_suggestion->ToContentSuggestion(articles));
}
observer->OnNewSuggestions(&provider_, articles,
std::move(content_suggestions));
}
ApplicationState application_state_;
std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> prefs_;
base::SimpleTestClock clock_;
FakeContentSuggestionsService suggestions_;
testing::StrictMock<MockContentSuggestionsNotifier>* notifier_;
std::unique_ptr<ContentSuggestionsNotifier> notifier_ownership_;
FakeArticleProvider provider_;
};
TEST_F(ContentSuggestionsNotifierServiceTest, ShouldInitializeAtStartup) {
InSequence s;
EXPECT_CALL(*notifier_, FlushCachedMetrics());
EXPECT_CALL(*notifier_, RegisterChannel(true)).WillOnce(Return(false));
ContentSuggestionsNotifierService service(prefs_.get(), &suggestions_,
std::move(notifier_ownership_));
}
TEST_F(ContentSuggestionsNotifierServiceTest,
ShouldNotInitializeAtStartupIfDisabled) {
prefs_->SetBoolean(prefs::kContentSuggestionsNotificationsEnabled, false);
InSequence s;
EXPECT_CALL(*notifier_, FlushCachedMetrics());
EXPECT_CALL(*notifier_, RegisterChannel(false)).WillOnce(Return(false));
ContentSuggestionsNotifierService service(prefs_.get(), &suggestions_,
std::move(notifier_ownership_));
}
TEST_F(ContentSuggestionsNotifierServiceTest,
ShouldClearNotificationSettingAfterOUpgrade) {
prefs_->SetBoolean(prefs::kContentSuggestionsNotificationsEnabled, false);
InSequence s;
EXPECT_CALL(*notifier_, FlushCachedMetrics());
EXPECT_CALL(*notifier_, RegisterChannel(false))
.WillOnce(Return(true)); // Channel actually registered
ContentSuggestionsNotifierService service(prefs_.get(), &suggestions_,
std::move(notifier_ownership_));
// Now that notification channels are used, our setting should be true
// unconditionally, as notification channels are used for settings instead.
EXPECT_EQ(true,
prefs_->GetBoolean(prefs::kContentSuggestionsNotificationsEnabled));
}
TEST_F(ContentSuggestionsNotifierServiceTest,
ShouldNotSendNotificationIfNotShouldNotify) {
InSequence s;
EXPECT_CALL(*notifier_, FlushCachedMetrics());
EXPECT_CALL(*notifier_, RegisterChannel(true)).WillOnce(Return(false));
ContentSuggestionsNotifierService service(prefs_.get(), &suggestions_,
std::move(notifier_ownership_));
std::vector<std::unique_ptr<RemoteSuggestion>> suggestions;
suggestions.emplace_back(
RemoteSuggestionBuilder().SetShouldNotify(false).Build());
// No notification.
NewSuggestions(suggestions);
}
TEST_F(ContentSuggestionsNotifierServiceTest,
ShouldSendNotificationIfShouldNotify) {
InSequence s;
EXPECT_CALL(*notifier_, FlushCachedMetrics());
EXPECT_CALL(*notifier_, RegisterChannel(true)).WillOnce(Return(false));
ContentSuggestionsNotifierService service(prefs_.get(), &suggestions_,
std::move(notifier_ownership_));
std::vector<std::unique_ptr<RemoteSuggestion>> suggestions;
suggestions.emplace_back(
RemoteSuggestionBuilder().SetShouldNotify(true).Build());
EXPECT_CALL(*notifier_, SendNotification(_, _, _, _, _, _, _));
NewSuggestions(suggestions);
}
TEST_F(ContentSuggestionsNotifierServiceTest,
ShouldNotSendNotificationIfNotificationsDisabled) {
prefs_->SetBoolean(prefs::kContentSuggestionsNotificationsEnabled, false);
InSequence s;
EXPECT_CALL(*notifier_, FlushCachedMetrics());
EXPECT_CALL(*notifier_, RegisterChannel(false)).WillOnce(Return(false));
ContentSuggestionsNotifierService service(prefs_.get(), &suggestions_,
std::move(notifier_ownership_));
std::vector<std::unique_ptr<RemoteSuggestion>> suggestions;
suggestions.emplace_back(
RemoteSuggestionBuilder().SetShouldNotify(true).Build());
// No notification.
NewSuggestions(suggestions);
}
} // namespace