blob: f79296ea2583c8b222a1150869efae83bfea299d [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 "components/ntp_snippets/breaking_news/subscription_manager_impl.h"
#include "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "base/test/histogram_tester.h"
#include "build/build_config.h"
#include "components/ntp_snippets/pref_names.h"
#include "components/ntp_snippets/remote/test_utils.h"
#include "components/prefs/testing_pref_service.h"
#include "net/base/net_errors.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_request_test_util.h"
#include "services/identity/public/cpp/identity_test_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::ElementsAre;
namespace ntp_snippets {
#if !defined(OS_CHROMEOS)
const char kTestEmail[] = "test@email.com";
#endif
const char kAPIKey[] = "fakeAPIkey";
const char kSubscriptionUrl[] = "http://valid-url.test/subscribe";
const char kSubscriptionUrlSignedIn[] = "http://valid-url.test/subscribe";
;
const char kSubscriptionUrlSignedOut[] =
"http://valid-url.test/subscribe?key=fakeAPIkey";
const char kUnsubscriptionUrl[] = "http://valid-url.test/unsubscribe";
const char kUnsubscriptionUrlSignedIn[] = "http://valid-url.test/unsubscribe";
const char kUnsubscriptionUrlSignedOut[] =
"http://valid-url.test/unsubscribe?key=fakeAPIkey";
class SubscriptionManagerImplTest : public testing::Test {
public:
SubscriptionManagerImplTest()
: request_context_getter_(
new net::TestURLRequestContextGetter(message_loop_.task_runner())) {
}
void SetUp() override {
SubscriptionManagerImpl::RegisterProfilePrefs(
utils_.pref_service()->registry());
}
scoped_refptr<net::URLRequestContextGetter> GetRequestContext() {
return request_context_getter_.get();
}
PrefService* GetPrefService() { return utils_.pref_service(); }
identity::IdentityTestEnvironment* GetIdentityTestEnv() {
return &identity_test_env_;
}
std::unique_ptr<SubscriptionManagerImpl> BuildSubscriptionManager() {
return std::make_unique<SubscriptionManagerImpl>(
GetRequestContext(), GetPrefService(),
/*variations_service=*/nullptr,
GetIdentityTestEnv()->identity_manager(),
/*locale=*/"", kAPIKey, GURL(kSubscriptionUrl),
GURL(kUnsubscriptionUrl));
}
net::TestURLFetcher* GetRunningFetcher() {
// All created TestURLFetchers have ID 0 by default.
net::TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(0);
DCHECK(url_fetcher);
return url_fetcher;
}
void RespondToSubscriptionRequestSuccessfully(bool is_signed_in) {
net::TestURLFetcher* url_fetcher = GetRunningFetcher();
if (is_signed_in) {
ASSERT_EQ(GURL(kSubscriptionUrlSignedIn), url_fetcher->GetOriginalURL());
} else {
ASSERT_EQ(GURL(kSubscriptionUrlSignedOut), url_fetcher->GetOriginalURL());
}
RespondSuccessfully();
}
void RespondToUnsubscriptionRequestSuccessfully(bool is_signed_in) {
net::TestURLFetcher* url_fetcher = GetRunningFetcher();
if (is_signed_in) {
ASSERT_EQ(GURL(kUnsubscriptionUrlSignedIn),
url_fetcher->GetOriginalURL());
} else {
ASSERT_EQ(GURL(kUnsubscriptionUrlSignedOut),
url_fetcher->GetOriginalURL());
}
RespondSuccessfully();
}
void RespondToSubscriptionWithError(bool is_signed_in, int error_code) {
net::TestURLFetcher* url_fetcher = GetRunningFetcher();
if (is_signed_in) {
ASSERT_EQ(GURL(kSubscriptionUrlSignedIn), url_fetcher->GetOriginalURL());
} else {
ASSERT_EQ(GURL(kSubscriptionUrlSignedOut), url_fetcher->GetOriginalURL());
}
RespondWithError(error_code);
}
void RespondToUnsubscriptionWithError(bool is_signed_in, int error_code) {
net::TestURLFetcher* url_fetcher = GetRunningFetcher();
if (is_signed_in) {
ASSERT_EQ(GURL(kUnsubscriptionUrlSignedIn),
url_fetcher->GetOriginalURL());
} else {
ASSERT_EQ(GURL(kUnsubscriptionUrlSignedOut),
url_fetcher->GetOriginalURL());
}
RespondWithError(error_code);
}
#if !defined(OS_CHROMEOS)
void SignIn() {
GetIdentityTestEnv()->MakePrimaryAccountAvailable(kTestEmail);
}
void SignOut() { GetIdentityTestEnv()->ClearPrimaryAccount(); }
#endif // !defined(OS_CHROMEOS)
private:
void RespondSuccessfully() {
net::TestURLFetcher* url_fetcher = GetRunningFetcher();
url_fetcher->set_status(net::URLRequestStatus());
url_fetcher->set_response_code(net::HTTP_OK);
url_fetcher->SetResponseString(std::string());
// Call the URLFetcher delegate to continue the test.
url_fetcher->delegate()->OnURLFetchComplete(url_fetcher);
}
void RespondWithError(int error_code) {
net::TestURLFetcher* url_fetcher = GetRunningFetcher();
url_fetcher->set_status(net::URLRequestStatus::FromError(error_code));
url_fetcher->SetResponseString(std::string());
// Call the URLFetcher delegate to continue the test.
url_fetcher->delegate()->OnURLFetchComplete(url_fetcher);
}
base::MessageLoop message_loop_;
test::RemoteSuggestionsTestUtils utils_;
identity::IdentityTestEnvironment identity_test_env_;
scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
net::TestURLFetcherFactory url_fetcher_factory_;
};
TEST_F(SubscriptionManagerImplTest, SubscribeSuccessfully) {
std::string subscription_token = "1234567890";
std::unique_ptr<SubscriptionManagerImpl> manager = BuildSubscriptionManager();
manager->Subscribe(subscription_token);
RespondToSubscriptionRequestSuccessfully(/*is_signed_in=*/false);
ASSERT_TRUE(manager->IsSubscribed());
EXPECT_EQ(subscription_token, GetPrefService()->GetString(
prefs::kBreakingNewsSubscriptionDataToken));
EXPECT_FALSE(GetPrefService()->GetBoolean(
prefs::kBreakingNewsSubscriptionDataIsAuthenticated));
}
// This test is relevant only on non-ChromeOS platforms, as the flow being
// tested here is not possible on ChromeOS.
#if !defined(OS_CHROMEOS)
TEST_F(SubscriptionManagerImplTest,
ShouldSubscribeWithAuthenticationWhenAuthenticated) {
// Sign in.
SignIn();
// Create manager and subscribe.
std::string subscription_token = "1234567890";
std::unique_ptr<SubscriptionManagerImpl> manager = BuildSubscriptionManager();
manager->Subscribe(subscription_token);
// Wait for the access token request and issue the access token.
GetIdentityTestEnv()->WaitForAccessTokenRequestAndRespondWithToken(
"access_token", base::Time::Max());
ASSERT_FALSE(manager->IsSubscribed());
RespondToSubscriptionRequestSuccessfully(/*is_signed_in=*/true);
ASSERT_TRUE(manager->IsSubscribed());
// Check that we are now subscribed correctly with authentication.
EXPECT_EQ(subscription_token, GetPrefService()->GetString(
prefs::kBreakingNewsSubscriptionDataToken));
EXPECT_TRUE(GetPrefService()->GetBoolean(
prefs::kBreakingNewsSubscriptionDataIsAuthenticated));
}
#endif
TEST_F(SubscriptionManagerImplTest, ShouldNotSubscribeIfError) {
std::string subscription_token = "1234567890";
std::unique_ptr<SubscriptionManagerImpl> manager = BuildSubscriptionManager();
manager->Subscribe(subscription_token);
RespondToSubscriptionWithError(/*is_signed_in=*/false, net::ERR_TIMED_OUT);
EXPECT_FALSE(manager->IsSubscribed());
}
TEST_F(SubscriptionManagerImplTest, UnsubscribeSuccessfully) {
std::string subscription_token = "1234567890";
std::unique_ptr<SubscriptionManagerImpl> manager = BuildSubscriptionManager();
manager->Subscribe(subscription_token);
RespondToSubscriptionRequestSuccessfully(/*is_signed_in=*/false);
ASSERT_TRUE(manager->IsSubscribed());
manager->Unsubscribe();
RespondToUnsubscriptionRequestSuccessfully(/*is_signed_in=*/false);
EXPECT_FALSE(manager->IsSubscribed());
EXPECT_FALSE(
GetPrefService()->HasPrefPath(prefs::kBreakingNewsSubscriptionDataToken));
}
TEST_F(SubscriptionManagerImplTest,
ShouldRemainSubscribedIfErrorDuringUnsubscribe) {
std::string subscription_token = "1234567890";
std::unique_ptr<SubscriptionManagerImpl> manager = BuildSubscriptionManager();
manager->Subscribe(subscription_token);
RespondToSubscriptionRequestSuccessfully(/*is_signed_in=*/false);
ASSERT_TRUE(manager->IsSubscribed());
manager->Unsubscribe();
RespondToUnsubscriptionWithError(/*is_signed_in=*/false, net::ERR_TIMED_OUT);
ASSERT_TRUE(manager->IsSubscribed());
EXPECT_EQ(subscription_token, GetPrefService()->GetString(
prefs::kBreakingNewsSubscriptionDataToken));
}
// This test is relevant only on non-ChromeOS platforms, as the flow being
// tested here is not possible on ChromeOS.
#if !defined(OS_CHROMEOS)
TEST_F(SubscriptionManagerImplTest,
ShouldResubscribeIfSignInAfterSubscription) {
// Create manager and subscribe.
std::string subscription_token = "1234567890";
std::unique_ptr<SubscriptionManagerImpl> manager = BuildSubscriptionManager();
manager->Subscribe(subscription_token);
RespondToSubscriptionRequestSuccessfully(/*is_signed_in=*/false);
ASSERT_FALSE(manager->NeedsToResubscribe());
// Sign in. This should trigger a resubscribe.
SignIn();
ASSERT_TRUE(manager->NeedsToResubscribe());
// Wait for the access token request that should occur and grant the access
// token.
GetIdentityTestEnv()->WaitForAccessTokenRequestAndRespondWithToken(
"access_token", base::Time::Max());
RespondToSubscriptionRequestSuccessfully(/*is_signed_in=*/true);
// Check that we are now subscribed with authentication.
EXPECT_TRUE(GetPrefService()->GetBoolean(
prefs::kBreakingNewsSubscriptionDataIsAuthenticated));
}
#endif
// This test is relevant only on non-ChromeOS platforms, as the flow being
// tested here is not possible on ChromeOS.
#if !defined(OS_CHROMEOS)
TEST_F(SubscriptionManagerImplTest,
ShouldResubscribeIfSignOutAfterSubscription) {
// Signin and subscribe.
SignIn();
std::string subscription_token = "1234567890";
std::unique_ptr<SubscriptionManagerImpl> manager = BuildSubscriptionManager();
manager->Subscribe(subscription_token);
GetIdentityTestEnv()->WaitForAccessTokenRequestAndRespondWithToken(
"access_token", base::Time::Max());
RespondToSubscriptionRequestSuccessfully(/*is_signed_in=*/true);
// Sign out; this should trigger a resubscribe.
SignOut();
EXPECT_TRUE(manager->NeedsToResubscribe());
RespondToSubscriptionRequestSuccessfully(/*is_signed_in=*/false);
// Check that we are now subscribed without authentication.
EXPECT_FALSE(GetPrefService()->GetBoolean(
prefs::kBreakingNewsSubscriptionDataIsAuthenticated));
}
#endif
TEST_F(SubscriptionManagerImplTest,
ShouldUpdateTokenInPrefWhenResubscribeWithChangeInToken) {
// Create manager and subscribe.
std::string old_subscription_token = "1234567890";
std::unique_ptr<SubscriptionManagerImpl> manager = BuildSubscriptionManager();
manager->Subscribe(old_subscription_token);
RespondToSubscriptionRequestSuccessfully(/*is_signed_in=*/false);
EXPECT_EQ(
old_subscription_token,
GetPrefService()->GetString(prefs::kBreakingNewsSubscriptionDataToken));
// Resubscribe with a new token.
std::string new_subscription_token = "0987654321";
manager->Resubscribe(new_subscription_token);
// Resubscribe with a new token should issue an unsubscribe request before
// subscribing.
RespondToUnsubscriptionRequestSuccessfully(/*is_signed_in=*/false);
RespondToSubscriptionRequestSuccessfully(/*is_signed_in=*/false);
// Check we are now subscribed with the new token.
EXPECT_EQ(
new_subscription_token,
GetPrefService()->GetString(prefs::kBreakingNewsSubscriptionDataToken));
}
TEST_F(SubscriptionManagerImplTest, ShouldReportSubscriptionResult) {
base::HistogramTester histogram_tester;
// Create manager and subscribe.
const std::string subscription_token = "token";
std::unique_ptr<SubscriptionManagerImpl> manager = BuildSubscriptionManager();
manager->Subscribe(subscription_token);
// TODO(vitaliii): Mock subscription request to avoid this low level errors.
RespondToSubscriptionWithError(/*is_signed_in=*/false,
/*error_code=*/net::ERR_INVALID_RESPONSE);
EXPECT_THAT(
histogram_tester.GetAllSamples("NewTabPage.ContentSuggestions."
"BreakingNews.SubscriptionRequestStatus"),
ElementsAre(base::Bucket(
/*min=*/static_cast<int>(StatusCode::TEMPORARY_ERROR),
/*count=*/1)));
}
TEST_F(SubscriptionManagerImplTest, ShouldReportUnsubscriptionResult) {
base::HistogramTester histogram_tester;
// Create manager and subscribe.
const std::string subscription_token = "token";
std::unique_ptr<SubscriptionManagerImpl> manager = BuildSubscriptionManager();
manager->Subscribe(subscription_token);
RespondToSubscriptionRequestSuccessfully(/*is_signed_in=*/false);
manager->Unsubscribe();
RespondToUnsubscriptionWithError(/*is_signed_in=*/false,
/*error_code=*/net::ERR_INVALID_RESPONSE);
EXPECT_THAT(histogram_tester.GetAllSamples("NewTabPage.ContentSuggestions."
"BreakingNews."
"UnsubscriptionRequestStatus"),
ElementsAre(base::Bucket(
/*min=*/static_cast<int>(StatusCode::TEMPORARY_ERROR),
/*count=*/1)));
}
} // namespace ntp_snippets