blob: b6847009f3303a0e332bbdbb84fe3d04e6002b8d [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 <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "base/message_loop/message_loop.h"
#include "base/strings/stringprintf.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/prefs/testing_pref_service.h"
#include "components/signin/core/browser/account_fetcher_service.h"
#include "components/signin/core/browser/account_info.h"
#include "components/signin/core/browser/account_tracker_service.h"
#include "components/signin/core/browser/fake_account_fetcher_service.h"
#include "components/signin/core/browser/test_signin_client.h"
#include "components/signin/core/common/signin_pref_names.h"
#include "google_apis/gaia/fake_oauth2_token_service.h"
#include "google_apis/gaia/gaia_oauth_client.h"
#include "net/http/http_status_code.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const std::string kTokenInfoResponseFormat =
"{ \
\"id\": \"%s\", \
\"email\": \"%s\", \
\"hd\": \"\", \
\"name\": \"%s\", \
\"given_name\": \"%s\", \
\"locale\": \"%s\", \
\"picture\": \"%s\" \
}";
const std::string kTokenInfoIncompleteResponseFormat =
"{ \
\"id\": \"%s\", \
\"email\": \"%s\", \
\"hd\": \"\", \
}";
enum TrackingEventType {
UPDATED,
REMOVED,
};
std::string AccountIdToEmail(const std::string& account_id) {
return account_id + "@gmail.com";
}
std::string AccountIdToGaiaId(const std::string& account_id) {
return "gaia-" + account_id;
}
std::string AccountIdToFullName(const std::string& account_id) {
return "full-name-" + account_id;
}
std::string AccountIdToGivenName(const std::string& account_id) {
return "given-name-" + account_id;
}
std::string AccountIdToLocale(const std::string& account_id) {
return "locale-" + account_id;
}
std::string AccountIdToPictureURL(const std::string& account_id) {
return "picture_url-" + account_id;
}
void CheckAccountDetails(const std::string& account_id,
const AccountInfo& info) {
EXPECT_EQ(account_id, info.account_id);
EXPECT_EQ(AccountIdToGaiaId(account_id), info.gaia);
EXPECT_EQ(AccountIdToEmail(account_id), info.email);
EXPECT_EQ(AccountTrackerService::kNoHostedDomainFound,
info.hosted_domain);
EXPECT_EQ(AccountIdToFullName(account_id), info.full_name);
EXPECT_EQ(AccountIdToGivenName(account_id), info.given_name);
EXPECT_EQ(AccountIdToLocale(account_id), info.locale);
}
void FakeUserInfoFetchSuccess(FakeAccountFetcherService* fetcher,
const std::string& account_id) {
fetcher->FakeUserInfoFetchSuccess(
account_id, AccountIdToEmail(account_id), AccountIdToGaiaId(account_id),
AccountTrackerService::kNoHostedDomainFound,
AccountIdToFullName(account_id), AccountIdToGivenName(account_id),
AccountIdToLocale(account_id), AccountIdToPictureURL(account_id));
}
class TrackingEvent {
public:
TrackingEvent(TrackingEventType type,
const std::string& account_id,
const std::string& gaia_id)
: type_(type),
account_id_(account_id),
gaia_id_(gaia_id) {}
TrackingEvent(TrackingEventType type,
const std::string& account_id)
: type_(type),
account_id_(account_id),
gaia_id_(AccountIdToGaiaId(account_id)) {}
bool operator==(const TrackingEvent& event) const {
return type_ == event.type_ && account_id_ == event.account_id_ &&
gaia_id_ == event.gaia_id_;
}
std::string ToString() const {
const char * typestr = "INVALID";
switch (type_) {
case UPDATED:
typestr = "UPD";
break;
case REMOVED:
typestr = "REM";
break;
}
return base::StringPrintf("{ type: %s, account_id: %s, gaia: %s }",
typestr,
account_id_.c_str(),
gaia_id_.c_str());
}
private:
friend bool CompareByUser(TrackingEvent a, TrackingEvent b);
TrackingEventType type_;
std::string account_id_;
std::string gaia_id_;
};
bool CompareByUser(TrackingEvent a, TrackingEvent b) {
return a.account_id_ < b.account_id_;
}
std::string Str(const std::vector<TrackingEvent>& events) {
std::string str = "[";
bool needs_comma = false;
for (std::vector<TrackingEvent>::const_iterator it =
events.begin(); it != events.end(); ++it) {
if (needs_comma)
str += ",\n ";
needs_comma = true;
str += it->ToString();
}
str += "]";
return str;
}
class AccountTrackerObserver : public AccountTrackerService::Observer {
public:
AccountTrackerObserver() {}
~AccountTrackerObserver() override {}
void Clear();
void SortEventsByUser();
testing::AssertionResult CheckEvents();
testing::AssertionResult CheckEvents(const TrackingEvent& e1);
testing::AssertionResult CheckEvents(const TrackingEvent& e1,
const TrackingEvent& e2);
testing::AssertionResult CheckEvents(const TrackingEvent& e1,
const TrackingEvent& e2,
const TrackingEvent& e3);
private:
// AccountTrackerService::Observer implementation
void OnAccountUpdated(const AccountInfo& ids) override;
void OnAccountRemoved(const AccountInfo& ids) override;
testing::AssertionResult CheckEvents(
const std::vector<TrackingEvent>& events);
std::vector<TrackingEvent> events_;
};
void AccountTrackerObserver::OnAccountUpdated(const AccountInfo& ids) {
events_.push_back(TrackingEvent(UPDATED, ids.account_id, ids.gaia));
}
void AccountTrackerObserver::OnAccountRemoved(const AccountInfo& ids) {
events_.push_back(TrackingEvent(REMOVED, ids.account_id, ids.gaia));
}
void AccountTrackerObserver::Clear() {
events_.clear();
}
void AccountTrackerObserver::SortEventsByUser() {
std::stable_sort(events_.begin(), events_.end(), CompareByUser);
}
testing::AssertionResult AccountTrackerObserver::CheckEvents() {
std::vector<TrackingEvent> events;
return CheckEvents(events);
}
testing::AssertionResult AccountTrackerObserver::CheckEvents(
const TrackingEvent& e1) {
std::vector<TrackingEvent> events;
events.push_back(e1);
return CheckEvents(events);
}
testing::AssertionResult AccountTrackerObserver::CheckEvents(
const TrackingEvent& e1,
const TrackingEvent& e2) {
std::vector<TrackingEvent> events;
events.push_back(e1);
events.push_back(e2);
return CheckEvents(events);
}
testing::AssertionResult AccountTrackerObserver::CheckEvents(
const TrackingEvent& e1,
const TrackingEvent& e2,
const TrackingEvent& e3) {
std::vector<TrackingEvent> events;
events.push_back(e1);
events.push_back(e2);
events.push_back(e3);
return CheckEvents(events);
}
testing::AssertionResult AccountTrackerObserver::CheckEvents(
const std::vector<TrackingEvent>& events) {
std::string maybe_newline = (events.size() + events_.size()) > 2 ? "\n" : "";
testing::AssertionResult result(
(events_ == events)
? testing::AssertionSuccess()
: (testing::AssertionFailure()
<< "Expected " << maybe_newline << Str(events) << ", "
<< maybe_newline << "Got " << maybe_newline << Str(events_)));
events_.clear();
return result;
}
} // namespace
class AccountTrackerServiceTest : public testing::Test {
public:
AccountTrackerServiceTest() {}
~AccountTrackerServiceTest() override {}
void SetUp() override {
fake_oauth2_token_service_.reset(new FakeOAuth2TokenService());
pref_service_.registry()->RegisterListPref(
AccountTrackerService::kAccountInfoPref);
pref_service_.registry()->RegisterIntegerPref(
prefs::kAccountIdMigrationState,
AccountTrackerService::MIGRATION_NOT_STARTED);
pref_service_.registry()->RegisterInt64Pref(
AccountFetcherService::kLastUpdatePref, 0);
signin_client_.reset(new TestSigninClient(&pref_service_));
signin_client_.get()->SetURLRequestContext(
new net::TestURLRequestContextGetter(message_loop_.task_runner()));
account_tracker_.reset(new AccountTrackerService());
account_tracker_->Initialize(signin_client_.get());
account_fetcher_.reset(new AccountFetcherService());
account_fetcher_->Initialize(signin_client_.get(),
fake_oauth2_token_service_.get(),
account_tracker_.get());
account_fetcher_->EnableNetworkFetchesForTest();
}
void TearDown() override {
account_fetcher_->Shutdown();
account_tracker_->Shutdown();
}
void SimulateTokenAvailable(const std::string& account_id) {
fake_oauth2_token_service_->AddAccount(account_id);
}
void SimulateTokenRevoked(const std::string& account_id) {
fake_oauth2_token_service_->RemoveAccount(account_id);
}
// Helpers to fake access token and user info fetching
void IssueAccessToken(const std::string& account_id) {
fake_oauth2_token_service_->IssueAllTokensForAccount(
account_id, "access_token-" + account_id, base::Time::Max());
}
std::string GenerateValidTokenInfoResponse(const std::string& account_id) {
return base::StringPrintf(
kTokenInfoResponseFormat.c_str(),
AccountIdToGaiaId(account_id).c_str(),
AccountIdToEmail(account_id).c_str(),
AccountIdToFullName(account_id).c_str(),
AccountIdToGivenName(account_id).c_str(),
AccountIdToLocale(account_id).c_str(),
AccountIdToPictureURL(account_id).c_str());
}
std::string GenerateIncompleteTokenInfoResponse(
const std::string& account_id) {
return base::StringPrintf(
kTokenInfoIncompleteResponseFormat.c_str(),
AccountIdToGaiaId(account_id).c_str(),
AccountIdToEmail(account_id).c_str());
}
void ReturnOAuthUrlFetchSuccess(const std::string& account_id);
void ReturnOAuthUrlFetchSuccessIncomplete(const std::string& account_id);
void ReturnOAuthUrlFetchFailure(const std::string& account_id);
net::TestURLFetcherFactory* test_fetcher_factory() {
return &test_fetcher_factory_;
}
AccountFetcherService* account_fetcher() { return account_fetcher_.get(); }
AccountTrackerService* account_tracker() { return account_tracker_.get(); }
OAuth2TokenService* token_service() {
return fake_oauth2_token_service_.get();
}
SigninClient* signin_client() { return signin_client_.get(); }
private:
void ReturnOAuthUrlFetchResults(int fetcher_id,
net::HttpStatusCode response_code,
const std::string& response_string);
base::MessageLoopForIO message_loop_;
net::TestURLFetcherFactory test_fetcher_factory_;
std::unique_ptr<FakeOAuth2TokenService> fake_oauth2_token_service_;
TestingPrefServiceSimple pref_service_;
std::unique_ptr<AccountFetcherService> account_fetcher_;
std::unique_ptr<AccountTrackerService> account_tracker_;
std::unique_ptr<TestSigninClient> signin_client_;
};
void AccountTrackerServiceTest::ReturnOAuthUrlFetchResults(
int fetcher_id,
net::HttpStatusCode response_code,
const std::string& response_string) {
net::TestURLFetcher* fetcher =
test_fetcher_factory_.GetFetcherByID(fetcher_id);
ASSERT_TRUE(fetcher);
fetcher->set_response_code(response_code);
fetcher->SetResponseString(response_string);
fetcher->delegate()->OnURLFetchComplete(fetcher);
}
void AccountTrackerServiceTest::ReturnOAuthUrlFetchSuccess(
const std::string& account_id) {
IssueAccessToken(account_id);
ReturnOAuthUrlFetchResults(gaia::GaiaOAuthClient::kUrlFetcherId,
net::HTTP_OK,
GenerateValidTokenInfoResponse(account_id));
}
void AccountTrackerServiceTest::ReturnOAuthUrlFetchSuccessIncomplete(
const std::string& account_id) {
IssueAccessToken(account_id);
ReturnOAuthUrlFetchResults(gaia::GaiaOAuthClient::kUrlFetcherId,
net::HTTP_OK,
GenerateIncompleteTokenInfoResponse(account_id));
}
void AccountTrackerServiceTest::ReturnOAuthUrlFetchFailure(
const std::string& account_id) {
IssueAccessToken(account_id);
ReturnOAuthUrlFetchResults(
gaia::GaiaOAuthClient::kUrlFetcherId, net::HTTP_BAD_REQUEST, "");
}
TEST_F(AccountTrackerServiceTest, Basic) {
}
TEST_F(AccountTrackerServiceTest, TokenAvailable) {
AccountTrackerObserver observer;
account_tracker()->AddObserver(&observer);
SimulateTokenAvailable("alpha");
ASSERT_FALSE(account_fetcher()->IsAllUserInfoFetched());
ASSERT_TRUE(observer.CheckEvents());
account_tracker()->RemoveObserver(&observer);
}
TEST_F(AccountTrackerServiceTest, TokenAvailable_Revoked) {
AccountTrackerObserver observer;
account_tracker()->AddObserver(&observer);
SimulateTokenAvailable("alpha");
SimulateTokenRevoked("alpha");
ASSERT_TRUE(account_fetcher()->IsAllUserInfoFetched());
ASSERT_TRUE(observer.CheckEvents());
account_tracker()->RemoveObserver(&observer);
}
TEST_F(AccountTrackerServiceTest, TokenAvailable_UserInfo) {
AccountTrackerObserver observer;
account_tracker()->AddObserver(&observer);
SimulateTokenAvailable("alpha");
ReturnOAuthUrlFetchSuccess("alpha");
ASSERT_TRUE(account_fetcher()->IsAllUserInfoFetched());
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(UPDATED, "alpha")));
account_tracker()->RemoveObserver(&observer);
}
TEST_F(AccountTrackerServiceTest, TokenAvailable_UserInfo_Revoked) {
AccountTrackerObserver observer;
account_tracker()->AddObserver(&observer);
SimulateTokenAvailable("alpha");
ReturnOAuthUrlFetchSuccess("alpha");
ASSERT_TRUE(account_fetcher()->IsAllUserInfoFetched());
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(UPDATED, "alpha")));
SimulateTokenRevoked("alpha");
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(REMOVED, "alpha")));
account_tracker()->RemoveObserver(&observer);
}
TEST_F(AccountTrackerServiceTest, TokenAvailable_UserInfoFailed) {
AccountTrackerObserver observer;
account_tracker()->AddObserver(&observer);
SimulateTokenAvailable("alpha");
ReturnOAuthUrlFetchFailure("alpha");
ASSERT_TRUE(account_fetcher()->IsAllUserInfoFetched());
ASSERT_TRUE(observer.CheckEvents());
account_tracker()->RemoveObserver(&observer);
}
TEST_F(AccountTrackerServiceTest, TokenAvailableTwice_UserInfoOnce) {
AccountTrackerObserver observer;
account_tracker()->AddObserver(&observer);
SimulateTokenAvailable("alpha");
ReturnOAuthUrlFetchSuccess("alpha");
ASSERT_TRUE(account_fetcher()->IsAllUserInfoFetched());
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(UPDATED, "alpha")));
SimulateTokenAvailable("alpha");
ASSERT_TRUE(account_fetcher()->IsAllUserInfoFetched());
ASSERT_TRUE(observer.CheckEvents());
account_tracker()->RemoveObserver(&observer);
}
TEST_F(AccountTrackerServiceTest, TokenAlreadyExists) {
SimulateTokenAvailable("alpha");
AccountTrackerService tracker;
AccountTrackerObserver observer;
AccountFetcherService fetcher;
tracker.AddObserver(&observer);
tracker.Initialize(signin_client());
fetcher.Initialize(signin_client(), token_service(), &tracker);
fetcher.EnableNetworkFetchesForTest();
ASSERT_FALSE(fetcher.IsAllUserInfoFetched());
ASSERT_TRUE(observer.CheckEvents());
tracker.RemoveObserver(&observer);
tracker.Shutdown();
fetcher.Shutdown();
}
TEST_F(AccountTrackerServiceTest, TwoTokenAvailable_TwoUserInfo) {
AccountTrackerObserver observer;
account_tracker()->AddObserver(&observer);
SimulateTokenAvailable("alpha");
SimulateTokenAvailable("beta");
ReturnOAuthUrlFetchSuccess("alpha");
ReturnOAuthUrlFetchSuccess("beta");
ASSERT_TRUE(account_fetcher()->IsAllUserInfoFetched());
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(UPDATED, "alpha"),
TrackingEvent(UPDATED, "beta")));
account_tracker()->RemoveObserver(&observer);
}
TEST_F(AccountTrackerServiceTest, TwoTokenAvailable_OneUserInfo) {
AccountTrackerObserver observer;
account_tracker()->AddObserver(&observer);
SimulateTokenAvailable("alpha");
SimulateTokenAvailable("beta");
ReturnOAuthUrlFetchSuccess("beta");
ASSERT_FALSE(account_fetcher()->IsAllUserInfoFetched());
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(UPDATED, "beta")));
ReturnOAuthUrlFetchSuccess("alpha");
ASSERT_TRUE(account_fetcher()->IsAllUserInfoFetched());
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(UPDATED, "alpha")));
account_tracker()->RemoveObserver(&observer);
}
TEST_F(AccountTrackerServiceTest, GetAccounts) {
SimulateTokenAvailable("alpha");
SimulateTokenAvailable("beta");
SimulateTokenAvailable("gamma");
ReturnOAuthUrlFetchSuccess("alpha");
ReturnOAuthUrlFetchSuccess("beta");
ReturnOAuthUrlFetchSuccess("gamma");
std::vector<AccountInfo> infos = account_tracker()->GetAccounts();
EXPECT_EQ(3u, infos.size());
CheckAccountDetails("alpha", infos[0]);
CheckAccountDetails("beta", infos[1]);
CheckAccountDetails("gamma", infos[2]);
}
TEST_F(AccountTrackerServiceTest, GetAccountInfo_Empty) {
AccountInfo info = account_tracker()->GetAccountInfo("alpha");
ASSERT_EQ("", info.account_id);
}
TEST_F(AccountTrackerServiceTest, GetAccountInfo_TokenAvailable) {
SimulateTokenAvailable("alpha");
AccountInfo info = account_tracker()->GetAccountInfo("alpha");
ASSERT_EQ("alpha", info.account_id);
ASSERT_EQ("", info.gaia);
ASSERT_EQ("", info.email);
}
TEST_F(AccountTrackerServiceTest, GetAccountInfo_TokenAvailable_UserInfo) {
SimulateTokenAvailable("alpha");
ReturnOAuthUrlFetchSuccess("alpha");
AccountInfo info = account_tracker()->GetAccountInfo("alpha");
CheckAccountDetails("alpha", info);
}
TEST_F(AccountTrackerServiceTest, GetAccountInfo_TokenAvailable_EnableNetwork) {
// Shutdown the network-enabled tracker built into the test case.
TearDown();
// Create an account tracker and an account fetcher service but do not enable
// network fetches.
AccountTrackerService tracker;
tracker.Initialize(signin_client());
AccountFetcherService fetcher_service;
fetcher_service.Initialize(signin_client(), token_service(), &tracker);
SimulateTokenAvailable("alpha");
IssueAccessToken("alpha");
// No fetcher has been created yet.
net::TestURLFetcher* fetcher = test_fetcher_factory()->GetFetcherByID(
gaia::GaiaOAuthClient::kUrlFetcherId);
ASSERT_FALSE(fetcher);
// Enable the network to create the fetcher then issue the access token.
fetcher_service.EnableNetworkFetchesForTest();
// Fetcher was created and executes properly.
ReturnOAuthUrlFetchSuccess("alpha");
AccountInfo info = tracker.GetAccountInfo("alpha");
CheckAccountDetails("alpha", info);
fetcher_service.Shutdown();
tracker.Shutdown();
}
TEST_F(AccountTrackerServiceTest, FindAccountInfoByGaiaId) {
SimulateTokenAvailable("alpha");
ReturnOAuthUrlFetchSuccess("alpha");
std::string gaia_id = AccountIdToGaiaId("alpha");
AccountInfo info = account_tracker()->FindAccountInfoByGaiaId(gaia_id);
ASSERT_EQ("alpha", info.account_id);
ASSERT_EQ(gaia_id, info.gaia);
gaia_id = AccountIdToGaiaId("beta");
info = account_tracker()->FindAccountInfoByGaiaId(gaia_id);
ASSERT_EQ("", info.account_id);
}
TEST_F(AccountTrackerServiceTest, FindAccountInfoByEmail) {
SimulateTokenAvailable("alpha");
ReturnOAuthUrlFetchSuccess("alpha");
std::string email = AccountIdToEmail("alpha");
AccountInfo info = account_tracker()->FindAccountInfoByEmail(email);
ASSERT_EQ("alpha", info.account_id);
ASSERT_EQ(email, info.email);
// Should also work with "canonically-equal" email addresses.
info = account_tracker()->FindAccountInfoByEmail("Alpha@Gmail.COM");
ASSERT_EQ("alpha", info.account_id);
ASSERT_EQ(email, info.email);
info = account_tracker()->FindAccountInfoByEmail("al.pha@gmail.com");
ASSERT_EQ("alpha", info.account_id);
ASSERT_EQ(email, info.email);
email = AccountIdToEmail("beta");
info = account_tracker()->FindAccountInfoByEmail(email);
ASSERT_EQ("", info.account_id);
}
TEST_F(AccountTrackerServiceTest, Persistence) {
// Create a tracker and add two accounts. This should cause the accounts
// to be saved to persistence.
{
AccountTrackerService tracker;
tracker.Initialize(signin_client());
SimulateTokenAvailable("alpha");
ReturnOAuthUrlFetchSuccess("alpha");
SimulateTokenAvailable("beta");
ReturnOAuthUrlFetchSuccess("beta");
tracker.Shutdown();
}
// Create a new tracker and make sure it loads the accounts correctly from
// persistence.
{
AccountTrackerService tracker;
AccountTrackerObserver observer;
tracker.AddObserver(&observer);
tracker.Initialize(signin_client());
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(UPDATED, "alpha"),
TrackingEvent(UPDATED, "beta")));
tracker.RemoveObserver(&observer);
std::vector<AccountInfo> infos = tracker.GetAccounts();
ASSERT_EQ(2u, infos.size());
CheckAccountDetails("alpha", infos[0]);
CheckAccountDetails("beta", infos[1]);
FakeAccountFetcherService fetcher;
fetcher.Initialize(signin_client(), token_service(), &tracker);
fetcher.EnableNetworkFetchesForTest();
// Remove an account.
// This will allow testing removal as well as child accounts which is only
// allowed for a single account.
SimulateTokenRevoked("alpha");
fetcher.FakeSetIsChildAccount("beta", true);
fetcher.Shutdown();
tracker.Shutdown();
}
// Create a new tracker and make sure it loads the single account from
// persistence. Also verify it is a child account.
{
AccountTrackerService tracker;
tracker.Initialize(signin_client());
std::vector<AccountInfo> infos = tracker.GetAccounts();
ASSERT_EQ(1u, infos.size());
CheckAccountDetails("beta", infos[0]);
ASSERT_TRUE(infos[0].is_child_account);
tracker.Shutdown();
}
}
TEST_F(AccountTrackerServiceTest, SeedAccountInfo) {
std::vector<AccountInfo> infos = account_tracker()->GetAccounts();
EXPECT_EQ(0u, infos.size());
const std::string gaia_id = AccountIdToGaiaId("alpha");
const std::string email = AccountIdToEmail("alpha");
const std::string account_id =
account_tracker()->PickAccountIdForAccount(gaia_id, email);
account_tracker()->SeedAccountInfo(gaia_id, email);
infos = account_tracker()->GetAccounts();
EXPECT_EQ(1u, infos.size());
EXPECT_EQ(account_id, infos[0].account_id);
EXPECT_EQ(gaia_id, infos[0].gaia);
EXPECT_EQ(email, infos[0].email);
}
TEST_F(AccountTrackerServiceTest, SeedAccountInfoFull) {
AccountTrackerObserver observer;
account_tracker()->AddObserver(&observer);
AccountInfo info;
info.gaia = AccountIdToGaiaId("alpha");
info.email = AccountIdToEmail("alpha");
info.full_name = AccountIdToFullName("alpha");
info.account_id = account_tracker()->SeedAccountInfo(info);
// Validate that seeding an unexisting account works and doesn't send a
// notification if the info isn't full.
AccountInfo stored_info = account_tracker()->GetAccountInfo(info.account_id);
EXPECT_EQ(info.gaia, stored_info.gaia);
EXPECT_EQ(info.email, stored_info.email);
EXPECT_EQ(info.full_name, stored_info.full_name);
EXPECT_TRUE(observer.CheckEvents());
// Validate that seeding new full informations to an existing account works
// and sends a notification.
info.given_name = AccountIdToGivenName("alpha");
info.hosted_domain = AccountTrackerService::kNoHostedDomainFound;
info.locale = AccountIdToLocale("alpha");
info.picture_url = AccountIdToPictureURL("alpha");
account_tracker()->SeedAccountInfo(info);
stored_info = account_tracker()->GetAccountInfo(info.account_id);
EXPECT_EQ(info.gaia, stored_info.gaia);
EXPECT_EQ(info.email, stored_info.email);
EXPECT_EQ(info.given_name, stored_info.given_name);
EXPECT_TRUE(
observer.CheckEvents(TrackingEvent(UPDATED, info.account_id, info.gaia)));
// Validate that seeding invalid information to an existing account doesn't
// work and doesn't send a notification.
info.given_name = AccountIdToGivenName("beta");
account_tracker()->SeedAccountInfo(info);
stored_info = account_tracker()->GetAccountInfo(info.account_id);
EXPECT_EQ(info.gaia, stored_info.gaia);
EXPECT_NE(info.given_name, stored_info.given_name);
EXPECT_TRUE(observer.CheckEvents());
account_tracker()->RemoveObserver(&observer);
}
TEST_F(AccountTrackerServiceTest, UpgradeToFullAccountInfo) {
// Start by simulating an incomplete account info and let it be saved to
// prefs.
{
AccountTrackerService tracker;
tracker.Initialize(signin_client());
AccountFetcherService fetcher;
fetcher.Initialize(signin_client(), token_service(), &tracker);
fetcher.EnableNetworkFetchesForTest();
SimulateTokenAvailable("incomplete");
ReturnOAuthUrlFetchSuccessIncomplete("incomplete");
tracker.Shutdown();
fetcher.Shutdown();
}
{
AccountTrackerService tracker;
tracker.Initialize(signin_client());
AccountFetcherService fetcher;
fetcher.Initialize(signin_client(), token_service(), &tracker);
fetcher.EnableNetworkFetchesForTest();
// Validate that the loaded AccountInfo from prefs is considered invalid.
std::vector<AccountInfo> infos = tracker.GetAccounts();
ASSERT_EQ(1u, infos.size());
ASSERT_FALSE(infos[0].IsValid());
// Simulate the same account getting a refresh token with all the info.
SimulateTokenAvailable("incomplete");
ReturnOAuthUrlFetchSuccess("incomplete");
// Validate that the account is now considered valid.
infos = tracker.GetAccounts();
ASSERT_EQ(1u, infos.size());
ASSERT_TRUE(infos[0].IsValid());
tracker.Shutdown();
fetcher.Shutdown();
}
// Reinstantiate a tracker to validate that the AccountInfo saved to prefs is
// now the upgraded one, considered valid.
{
AccountTrackerService tracker;
AccountTrackerObserver observer;
tracker.AddObserver(&observer);
tracker.Initialize(signin_client());
AccountFetcherService fetcher;
fetcher.Initialize(signin_client(), token_service(), &tracker);
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(UPDATED, "incomplete")));
// Enabling network fetches shouldn't cause any actual fetch since the
// AccountInfos loaded from prefs should be valid.
fetcher.EnableNetworkFetchesForTest();
std::vector<AccountInfo> infos = tracker.GetAccounts();
ASSERT_EQ(1u, infos.size());
ASSERT_TRUE(infos[0].IsValid());
// Check that no network fetches were made.
ASSERT_TRUE(observer.CheckEvents());
tracker.RemoveObserver(&observer);
tracker.Shutdown();
fetcher.Shutdown();
}
}
TEST_F(AccountTrackerServiceTest, TimerRefresh) {
// Start by creating a tracker and adding a couple accounts to be persisted to
// prefs.
{
AccountTrackerService tracker;
tracker.Initialize(signin_client());
AccountFetcherService fetcher;
fetcher.Initialize(signin_client(), token_service(), &tracker);
fetcher.EnableNetworkFetchesForTest();
SimulateTokenAvailable("alpha");
ReturnOAuthUrlFetchSuccess("alpha");
SimulateTokenAvailable("beta");
ReturnOAuthUrlFetchSuccess("beta");
tracker.Shutdown();
fetcher.Shutdown();
}
// Rewind the time by half a day, which shouldn't be enough to trigger a
// network refresh.
base::Time fake_update = base::Time::Now() - base::TimeDelta::FromHours(12);
signin_client()->GetPrefs()->SetInt64(
AccountFetcherService::kLastUpdatePref,
fake_update.ToInternalValue());
// Instantiate a new ATS, making sure the persisted accounts are still there
// and that no network fetches happen.
{
AccountTrackerService tracker;
tracker.Initialize(signin_client());
AccountFetcherService fetcher;
fetcher.Initialize(signin_client(), token_service(), &tracker);
ASSERT_TRUE(fetcher.IsAllUserInfoFetched());
std::vector<AccountInfo> infos = tracker.GetAccounts();
ASSERT_EQ(2u, infos.size());
ASSERT_TRUE(infos[0].IsValid());
ASSERT_TRUE(infos[1].IsValid());
fetcher.EnableNetworkFetchesForTest();
ASSERT_TRUE(fetcher.IsAllUserInfoFetched());
tracker.Shutdown();
fetcher.Shutdown();
}
// Rewind the last updated time enough to trigger a network refresh.
fake_update = base::Time::Now() - base::TimeDelta::FromHours(25);
signin_client()->GetPrefs()->SetInt64(
AccountFetcherService::kLastUpdatePref,
fake_update.ToInternalValue());
// Instantiate a new tracker and validate that even though the AccountInfos
// are still valid, the network fetches are started.
{
AccountTrackerService tracker;
tracker.Initialize(signin_client());
AccountFetcherService fetcher;
fetcher.Initialize(signin_client(), token_service(), &tracker);
ASSERT_TRUE(fetcher.IsAllUserInfoFetched());
std::vector<AccountInfo> infos = tracker.GetAccounts();
ASSERT_EQ(2u, infos.size());
ASSERT_TRUE(infos[0].IsValid());
ASSERT_TRUE(infos[1].IsValid());
fetcher.EnableNetworkFetchesForTest();
ASSERT_FALSE(fetcher.IsAllUserInfoFetched());
tracker.Shutdown();
fetcher.Shutdown();
}
}
TEST_F(AccountTrackerServiceTest, LegacyDottedAccountIds) {
// Start by creating a tracker and adding an account with a dotted account id
// because of an old bug in token service. The token service would also add
// a correct non-dotted account id for the same account.
{
AccountTrackerService tracker;
tracker.Initialize(signin_client());
AccountFetcherService fetcher;
fetcher.Initialize(signin_client(), token_service(), &tracker);
fetcher.EnableNetworkFetchesForTest();
SimulateTokenAvailable("foo.bar@gmail.com");
SimulateTokenAvailable("foobar@gmail.com");
ReturnOAuthUrlFetchSuccess("foo.bar@gmail.com");
ReturnOAuthUrlFetchSuccess("foobar@gmail.com");
tracker.Shutdown();
fetcher.Shutdown();
}
// Remove the bad account now from the token service to simulate that it
// has been "fixed".
SimulateTokenRevoked("foo.bar@gmail.com");
// Instantiate a new tracker and validate that it has only one account, and
// it is the correct non dotted one.
{
AccountTrackerService tracker;
tracker.Initialize(signin_client());
AccountFetcherService fetcher;
fetcher.Initialize(signin_client(), token_service(), &tracker);
ASSERT_TRUE(fetcher.IsAllUserInfoFetched());
std::vector<AccountInfo> infos = tracker.GetAccounts();
ASSERT_EQ(1u, infos.size());
ASSERT_STREQ("foobar@gmail.com", infos[0].account_id.c_str());
tracker.Shutdown();
fetcher.Shutdown();
}
}
TEST_F(AccountTrackerServiceTest, MigrateAccountIdToGaiaId) {
if (account_tracker()->GetMigrationState() !=
AccountTrackerService::MIGRATION_NOT_STARTED) {
AccountTrackerService tracker;
TestingPrefServiceSimple pref;
AccountInfo account_info;
std::string email_alpha = AccountIdToEmail("alpha");
std::string gaia_alpha = AccountIdToGaiaId("alpha");
std::string email_beta = AccountIdToEmail("beta");
std::string gaia_beta = AccountIdToGaiaId("beta");
pref.registry()->RegisterListPref(AccountTrackerService::kAccountInfoPref);
pref.registry()->RegisterIntegerPref(
prefs::kAccountIdMigrationState,
AccountTrackerService::MIGRATION_NOT_STARTED);
ListPrefUpdate update(&pref, AccountTrackerService::kAccountInfoPref);
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->SetString("account_id", base::UTF8ToUTF16(email_alpha));
dict->SetString("email", base::UTF8ToUTF16(email_alpha));
dict->SetString("gaia", base::UTF8ToUTF16(gaia_alpha));
update->Append(std::move(dict));
dict.reset(new base::DictionaryValue());
dict->SetString("account_id", base::UTF8ToUTF16(email_beta));
dict->SetString("email", base::UTF8ToUTF16(email_beta));
dict->SetString("gaia", base::UTF8ToUTF16(gaia_beta));
update->Append(std::move(dict));
std::unique_ptr<TestSigninClient> client;
client.reset(new TestSigninClient(&pref));
tracker.Initialize(client.get());
ASSERT_EQ(tracker.GetMigrationState(),
AccountTrackerService::MIGRATION_IN_PROGRESS);
account_info = tracker.GetAccountInfo(gaia_alpha);
ASSERT_EQ(account_info.account_id, gaia_alpha);
ASSERT_EQ(account_info.gaia, gaia_alpha);
ASSERT_EQ(account_info.email, email_alpha);
account_info = tracker.GetAccountInfo(gaia_beta);
ASSERT_EQ(account_info.account_id, gaia_beta);
ASSERT_EQ(account_info.gaia, gaia_beta);
ASSERT_EQ(account_info.email, email_beta);
std::vector<AccountInfo> accounts = tracker.GetAccounts();
ASSERT_EQ(2u, accounts.size());
}
}
TEST_F(AccountTrackerServiceTest, CanNotMigrateAccountIdToGaiaId) {
if ((account_tracker()->GetMigrationState() !=
AccountTrackerService::MIGRATION_NOT_STARTED)) {
AccountTrackerService tracker;
TestingPrefServiceSimple pref;
AccountInfo account_info;
std::string email_alpha = AccountIdToEmail("alpha");
std::string gaia_alpha = AccountIdToGaiaId("alpha");
std::string email_beta = AccountIdToEmail("beta");
pref.registry()->RegisterListPref(AccountTrackerService::kAccountInfoPref);
pref.registry()->RegisterIntegerPref(
prefs::kAccountIdMigrationState,
AccountTrackerService::MIGRATION_NOT_STARTED);
ListPrefUpdate update(&pref, AccountTrackerService::kAccountInfoPref);
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->SetString("account_id", base::UTF8ToUTF16(email_alpha));
dict->SetString("email", base::UTF8ToUTF16(email_alpha));
dict->SetString("gaia", base::UTF8ToUTF16(gaia_alpha));
update->Append(std::move(dict));
dict.reset(new base::DictionaryValue());
dict->SetString("account_id", base::UTF8ToUTF16(email_beta));
dict->SetString("email", base::UTF8ToUTF16(email_beta));
dict->SetString("gaia", base::UTF8ToUTF16(std::string()));
update->Append(std::move(dict));
std::unique_ptr<TestSigninClient> client;
client.reset(new TestSigninClient(&pref));
tracker.Initialize(client.get());
ASSERT_EQ(tracker.GetMigrationState(),
AccountTrackerService::MIGRATION_NOT_STARTED);
account_info = tracker.GetAccountInfo(email_alpha);
ASSERT_EQ(account_info.account_id, email_alpha);
ASSERT_EQ(account_info.gaia, gaia_alpha);
ASSERT_EQ(account_info.email, email_alpha);
account_info = tracker.GetAccountInfo(email_beta);
ASSERT_EQ(account_info.account_id, email_beta);
ASSERT_EQ(account_info.email, email_beta);
std::vector<AccountInfo> accounts = tracker.GetAccounts();
ASSERT_EQ(2u, accounts.size());
}
}
TEST_F(AccountTrackerServiceTest, GaiaIdMigrationCrashInTheMiddle) {
if (account_tracker()->GetMigrationState() !=
AccountTrackerService::MIGRATION_NOT_STARTED) {
AccountTrackerService tracker;
TestingPrefServiceSimple pref;
AccountInfo account_info;
std::string email_alpha = AccountIdToEmail("alpha");
std::string gaia_alpha = AccountIdToGaiaId("alpha");
std::string email_beta = AccountIdToEmail("beta");
std::string gaia_beta = AccountIdToGaiaId("beta");
pref.registry()->RegisterListPref(AccountTrackerService::kAccountInfoPref);
pref.registry()->RegisterIntegerPref(
prefs::kAccountIdMigrationState,
AccountTrackerService::MIGRATION_IN_PROGRESS);
ListPrefUpdate update(&pref, AccountTrackerService::kAccountInfoPref);
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->SetString("account_id", base::UTF8ToUTF16(email_alpha));
dict->SetString("email", base::UTF8ToUTF16(email_alpha));
dict->SetString("gaia", base::UTF8ToUTF16(gaia_alpha));
update->Append(std::move(dict));
dict.reset(new base::DictionaryValue());
dict->SetString("account_id", base::UTF8ToUTF16(email_beta));
dict->SetString("email", base::UTF8ToUTF16(email_beta));
dict->SetString("gaia", base::UTF8ToUTF16(gaia_beta));
update->Append(std::move(dict));
// Succeed miggrated account.
dict.reset(new base::DictionaryValue());
dict->SetString("account_id", base::UTF8ToUTF16(gaia_alpha));
dict->SetString("email", base::UTF8ToUTF16(email_alpha));
dict->SetString("gaia", base::UTF8ToUTF16(gaia_alpha));
update->Append(std::move(dict));
std::unique_ptr<TestSigninClient> client;
client.reset(new TestSigninClient(&pref));
tracker.Initialize(client.get());
ASSERT_EQ(tracker.GetMigrationState(),
AccountTrackerService::MIGRATION_IN_PROGRESS);
account_info = tracker.GetAccountInfo(gaia_alpha);
ASSERT_EQ(account_info.account_id, gaia_alpha);
ASSERT_EQ(account_info.gaia, gaia_alpha);
ASSERT_EQ(account_info.email, email_alpha);
account_info = tracker.GetAccountInfo(gaia_beta);
ASSERT_EQ(account_info.account_id, gaia_beta);
ASSERT_EQ(account_info.gaia, gaia_beta);
ASSERT_EQ(account_info.email, email_beta);
std::vector<AccountInfo> accounts = tracker.GetAccounts();
ASSERT_EQ(2u, accounts.size());
tracker.SetMigrationDone();
tracker.Shutdown();
tracker.Initialize(client.get());
ASSERT_EQ(tracker.GetMigrationState(),
AccountTrackerService::MIGRATION_DONE);
account_info = tracker.GetAccountInfo(gaia_alpha);
ASSERT_EQ(account_info.account_id, gaia_alpha);
ASSERT_EQ(account_info.gaia, gaia_alpha);
ASSERT_EQ(account_info.email, email_alpha);
account_info = tracker.GetAccountInfo(gaia_beta);
ASSERT_EQ(account_info.account_id, gaia_beta);
ASSERT_EQ(account_info.gaia, gaia_beta);
ASSERT_EQ(account_info.email, email_beta);
accounts.clear();
accounts = tracker.GetAccounts();
ASSERT_EQ(2u, accounts.size());
}
}
TEST_F(AccountTrackerServiceTest, ChildAccountBasic) {
AccountTrackerService tracker;
tracker.Initialize(signin_client());
FakeAccountFetcherService fetcher;
fetcher.Initialize(signin_client(), token_service(), &tracker);
fetcher.EnableNetworkFetchesForTest();
AccountTrackerObserver observer;
tracker.AddObserver(&observer);
std::string child_id("child");
{
// Responses are processed iff there is a single account with a valid token
// and the response is for that account.
fetcher.FakeSetIsChildAccount(child_id, true);
ASSERT_TRUE(observer.CheckEvents());
}
{
SimulateTokenAvailable(child_id);
IssueAccessToken(child_id);
fetcher.FakeSetIsChildAccount(child_id, true);
// Response was processed but observer is not notified as the account state
// is invalid.
ASSERT_TRUE(observer.CheckEvents());
AccountInfo info = tracker.GetAccountInfo(child_id);
ASSERT_TRUE(info.is_child_account);
SimulateTokenRevoked(child_id);
}
tracker.RemoveObserver(&observer);
fetcher.Shutdown();
tracker.Shutdown();
}
TEST_F(AccountTrackerServiceTest, ChildAccountUpdatedAndRevoked) {
AccountTrackerService tracker;
tracker.Initialize(signin_client());
FakeAccountFetcherService fetcher;
fetcher.Initialize(signin_client(), token_service(), &tracker);
fetcher.EnableNetworkFetchesForTest();
AccountTrackerObserver observer;
tracker.AddObserver(&observer);
std::string child_id("child");
SimulateTokenAvailable(child_id);
IssueAccessToken(child_id);
fetcher.FakeSetIsChildAccount(child_id, false);
FakeUserInfoFetchSuccess(&fetcher, child_id);
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(UPDATED, child_id)));
AccountInfo info = tracker.GetAccountInfo(child_id);
ASSERT_FALSE(info.is_child_account);
SimulateTokenRevoked(child_id);
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(REMOVED, child_id)));
tracker.RemoveObserver(&observer);
fetcher.Shutdown();
tracker.Shutdown();
}
TEST_F(AccountTrackerServiceTest, ChildAccountUpdatedAndRevokedWithUpdate) {
AccountTrackerService tracker;
tracker.Initialize(signin_client());
FakeAccountFetcherService fetcher;
fetcher.Initialize(signin_client(), token_service(), &tracker);
fetcher.EnableNetworkFetchesForTest();
AccountTrackerObserver observer;
tracker.AddObserver(&observer);
std::string child_id("child");
SimulateTokenAvailable(child_id);
IssueAccessToken(child_id);
fetcher.FakeSetIsChildAccount(child_id, true);
FakeUserInfoFetchSuccess(&fetcher, child_id);
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(UPDATED, child_id)));
AccountInfo info = tracker.GetAccountInfo(child_id);
ASSERT_TRUE(info.is_child_account);
SimulateTokenRevoked(child_id);
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(UPDATED, child_id),
TrackingEvent(REMOVED, child_id)));
tracker.RemoveObserver(&observer);
fetcher.Shutdown();
tracker.Shutdown();
}
TEST_F(AccountTrackerServiceTest, ChildAccountUpdatedTwiceThenRevoked) {
AccountTrackerService tracker;
tracker.Initialize(signin_client());
FakeAccountFetcherService fetcher;
fetcher.Initialize(signin_client(), token_service(), &tracker);
fetcher.EnableNetworkFetchesForTest();
AccountTrackerObserver observer;
tracker.AddObserver(&observer);
std::string child_id("child");
SimulateTokenAvailable(child_id);
IssueAccessToken(child_id);
// Observers notified the first time.
FakeUserInfoFetchSuccess(&fetcher, child_id);
// Since the account state is already valid, this will notify the
// observers for the second time.
fetcher.FakeSetIsChildAccount(child_id, true);
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(UPDATED, child_id),
TrackingEvent(UPDATED, child_id)));
SimulateTokenRevoked(child_id);
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(UPDATED, child_id),
TrackingEvent(REMOVED, child_id)));
tracker.RemoveObserver(&observer);
fetcher.Shutdown();
tracker.Shutdown();
}
TEST_F(AccountTrackerServiceTest, ChildAccountGraduation) {
AccountTrackerService tracker;
tracker.Initialize(signin_client());
FakeAccountFetcherService fetcher;
fetcher.Initialize(signin_client(), token_service(), &tracker);
fetcher.EnableNetworkFetchesForTest();
AccountTrackerObserver observer;
tracker.AddObserver(&observer);
std::string child_id("child");
SimulateTokenAvailable(child_id);
IssueAccessToken(child_id);
// Set and verify this is a child account.
fetcher.FakeSetIsChildAccount(child_id, true);
AccountInfo info = tracker.GetAccountInfo(child_id);
ASSERT_TRUE(info.is_child_account);
FakeUserInfoFetchSuccess(&fetcher, child_id);
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(UPDATED, child_id)));
// Now simulate child account graduation.
fetcher.FakeSetIsChildAccount(child_id, false);
info = tracker.GetAccountInfo(child_id);
ASSERT_FALSE(info.is_child_account);
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(UPDATED, child_id)));
SimulateTokenRevoked(child_id);
ASSERT_TRUE(observer.CheckEvents(TrackingEvent(REMOVED, child_id)));
tracker.RemoveObserver(&observer);
fetcher.Shutdown();
tracker.Shutdown();
}