blob: fcafe78b46904264c3ada7b11d936ee65d4e2f36 [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/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_task_environment.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 "services/identity/public/cpp/identity_test_environment.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.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()
: test_shared_loader_factory_(
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_)) {}
void SetUp() override {
SubscriptionManagerImpl::RegisterProfilePrefs(
utils_.pref_service()->registry());
}
scoped_refptr<network::SharedURLLoaderFactory> GetSharedURLLoaderFactory() {
return test_shared_loader_factory_;
}
PrefService* GetPrefService() { return utils_.pref_service(); }
identity::IdentityTestEnvironment* GetIdentityTestEnv() {
return &identity_test_env_;
}
std::unique_ptr<SubscriptionManagerImpl> BuildSubscriptionManager() {
return std::make_unique<SubscriptionManagerImpl>(
GetSharedURLLoaderFactory(), GetPrefService(),
/*variations_service=*/nullptr,
GetIdentityTestEnv()->identity_manager(),
/*locale=*/"", kAPIKey, GURL(kSubscriptionUrl),
GURL(kUnsubscriptionUrl));
}
void RespondToSubscriptionRequestSuccessfully(bool is_signed_in) {
if (is_signed_in) {
RespondSuccessfully(GURL(kSubscriptionUrlSignedIn));
} else {
RespondSuccessfully(GURL(kSubscriptionUrlSignedOut));
}
}
void RespondToUnsubscriptionRequestSuccessfully(bool is_signed_in) {
if (is_signed_in) {
RespondSuccessfully(GURL(kUnsubscriptionUrlSignedIn));
} else {
RespondSuccessfully(GURL(kUnsubscriptionUrlSignedOut));
}
}
void RespondToSubscriptionWithError(bool is_signed_in, int error_code) {
if (is_signed_in) {
RespondWithError(GURL(kSubscriptionUrlSignedIn), error_code);
} else {
RespondWithError(GURL(kSubscriptionUrlSignedOut), error_code);
}
}
void RespondToUnsubscriptionWithError(bool is_signed_in, int error_code) {
if (is_signed_in) {
RespondWithError(GURL(kUnsubscriptionUrlSignedIn), error_code);
} else {
RespondWithError(GURL(kUnsubscriptionUrlSignedOut), error_code);
}
}
#if !defined(OS_CHROMEOS)
void SignIn() {
GetIdentityTestEnv()->MakePrimaryAccountAvailable(kTestEmail);
}
void SignOut() { GetIdentityTestEnv()->ClearPrimaryAccount(); }
#endif // !defined(OS_CHROMEOS)
private:
void RespondSuccessfully(const GURL& url) {
test_url_loader_factory_.AddResponse(url.spec(), "");
base::RunLoop().RunUntilIdle();
}
void RespondWithError(const GURL& url, int error_code) {
network::URLLoaderCompletionStatus status(error_code);
test_url_loader_factory_.AddResponse(url, network::ResourceResponseHead(),
"", status);
base::RunLoop().RunUntilIdle();
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
test::RemoteSuggestionsTestUtils utils_;
identity::IdentityTestEnvironment identity_test_env_;
network::TestURLLoaderFactory test_url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_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()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
"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()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
"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()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
"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