blob: 7aa45beb51ef78bd92d083be71a3b67f980f5e4e [file] [log] [blame]
// Copyright 2014 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/suggestions/suggestions_service_impl.h"
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "components/signin/core/browser/account_tracker_service.h"
#include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
#include "components/signin/core/browser/fake_signin_manager.h"
#include "components/signin/core/browser/test_signin_client.h"
#include "components/suggestions/blacklist_store.h"
#include "components/suggestions/features.h"
#include "components/suggestions/image_manager.h"
#include "components/suggestions/proto/suggestions.pb.h"
#include "components/suggestions/suggestions_store.h"
#include "components/sync/driver/fake_sync_service.h"
#include "components/sync/driver/sync_service.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "net/base/url_util.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_request_status.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/image/image.h"
using sync_preferences::TestingPrefServiceSyncable;
using syncer::SyncServiceObserver;
using testing::_;
using testing::AnyNumber;
using testing::DoAll;
using testing::Eq;
using testing::Mock;
using testing::Return;
using testing::SetArgPointee;
using testing::StrictMock;
namespace {
const char kAccountId[] = "account";
const char kSuggestionsUrlPath[] = "/chromesuggestions";
const char kBlacklistUrlPath[] = "/chromesuggestions/blacklist";
const char kBlacklistClearUrlPath[] = "/chromesuggestions/blacklist/clear";
const char kTestTitle[] = "a title";
const char kTestUrl[] = "http://go.com";
const char kTestFaviconUrl[] =
"https://s2.googleusercontent.com/s2/favicons?domain_url="
"http://go.com&alt=s&sz=32";
const char kBlacklistedUrl[] = "http://blacklist.com";
const int64_t kTestSetExpiry = 12121212; // This timestamp lies in the past.
// GMock matcher for protobuf equality.
MATCHER_P(EqualsProto, message, "") {
// This implementation assumes protobuf serialization is deterministic, which
// is true in practice but technically not something that code is supposed
// to rely on. However, it vastly simplifies the implementation.
std::string expected_serialized, actual_serialized;
message.SerializeToString(&expected_serialized);
arg.SerializeToString(&actual_serialized);
return expected_serialized == actual_serialized;
}
} // namespace
namespace suggestions {
SuggestionsProfile CreateSuggestionsProfile() {
SuggestionsProfile profile;
profile.set_timestamp(123);
ChromeSuggestion* suggestion = profile.add_suggestions();
suggestion->set_title(kTestTitle);
suggestion->set_url(kTestUrl);
return profile;
}
class MockSyncService : public syncer::FakeSyncService {
public:
MockSyncService() {}
virtual ~MockSyncService() {}
MOCK_CONST_METHOD0(CanSyncStart, bool());
MOCK_CONST_METHOD0(IsSyncActive, bool());
MOCK_CONST_METHOD0(ConfigurationDone, bool());
MOCK_CONST_METHOD0(GetActiveDataTypes, syncer::ModelTypeSet());
};
class TestSuggestionsStore : public suggestions::SuggestionsStore {
public:
TestSuggestionsStore() { cached_suggestions = CreateSuggestionsProfile(); }
bool LoadSuggestions(SuggestionsProfile* suggestions) override {
suggestions->CopyFrom(cached_suggestions);
return cached_suggestions.suggestions_size();
}
bool StoreSuggestions(const SuggestionsProfile& suggestions) override {
cached_suggestions.CopyFrom(suggestions);
return true;
}
void ClearSuggestions() override {
cached_suggestions = SuggestionsProfile();
}
SuggestionsProfile cached_suggestions;
};
class MockImageManager : public suggestions::ImageManager {
public:
MockImageManager() {}
virtual ~MockImageManager() {}
MOCK_METHOD1(Initialize, void(const SuggestionsProfile&));
MOCK_METHOD2(GetImageForURL,
void(const GURL&,
base::Callback<void(const GURL&, const gfx::Image&)>));
MOCK_METHOD2(AddImageURL, void(const GURL&, const GURL&));
};
class MockBlacklistStore : public suggestions::BlacklistStore {
public:
MOCK_METHOD1(BlacklistUrl, bool(const GURL&));
MOCK_METHOD0(ClearBlacklist, void());
MOCK_METHOD1(GetTimeUntilReadyForUpload, bool(base::TimeDelta*));
MOCK_METHOD2(GetTimeUntilURLReadyForUpload,
bool(const GURL&, base::TimeDelta*));
MOCK_METHOD1(GetCandidateForUpload, bool(GURL*));
MOCK_METHOD1(RemoveUrl, bool(const GURL&));
MOCK_METHOD1(FilterSuggestions, void(SuggestionsProfile*));
};
class SuggestionsServiceTest : public testing::Test {
protected:
SuggestionsServiceTest()
: signin_client_(&pref_service_),
signin_manager_(&signin_client_, &account_tracker_),
request_context_(new net::TestURLRequestContextGetter(
io_message_loop_.task_runner())),
mock_thumbnail_manager_(nullptr),
mock_blacklist_store_(nullptr),
test_suggestions_store_(nullptr) {
SigninManagerBase::RegisterProfilePrefs(pref_service_.registry());
SigninManagerBase::RegisterPrefs(pref_service_.registry());
signin_manager_.SignIn(kAccountId);
token_service_.UpdateCredentials(kAccountId, "refresh_token");
token_service_.set_auto_post_fetch_response_on_message_loop(true);
}
~SuggestionsServiceTest() override {}
void SetUp() override {
EXPECT_CALL(*sync_service(), CanSyncStart())
.Times(AnyNumber())
.WillRepeatedly(Return(true));
EXPECT_CALL(*sync_service(), IsSyncActive())
.Times(AnyNumber())
.WillRepeatedly(Return(true));
EXPECT_CALL(*sync_service(), ConfigurationDone())
.Times(AnyNumber())
.WillRepeatedly(Return(true));
EXPECT_CALL(*sync_service(), GetActiveDataTypes())
.Times(AnyNumber())
.WillRepeatedly(
Return(syncer::ModelTypeSet(syncer::HISTORY_DELETE_DIRECTIVES)));
// These objects are owned by the SuggestionsService, but we keep the
// pointers around for testing.
test_suggestions_store_ = new TestSuggestionsStore();
mock_thumbnail_manager_ = new StrictMock<MockImageManager>();
mock_blacklist_store_ = new StrictMock<MockBlacklistStore>();
suggestions_service_ = base::MakeUnique<SuggestionsServiceImpl>(
&signin_manager_, &token_service_, &mock_sync_service_,
request_context_.get(), base::WrapUnique(test_suggestions_store_),
base::WrapUnique(mock_thumbnail_manager_),
base::WrapUnique(mock_blacklist_store_));
}
GURL GetCurrentlyQueriedUrl() {
net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0);
if (!fetcher) {
return GURL();
}
return fetcher->GetOriginalURL();
}
void RespondToFetch(const std::string& response_body,
net::HttpStatusCode response_code,
net::URLRequestStatus status) {
net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0);
ASSERT_TRUE(fetcher) << "Tried to respond to fetch that is not ongoing!";
fetcher->SetResponseString(response_body);
fetcher->set_response_code(response_code);
fetcher->set_status(status);
fetcher->delegate()->OnURLFetchComplete(fetcher);
}
void RespondToFetchWithProfile(const SuggestionsProfile& suggestions) {
RespondToFetch(
suggestions.SerializeAsString(), net::HTTP_OK,
net::URLRequestStatus(net::URLRequestStatus::SUCCESS, net::OK));
}
FakeProfileOAuth2TokenService* token_service() { return &token_service_; }
MockSyncService* sync_service() { return &mock_sync_service_; }
MockImageManager* thumbnail_manager() { return mock_thumbnail_manager_; }
MockBlacklistStore* blacklist_store() { return mock_blacklist_store_; }
TestSuggestionsStore* suggestions_store() { return test_suggestions_store_; }
SuggestionsServiceImpl* suggestions_service() {
return suggestions_service_.get();
}
private:
base::MessageLoopForIO io_message_loop_;
TestingPrefServiceSyncable pref_service_;
AccountTrackerService account_tracker_;
TestSigninClient signin_client_;
FakeSigninManagerBase signin_manager_;
net::TestURLFetcherFactory factory_;
FakeProfileOAuth2TokenService token_service_;
MockSyncService mock_sync_service_;
scoped_refptr<net::TestURLRequestContextGetter> request_context_;
// Owned by the SuggestionsService.
MockImageManager* mock_thumbnail_manager_;
MockBlacklistStore* mock_blacklist_store_;
TestSuggestionsStore* test_suggestions_store_;
std::unique_ptr<SuggestionsServiceImpl> suggestions_service_;
DISALLOW_COPY_AND_ASSIGN(SuggestionsServiceTest);
};
TEST_F(SuggestionsServiceTest, FetchSuggestionsData) {
base::MockCallback<SuggestionsService::ResponseCallback> callback;
auto subscription = suggestions_service()->AddCallback(callback.Get());
EXPECT_CALL(*thumbnail_manager(), Initialize(_));
EXPECT_CALL(*blacklist_store(), FilterSuggestions(_));
EXPECT_CALL(*blacklist_store(), GetTimeUntilReadyForUpload(_))
.WillOnce(Return(false));
// Send the request. The data should be returned to the callback.
suggestions_service()->FetchSuggestionsData();
EXPECT_CALL(callback, Run(_));
// Wait for the eventual network request.
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(GetCurrentlyQueriedUrl().is_valid());
EXPECT_EQ(GetCurrentlyQueriedUrl().path(), kSuggestionsUrlPath);
RespondToFetchWithProfile(CreateSuggestionsProfile());
SuggestionsProfile suggestions;
suggestions_store()->LoadSuggestions(&suggestions);
ASSERT_EQ(1, suggestions.suggestions_size());
EXPECT_EQ(kTestTitle, suggestions.suggestions(0).title());
EXPECT_EQ(kTestUrl, suggestions.suggestions(0).url());
EXPECT_EQ(kTestFaviconUrl, suggestions.suggestions(0).favicon_url());
}
TEST_F(SuggestionsServiceTest, IgnoresNoopSyncChange) {
base::MockCallback<SuggestionsService::ResponseCallback> callback;
EXPECT_CALL(callback, Run(_)).Times(0);
auto subscription = suggestions_service()->AddCallback(callback.Get());
// An no-op change should not result in a suggestions refresh.
static_cast<SyncServiceObserver*>(suggestions_service())
->OnStateChanged(sync_service());
// Wait for eventual (but unexpected) network requests.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(suggestions_service()->has_pending_request_for_testing());
}
TEST_F(SuggestionsServiceTest, IgnoresUninterestingSyncChange) {
base::MockCallback<SuggestionsService::ResponseCallback> callback;
EXPECT_CALL(callback, Run(_)).Times(0);
auto subscription = suggestions_service()->AddCallback(callback.Get());
// An uninteresting change should not result in a network request (the
// SyncState is INITIALIZED_ENABLED_HISTORY before and after).
EXPECT_CALL(*sync_service(), GetActiveDataTypes())
.Times(AnyNumber())
.WillRepeatedly(Return(syncer::ModelTypeSet(
syncer::HISTORY_DELETE_DIRECTIVES, syncer::BOOKMARKS)));
static_cast<SyncServiceObserver*>(suggestions_service())
->OnStateChanged(sync_service());
// Wait for eventual (but unexpected) network requests.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(suggestions_service()->has_pending_request_for_testing());
}
// During startup, the state changes from NOT_INITIALIZED_ENABLED to
// INITIALIZED_ENABLED_HISTORY (for a signed-in user with history sync enabled).
// This should *not* result in an automatic fetch.
TEST_F(SuggestionsServiceTest, DoesNotFetchOnStartup) {
// The sync service starts out inactive.
EXPECT_CALL(*sync_service(), IsSyncActive()).WillRepeatedly(Return(false));
static_cast<SyncServiceObserver*>(suggestions_service())
->OnStateChanged(sync_service());
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(suggestions_service()->has_pending_request_for_testing());
// Sync getting enabled should not result in a fetch.
EXPECT_CALL(*sync_service(), IsSyncActive()).WillRepeatedly(Return(true));
static_cast<SyncServiceObserver*>(suggestions_service())
->OnStateChanged(sync_service());
// Wait for eventual (but unexpected) network requests.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(suggestions_service()->has_pending_request_for_testing());
}
TEST_F(SuggestionsServiceTest, BuildUrlWithDefaultMinZeroParamForFewFeature) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(kUseSuggestionsEvenIfFewFeature);
EXPECT_CALL(*thumbnail_manager(), Initialize(_));
EXPECT_CALL(*blacklist_store(), FilterSuggestions(_));
EXPECT_CALL(*blacklist_store(), GetTimeUntilReadyForUpload(_))
.WillOnce(Return(false));
// Send the request. The data should be returned to the callback.
suggestions_service()->FetchSuggestionsData();
// Wait for the eventual network request.
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(GetCurrentlyQueriedUrl().is_valid());
EXPECT_EQ(GetCurrentlyQueriedUrl().path(), kSuggestionsUrlPath);
std::string min_suggestions;
EXPECT_TRUE(net::GetValueForKeyInQuery(GetCurrentlyQueriedUrl(), "min",
&min_suggestions));
EXPECT_EQ(min_suggestions, "0");
RespondToFetchWithProfile(CreateSuggestionsProfile());
}
TEST_F(SuggestionsServiceTest, FetchSuggestionsDataSyncNotInitializedEnabled) {
EXPECT_CALL(*sync_service(), IsSyncActive()).WillRepeatedly(Return(false));
static_cast<SyncServiceObserver*>(suggestions_service())
->OnStateChanged(sync_service());
base::MockCallback<SuggestionsService::ResponseCallback> callback;
EXPECT_CALL(callback, Run(_)).Times(0);
auto subscription = suggestions_service()->AddCallback(callback.Get());
// Try to fetch suggestions. Since sync is not active, no network request
// should be sent.
suggestions_service()->FetchSuggestionsData();
// Wait for eventual (but unexpected) network requests.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(suggestions_service()->has_pending_request_for_testing());
// |suggestions_store()| should still contain the default values.
SuggestionsProfile suggestions;
suggestions_store()->LoadSuggestions(&suggestions);
EXPECT_THAT(suggestions, EqualsProto(CreateSuggestionsProfile()));
}
TEST_F(SuggestionsServiceTest, FetchSuggestionsDataSyncDisabled) {
EXPECT_CALL(*sync_service(), CanSyncStart()).WillRepeatedly(Return(false));
base::MockCallback<SuggestionsService::ResponseCallback> callback;
auto subscription = suggestions_service()->AddCallback(callback.Get());
// Tell SuggestionsService that the sync state changed. The cache should be
// cleared and empty data returned to the callback.
EXPECT_CALL(callback, Run(EqualsProto(SuggestionsProfile())));
static_cast<SyncServiceObserver*>(suggestions_service())
->OnStateChanged(sync_service());
// Try to fetch suggestions. Since sync is not active, no network request
// should be sent.
suggestions_service()->FetchSuggestionsData();
// Wait for eventual (but unexpected) network requests.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(suggestions_service()->has_pending_request_for_testing());
}
TEST_F(SuggestionsServiceTest, FetchSuggestionsDataNoAccessToken) {
token_service()->set_auto_post_fetch_response_on_message_loop(false);
base::MockCallback<SuggestionsService::ResponseCallback> callback;
EXPECT_CALL(callback, Run(_)).Times(0);
auto subscription = suggestions_service()->AddCallback(callback.Get());
EXPECT_CALL(*blacklist_store(), GetTimeUntilReadyForUpload(_))
.WillOnce(Return(false));
suggestions_service()->FetchSuggestionsData();
token_service()->IssueErrorForAllPendingRequests(GoogleServiceAuthError(
GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS));
// Wait for eventual (but unexpected) network requests.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(suggestions_service()->has_pending_request_for_testing());
}
TEST_F(SuggestionsServiceTest, FetchingSuggestionsIgnoresRequestFailure) {
EXPECT_CALL(*blacklist_store(), GetTimeUntilReadyForUpload(_))
.WillOnce(Return(false));
suggestions_service()->FetchSuggestionsData();
// Wait for the eventual network request.
base::RunLoop().RunUntilIdle();
RespondToFetch("irrelevant", net::HTTP_OK,
net::URLRequestStatus(net::URLRequestStatus::FAILED,
net::ERR_INVALID_RESPONSE));
}
TEST_F(SuggestionsServiceTest, FetchingSuggestionsClearsStoreIfResponseNotOK) {
suggestions_store()->StoreSuggestions(CreateSuggestionsProfile());
// Expect that an upload to the blacklist is scheduled.
EXPECT_CALL(*blacklist_store(), GetTimeUntilReadyForUpload(_))
.WillOnce(Return(false));
// Send the request. Empty data will be returned to the callback.
suggestions_service()->FetchSuggestionsData();
// Wait for the eventual network request.
base::RunLoop().RunUntilIdle();
RespondToFetch(
"irrelevant", net::HTTP_BAD_REQUEST,
net::URLRequestStatus(net::URLRequestStatus::SUCCESS, net::OK));
SuggestionsProfile empty_suggestions;
EXPECT_FALSE(suggestions_store()->LoadSuggestions(&empty_suggestions));
}
TEST_F(SuggestionsServiceTest, BlacklistURL) {
// Calling RunUntilIdle on the RunLoop only works when the task is not posted
// for the future.
const base::TimeDelta no_delay = base::TimeDelta::FromSeconds(0);
suggestions_service()->set_blacklist_delay_for_testing(no_delay);
base::MockCallback<SuggestionsService::ResponseCallback> callback;
auto subscription = suggestions_service()->AddCallback(callback.Get());
EXPECT_CALL(*thumbnail_manager(), Initialize(_)).Times(2);
EXPECT_CALL(*blacklist_store(), BlacklistUrl(Eq(GURL(kBlacklistedUrl))))
.WillOnce(Return(true));
EXPECT_CALL(*blacklist_store(), FilterSuggestions(_)).Times(2);
EXPECT_CALL(*blacklist_store(), GetTimeUntilReadyForUpload(_))
.WillOnce(DoAll(SetArgPointee<0>(no_delay), Return(true)))
.WillOnce(Return(false));
EXPECT_CALL(*blacklist_store(), GetCandidateForUpload(_))
.WillOnce(DoAll(SetArgPointee<0>(GURL(kBlacklistedUrl)), Return(true)));
EXPECT_CALL(*blacklist_store(), RemoveUrl(Eq(GURL(kBlacklistedUrl))))
.WillOnce(Return(true));
EXPECT_CALL(callback, Run(_)).Times(2);
EXPECT_TRUE(suggestions_service()->BlacklistURL(GURL(kBlacklistedUrl)));
// Wait on the upload task, the blacklist request and the next blacklist
// scheduling task. This only works when the scheduling task is not for future
// execution (note how both the SuggestionsService's scheduling delay and the
// BlacklistStore's candidacy delay are zero).
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetCurrentlyQueriedUrl().path(), kBlacklistUrlPath);
RespondToFetchWithProfile(CreateSuggestionsProfile());
SuggestionsProfile suggestions;
suggestions_store()->LoadSuggestions(&suggestions);
ASSERT_EQ(1, suggestions.suggestions_size());
EXPECT_EQ(kTestTitle, suggestions.suggestions(0).title());
EXPECT_EQ(kTestUrl, suggestions.suggestions(0).url());
EXPECT_EQ(kTestFaviconUrl, suggestions.suggestions(0).favicon_url());
}
TEST_F(SuggestionsServiceTest, BlacklistURLFails) {
base::MockCallback<SuggestionsService::ResponseCallback> callback;
EXPECT_CALL(callback, Run(_)).Times(0);
auto subscription = suggestions_service()->AddCallback(callback.Get());
EXPECT_CALL(*blacklist_store(), BlacklistUrl(Eq(GURL(kBlacklistedUrl))))
.WillOnce(Return(false));
EXPECT_FALSE(suggestions_service()->BlacklistURL(GURL(kBlacklistedUrl)));
}
TEST_F(SuggestionsServiceTest, RetryBlacklistURLRequestAfterFailure) {
// Calling RunUntilIdle on the RunLoop only works when the task is not
// posted for the future.
const base::TimeDelta no_delay = base::TimeDelta::FromSeconds(0);
suggestions_service()->set_blacklist_delay_for_testing(no_delay);
base::MockCallback<SuggestionsService::ResponseCallback> callback;
auto subscription = suggestions_service()->AddCallback(callback.Get());
// Set expectations for first, failing request.
EXPECT_CALL(*thumbnail_manager(), Initialize(_));
EXPECT_CALL(*blacklist_store(), BlacklistUrl(Eq(GURL(kBlacklistedUrl))))
.WillOnce(Return(true));
EXPECT_CALL(*blacklist_store(), FilterSuggestions(_));
EXPECT_CALL(*blacklist_store(), GetTimeUntilReadyForUpload(_))
.WillOnce(DoAll(SetArgPointee<0>(no_delay), Return(true)))
.WillOnce(DoAll(SetArgPointee<0>(no_delay), Return(true)));
EXPECT_CALL(*blacklist_store(), GetCandidateForUpload(_))
.WillOnce(DoAll(SetArgPointee<0>(GURL(kBlacklistedUrl)), Return(true)));
EXPECT_CALL(callback, Run(_)).Times(2);
// Blacklist call, first request attempt.
EXPECT_TRUE(suggestions_service()->BlacklistURL(GURL(kBlacklistedUrl)));
// Wait for the first scheduling receiving a failing response.
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(GetCurrentlyQueriedUrl().is_valid());
EXPECT_EQ(GetCurrentlyQueriedUrl().path(), kBlacklistUrlPath);
RespondToFetch("irrelevant", net::HTTP_OK,
net::URLRequestStatus(net::URLRequestStatus::FAILED,
net::ERR_INVALID_RESPONSE));
// Assert that the failure was processed as expected.
Mock::VerifyAndClearExpectations(thumbnail_manager());
Mock::VerifyAndClearExpectations(blacklist_store());
// Now expect the retried request to succeed.
EXPECT_CALL(*thumbnail_manager(), Initialize(_));
EXPECT_CALL(*blacklist_store(), FilterSuggestions(_));
EXPECT_CALL(*blacklist_store(), GetTimeUntilReadyForUpload(_))
.WillOnce(Return(false));
EXPECT_CALL(*blacklist_store(), GetCandidateForUpload(_))
.WillOnce(DoAll(SetArgPointee<0>(GURL(kBlacklistedUrl)), Return(true)));
EXPECT_CALL(*blacklist_store(), RemoveUrl(Eq(GURL(kBlacklistedUrl))))
.WillOnce(Return(true));
// Wait for the second scheduling followed by a successful response.
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(GetCurrentlyQueriedUrl().is_valid());
EXPECT_EQ(GetCurrentlyQueriedUrl().path(), kBlacklistUrlPath);
RespondToFetchWithProfile(CreateSuggestionsProfile());
SuggestionsProfile suggestions;
suggestions_store()->LoadSuggestions(&suggestions);
ASSERT_EQ(1, suggestions.suggestions_size());
EXPECT_EQ(kTestTitle, suggestions.suggestions(0).title());
EXPECT_EQ(kTestUrl, suggestions.suggestions(0).url());
EXPECT_EQ(kTestFaviconUrl, suggestions.suggestions(0).favicon_url());
}
TEST_F(SuggestionsServiceTest, UndoBlacklistURL) {
// Ensure scheduling the request doesn't happen before undo.
const base::TimeDelta delay = base::TimeDelta::FromHours(1);
suggestions_service()->set_blacklist_delay_for_testing(delay);
base::MockCallback<SuggestionsService::ResponseCallback> callback;
auto subscription = suggestions_service()->AddCallback(callback.Get());
// Blacklist expectations.
EXPECT_CALL(*blacklist_store(), BlacklistUrl(Eq(GURL(kBlacklistedUrl))))
.WillOnce(Return(true));
EXPECT_CALL(*thumbnail_manager(),
Initialize(EqualsProto(CreateSuggestionsProfile())))
.Times(AnyNumber());
EXPECT_CALL(*blacklist_store(), FilterSuggestions(_)).Times(AnyNumber());
EXPECT_CALL(*blacklist_store(), GetTimeUntilReadyForUpload(_))
.WillOnce(DoAll(SetArgPointee<0>(delay), Return(true)));
// Undo expectations.
EXPECT_CALL(*blacklist_store(),
GetTimeUntilURLReadyForUpload(Eq(GURL(kBlacklistedUrl)), _))
.WillOnce(DoAll(SetArgPointee<1>(delay), Return(true)));
EXPECT_CALL(*blacklist_store(), RemoveUrl(Eq(GURL(kBlacklistedUrl))))
.WillOnce(Return(true));
EXPECT_CALL(callback, Run(_)).Times(2);
EXPECT_TRUE(suggestions_service()->BlacklistURL(GURL(kBlacklistedUrl)));
EXPECT_TRUE(suggestions_service()->UndoBlacklistURL(GURL(kBlacklistedUrl)));
}
TEST_F(SuggestionsServiceTest, ClearBlacklist) {
const base::TimeDelta delay = base::TimeDelta::FromHours(1);
suggestions_service()->set_blacklist_delay_for_testing(delay);
base::MockCallback<SuggestionsService::ResponseCallback> callback;
auto subscription = suggestions_service()->AddCallback(callback.Get());
// Blacklist expectations.
EXPECT_CALL(*blacklist_store(), BlacklistUrl(Eq(GURL(kBlacklistedUrl))))
.WillOnce(Return(true));
EXPECT_CALL(*thumbnail_manager(),
Initialize(EqualsProto(CreateSuggestionsProfile())))
.Times(AnyNumber());
EXPECT_CALL(*blacklist_store(), FilterSuggestions(_)).Times(AnyNumber());
EXPECT_CALL(*blacklist_store(), GetTimeUntilReadyForUpload(_))
.WillOnce(DoAll(SetArgPointee<0>(delay), Return(true)));
EXPECT_CALL(*blacklist_store(), ClearBlacklist());
EXPECT_CALL(callback, Run(_)).Times(2);
EXPECT_TRUE(suggestions_service()->BlacklistURL(GURL(kBlacklistedUrl)));
suggestions_service()->ClearBlacklist();
// Wait for the eventual network request.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetCurrentlyQueriedUrl().path(), kBlacklistClearUrlPath);
}
TEST_F(SuggestionsServiceTest, UndoBlacklistURLFailsIfNotInBlacklist) {
// Ensure scheduling the request doesn't happen before undo.
const base::TimeDelta delay = base::TimeDelta::FromHours(1);
suggestions_service()->set_blacklist_delay_for_testing(delay);
base::MockCallback<SuggestionsService::ResponseCallback> callback;
auto subscription = suggestions_service()->AddCallback(callback.Get());
// Blacklist expectations.
EXPECT_CALL(*blacklist_store(), BlacklistUrl(Eq(GURL(kBlacklistedUrl))))
.WillOnce(Return(true));
EXPECT_CALL(*thumbnail_manager(),
Initialize(EqualsProto(CreateSuggestionsProfile())));
EXPECT_CALL(*blacklist_store(), FilterSuggestions(_));
EXPECT_CALL(*blacklist_store(), GetTimeUntilReadyForUpload(_))
.WillOnce(DoAll(SetArgPointee<0>(delay), Return(true)));
// Undo expectations.
// URL is not in local blacklist.
EXPECT_CALL(*blacklist_store(),
GetTimeUntilURLReadyForUpload(Eq(GURL(kBlacklistedUrl)), _))
.WillOnce(Return(false));
EXPECT_CALL(callback, Run(_));
EXPECT_TRUE(suggestions_service()->BlacklistURL(GURL(kBlacklistedUrl)));
EXPECT_FALSE(suggestions_service()->UndoBlacklistURL(GURL(kBlacklistedUrl)));
}
TEST_F(SuggestionsServiceTest, UndoBlacklistURLFailsIfAlreadyCandidate) {
// Ensure scheduling the request doesn't happen before undo.
const base::TimeDelta delay = base::TimeDelta::FromHours(1);
suggestions_service()->set_blacklist_delay_for_testing(delay);
base::MockCallback<SuggestionsService::ResponseCallback> callback;
auto subscription = suggestions_service()->AddCallback(callback.Get());
// Blacklist expectations.
EXPECT_CALL(*blacklist_store(), BlacklistUrl(Eq(GURL(kBlacklistedUrl))))
.WillOnce(Return(true));
EXPECT_CALL(*thumbnail_manager(),
Initialize(EqualsProto(CreateSuggestionsProfile())));
EXPECT_CALL(*blacklist_store(), FilterSuggestions(_));
EXPECT_CALL(*blacklist_store(), GetTimeUntilReadyForUpload(_))
.WillOnce(DoAll(SetArgPointee<0>(delay), Return(true)));
// URL is not yet candidate for upload.
const base::TimeDelta negative_delay = base::TimeDelta::FromHours(-1);
EXPECT_CALL(*blacklist_store(),
GetTimeUntilURLReadyForUpload(Eq(GURL(kBlacklistedUrl)), _))
.WillOnce(DoAll(SetArgPointee<1>(negative_delay), Return(true)));
EXPECT_CALL(callback, Run(_));
EXPECT_TRUE(suggestions_service()->BlacklistURL(GURL(kBlacklistedUrl)));
EXPECT_FALSE(suggestions_service()->UndoBlacklistURL(GURL(kBlacklistedUrl)));
}
TEST_F(SuggestionsServiceTest, TemporarilyIncreasesBlacklistDelayOnFailure) {
EXPECT_CALL(*thumbnail_manager(), Initialize(_)).Times(AnyNumber());
EXPECT_CALL(*blacklist_store(), FilterSuggestions(_)).Times(AnyNumber());
EXPECT_CALL(*blacklist_store(), GetTimeUntilReadyForUpload(_))
.Times(AnyNumber())
.WillRepeatedly(Return(false));
const base::TimeDelta initial_delay =
suggestions_service()->blacklist_delay_for_testing();
// Delay unchanged on success.
suggestions_service()->FetchSuggestionsData();
base::RunLoop().RunUntilIdle();
RespondToFetchWithProfile(CreateSuggestionsProfile());
EXPECT_EQ(initial_delay,
suggestions_service()->blacklist_delay_for_testing());
// Delay increases on failure.
suggestions_service()->FetchSuggestionsData();
base::RunLoop().RunUntilIdle();
RespondToFetch(
"irrelevant", net::HTTP_BAD_REQUEST,
net::URLRequestStatus(net::URLRequestStatus::SUCCESS, net::OK));
EXPECT_GT(suggestions_service()->blacklist_delay_for_testing(),
initial_delay);
// Delay resets on success.
suggestions_service()->FetchSuggestionsData();
base::RunLoop().RunUntilIdle();
RespondToFetchWithProfile(CreateSuggestionsProfile());
EXPECT_EQ(initial_delay,
suggestions_service()->blacklist_delay_for_testing());
}
TEST_F(SuggestionsServiceTest, DoesNotOverrideDefaultExpiryTime) {
EXPECT_CALL(*thumbnail_manager(), Initialize(_));
EXPECT_CALL(*blacklist_store(), FilterSuggestions(_));
EXPECT_CALL(*blacklist_store(), GetTimeUntilReadyForUpload(_))
.WillOnce(Return(false));
suggestions_service()->FetchSuggestionsData();
base::RunLoop().RunUntilIdle();
// Creates one suggestion without timestamp and adds a second with timestamp.
SuggestionsProfile profile = CreateSuggestionsProfile();
ChromeSuggestion* suggestion = profile.add_suggestions();
suggestion->set_title(kTestTitle);
suggestion->set_url(kTestUrl);
suggestion->set_expiry_ts(kTestSetExpiry);
RespondToFetchWithProfile(profile);
SuggestionsProfile suggestions;
suggestions_store()->LoadSuggestions(&suggestions);
ASSERT_EQ(2, suggestions.suggestions_size());
// Suggestion[0] had no time stamp and should be ahead of the old suggestion.
EXPECT_LT(kTestSetExpiry, suggestions.suggestions(0).expiry_ts());
// Suggestion[1] had a very old time stamp but should not be updated.
EXPECT_EQ(kTestSetExpiry, suggestions.suggestions(1).expiry_ts());
}
TEST_F(SuggestionsServiceTest, GetPageThumbnail) {
const GURL test_url(kTestUrl);
const GURL thumbnail_url("https://www.thumbnails.com/thumb.jpg");
base::Callback<void(const GURL&, const gfx::Image&)> dummy_callback;
EXPECT_CALL(*thumbnail_manager(), GetImageForURL(test_url, _));
suggestions_service()->GetPageThumbnail(test_url, dummy_callback);
EXPECT_CALL(*thumbnail_manager(), AddImageURL(test_url, thumbnail_url));
EXPECT_CALL(*thumbnail_manager(), GetImageForURL(test_url, _));
suggestions_service()->GetPageThumbnailWithURL(test_url, thumbnail_url,
dummy_callback);
}
} // namespace suggestions