blob: 890b366dc5b2abff56693dbc05ae26b910d6cc11 [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/gcm_driver/account_tracker.h"
#include <algorithm>
#include <vector>
#include "base/strings/stringprintf.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const char kPrimaryAccountEmail[] = "primary_account@example.com";
enum TrackingEventType { SIGN_IN, SIGN_OUT };
class TrackingEvent {
public:
TrackingEvent(TrackingEventType type,
const CoreAccountId& account_id,
const std::string& gaia_id)
: type_(type), account_id_(account_id), gaia_id_(gaia_id) {}
TrackingEvent(TrackingEventType type, const CoreAccountInfo& account_info)
: type_(type),
account_id_(account_info.account_id),
gaia_id_(account_info.gaia) {}
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 SIGN_IN:
typestr = " IN";
break;
case SIGN_OUT:
typestr = "OUT";
break;
}
return base::StringPrintf("{ type: %s, account_id: %s, gaia: %s }", typestr,
account_id_.ToString().c_str(), gaia_id_.c_str());
}
private:
friend bool CompareByUser(TrackingEvent a, TrackingEvent b);
TrackingEventType type_;
CoreAccountId 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 (auto it = events.begin(); it != events.end(); ++it) {
if (needs_comma)
str += ",\n ";
needs_comma = true;
str += it->ToString();
}
str += "]";
return str;
}
} // namespace
namespace gcm {
class AccountTrackerObserver : public AccountTracker::Observer {
public:
AccountTrackerObserver() {}
virtual ~AccountTrackerObserver() {}
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);
testing::AssertionResult CheckEvents(const TrackingEvent& e1,
const TrackingEvent& e2,
const TrackingEvent& e3,
const TrackingEvent& e4);
testing::AssertionResult CheckEvents(const TrackingEvent& e1,
const TrackingEvent& e2,
const TrackingEvent& e3,
const TrackingEvent& e4,
const TrackingEvent& e5);
testing::AssertionResult CheckEvents(const TrackingEvent& e1,
const TrackingEvent& e2,
const TrackingEvent& e3,
const TrackingEvent& e4,
const TrackingEvent& e5,
const TrackingEvent& e6);
void Clear();
void SortEventsByUser();
// AccountTracker::Observer implementation
void OnAccountSignInChanged(const CoreAccountInfo& account,
bool is_signed_in) override;
private:
testing::AssertionResult CheckEvents(
const std::vector<TrackingEvent>& events);
std::vector<TrackingEvent> events_;
};
void AccountTrackerObserver::OnAccountSignInChanged(
const CoreAccountInfo& account,
bool is_signed_in) {
events_.push_back(TrackingEvent(is_signed_in ? SIGN_IN : SIGN_OUT,
account.account_id, account.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 TrackingEvent& e1,
const TrackingEvent& e2,
const TrackingEvent& e3,
const TrackingEvent& e4) {
std::vector<TrackingEvent> events;
events.push_back(e1);
events.push_back(e2);
events.push_back(e3);
events.push_back(e4);
return CheckEvents(events);
}
testing::AssertionResult AccountTrackerObserver::CheckEvents(
const TrackingEvent& e1,
const TrackingEvent& e2,
const TrackingEvent& e3,
const TrackingEvent& e4,
const TrackingEvent& e5) {
std::vector<TrackingEvent> events;
events.push_back(e1);
events.push_back(e2);
events.push_back(e3);
events.push_back(e4);
events.push_back(e5);
return CheckEvents(events);
}
testing::AssertionResult AccountTrackerObserver::CheckEvents(
const TrackingEvent& e1,
const TrackingEvent& e2,
const TrackingEvent& e3,
const TrackingEvent& e4,
const TrackingEvent& e5,
const TrackingEvent& e6) {
std::vector<TrackingEvent> events;
events.push_back(e1);
events.push_back(e2);
events.push_back(e3);
events.push_back(e4);
events.push_back(e5);
events.push_back(e6);
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;
}
class AccountTrackerTest : public testing::Test {
public:
AccountTrackerTest() {}
~AccountTrackerTest() override {}
void SetUp() override {
account_tracker_.reset(
new AccountTracker(identity_test_env_.identity_manager()));
account_tracker_->AddObserver(&observer_);
}
void TearDown() override {
account_tracker_->RemoveObserver(&observer_);
account_tracker_->Shutdown();
}
AccountTrackerObserver* observer() { return &observer_; }
AccountTracker* account_tracker() { return account_tracker_.get(); }
// Helpers to pass fake events to the tracker.
// Sets the primary account info. Returns the account ID of the
// newly-set account.
// NOTE: On ChromeOS, the login callback is never fired in production (since
// the underlying GoogleSigninSucceeded callback is never sent). Tests that
// exercise functionality dependent on that callback firing are not relevant
// on ChromeOS and should simply not run on that platform.
CoreAccountInfo SetActiveAccount(const std::string& email) {
return identity_test_env_.SetPrimaryAccount(email);
}
// Helpers that go through a logout flow.
// NOTE: On ChromeOS, the logout callback is never fired in production (since
// the underlying GoogleSignedOut callback is never sent). Tests that exercise
// functionality dependent on that callback firing are not relevant on ChromeOS
// and should simply not run on that platform.
#if !defined(OS_CHROMEOS)
void NotifyLogoutOfPrimaryAccountOnly() {
identity_test_env_.ClearPrimaryAccount(
signin::ClearPrimaryAccountPolicy::KEEP_ALL_ACCOUNTS);
}
void NotifyLogoutOfAllAccounts() {
identity_test_env_.ClearPrimaryAccount(
signin::ClearPrimaryAccountPolicy::REMOVE_ALL_ACCOUNTS);
}
#endif
CoreAccountInfo AddAccountWithToken(const std::string& email) {
return identity_test_env_.MakeAccountAvailable(email);
}
void NotifyTokenAvailable(const CoreAccountId& account_id) {
identity_test_env_.SetRefreshTokenForAccount(account_id);
}
void NotifyTokenRevoked(const CoreAccountId& account_id) {
identity_test_env_.RemoveRefreshTokenForAccount(account_id);
}
CoreAccountInfo SetupPrimaryLogin() {
// Initial setup for tests that start with a signed in profile.
CoreAccountInfo primary_account = SetActiveAccount(kPrimaryAccountEmail);
NotifyTokenAvailable(primary_account.account_id);
observer()->Clear();
return primary_account;
}
private:
// net:: stuff needs IO message loop.
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
signin::IdentityTestEnvironment identity_test_env_;
std::unique_ptr<AccountTracker> account_tracker_;
AccountTrackerObserver observer_;
};
// Primary tests just involve the Active account
TEST_F(AccountTrackerTest, PrimaryNoEventsBeforeLogin) {
CoreAccountInfo account = AddAccountWithToken("me@dummy.com");
NotifyTokenRevoked(account.account_id);
// Logout is not possible on ChromeOS.
#if !defined(OS_CHROMEOS)
NotifyLogoutOfAllAccounts();
#endif
EXPECT_TRUE(observer()->CheckEvents());
}
TEST_F(AccountTrackerTest, PrimaryLoginThenTokenAvailable) {
CoreAccountInfo primary_account = SetActiveAccount(kPrimaryAccountEmail);
NotifyTokenAvailable(primary_account.account_id);
EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_IN, primary_account)));
}
TEST_F(AccountTrackerTest, PrimaryRevoke) {
CoreAccountInfo primary_account = SetActiveAccount(kPrimaryAccountEmail);
NotifyTokenAvailable(primary_account.account_id);
observer()->Clear();
NotifyTokenRevoked(primary_account.account_id);
EXPECT_TRUE(
observer()->CheckEvents(TrackingEvent(SIGN_OUT, primary_account)));
}
TEST_F(AccountTrackerTest, PrimaryRevokeThenTokenAvailable) {
CoreAccountInfo primary_account = SetActiveAccount(kPrimaryAccountEmail);
NotifyTokenAvailable(primary_account.account_id);
NotifyTokenRevoked(primary_account.account_id);
observer()->Clear();
NotifyTokenAvailable(primary_account.account_id);
EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_IN, primary_account)));
}
// These tests exercise true login/logout, which are not possible on ChromeOS.
#if !defined(OS_CHROMEOS)
TEST_F(AccountTrackerTest, PrimaryTokenAvailableThenLogin) {
AddAccountWithToken(kPrimaryAccountEmail);
EXPECT_TRUE(observer()->CheckEvents());
CoreAccountInfo primary_account = SetActiveAccount(kPrimaryAccountEmail);
EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_IN, primary_account)));
}
TEST_F(AccountTrackerTest, PrimaryTokenAvailableAndRevokedThenLogin) {
CoreAccountInfo primary_account = AddAccountWithToken(kPrimaryAccountEmail);
EXPECT_TRUE(observer()->CheckEvents());
NotifyTokenRevoked(primary_account.account_id);
EXPECT_TRUE(observer()->CheckEvents());
SetActiveAccount(kPrimaryAccountEmail);
EXPECT_TRUE(observer()->CheckEvents());
}
TEST_F(AccountTrackerTest, PrimaryRevokeThenLogin) {
CoreAccountInfo primary_account = SetActiveAccount(kPrimaryAccountEmail);
NotifyTokenAvailable(primary_account.account_id);
NotifyLogoutOfAllAccounts();
observer()->Clear();
SetActiveAccount(kPrimaryAccountEmail);
EXPECT_TRUE(observer()->CheckEvents());
}
TEST_F(AccountTrackerTest, PrimaryLogoutThenRevoke) {
CoreAccountInfo primary_account = SetActiveAccount(kPrimaryAccountEmail);
NotifyTokenAvailable(primary_account.account_id);
observer()->Clear();
NotifyLogoutOfAllAccounts();
EXPECT_TRUE(
observer()->CheckEvents(TrackingEvent(SIGN_OUT, primary_account)));
NotifyTokenRevoked(primary_account.account_id);
EXPECT_TRUE(observer()->CheckEvents());
}
#endif
// Non-primary accounts
TEST_F(AccountTrackerTest, Available) {
SetupPrimaryLogin();
CoreAccountInfo account = AddAccountWithToken("user@example.com");
EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_IN, account)));
}
TEST_F(AccountTrackerTest, AvailableRevokeAvailable) {
SetupPrimaryLogin();
CoreAccountInfo account = AddAccountWithToken("user@example.com");
NotifyTokenRevoked(account.account_id);
EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_IN, account),
TrackingEvent(SIGN_OUT, account)));
NotifyTokenAvailable(account.account_id);
EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_IN, account)));
}
TEST_F(AccountTrackerTest, AvailableRevokeRevoke) {
SetupPrimaryLogin();
CoreAccountInfo account = AddAccountWithToken("user@example.com");
NotifyTokenRevoked(account.account_id);
EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_IN, account),
TrackingEvent(SIGN_OUT, account)));
NotifyTokenRevoked(account.account_id);
EXPECT_TRUE(observer()->CheckEvents());
}
TEST_F(AccountTrackerTest, AvailableAvailable) {
SetupPrimaryLogin();
CoreAccountInfo account = AddAccountWithToken("user@example.com");
EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_IN, account)));
NotifyTokenAvailable(account.account_id);
EXPECT_TRUE(observer()->CheckEvents());
}
TEST_F(AccountTrackerTest, TwoAccounts) {
SetupPrimaryLogin();
CoreAccountInfo alpha_account = AddAccountWithToken("alpha@example.com");
EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_IN, alpha_account)));
CoreAccountInfo beta_account = AddAccountWithToken("beta@example.com");
EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_IN, beta_account)));
NotifyTokenRevoked(alpha_account.account_id);
EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_OUT, alpha_account)));
NotifyTokenRevoked(beta_account.account_id);
EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_OUT, beta_account)));
}
// Primary/non-primary interactions
TEST_F(AccountTrackerTest, MultiNoEventsBeforeLogin) {
CoreAccountInfo account1 = AddAccountWithToken("user@example.com");
CoreAccountInfo account2 = AddAccountWithToken("user2@example.com");
NotifyTokenRevoked(account2.account_id);
NotifyTokenRevoked(account2.account_id);
// Logout is not possible on ChromeOS.
#if !defined(OS_CHROMEOS)
NotifyLogoutOfAllAccounts();
#endif
EXPECT_TRUE(observer()->CheckEvents());
}
TEST_F(AccountTrackerTest, MultiRevokePrimaryDoesNotRemoveAllAccounts) {
CoreAccountInfo primary_account = SetActiveAccount(kPrimaryAccountEmail);
NotifyTokenAvailable(primary_account.account_id);
CoreAccountInfo account = AddAccountWithToken("user@example.com");
observer()->Clear();
NotifyTokenRevoked(primary_account.account_id);
observer()->SortEventsByUser();
EXPECT_TRUE(
observer()->CheckEvents(TrackingEvent(SIGN_OUT, primary_account)));
}
TEST_F(AccountTrackerTest, GetAccountsPrimary) {
CoreAccountInfo primary_account = SetupPrimaryLogin();
std::vector<CoreAccountInfo> account = account_tracker()->GetAccounts();
EXPECT_EQ(1ul, account.size());
EXPECT_EQ(primary_account.account_id, account[0].account_id);
EXPECT_EQ(primary_account.gaia, account[0].gaia);
EXPECT_EQ(primary_account.email, account[0].email);
}
TEST_F(AccountTrackerTest, GetAccountsSignedOut) {
std::vector<CoreAccountInfo> accounts = account_tracker()->GetAccounts();
EXPECT_EQ(0ul, accounts.size());
}
TEST_F(AccountTrackerTest, GetMultipleAccounts) {
CoreAccountInfo primary_account = SetupPrimaryLogin();
CoreAccountInfo alpha_account = AddAccountWithToken("alpha@example.com");
CoreAccountInfo beta_account = AddAccountWithToken("beta@example.com");
std::vector<CoreAccountInfo> account = account_tracker()->GetAccounts();
EXPECT_EQ(3ul, account.size());
EXPECT_EQ(primary_account.account_id, account[0].account_id);
EXPECT_EQ(primary_account.email, account[0].email);
EXPECT_EQ(primary_account.gaia, account[0].gaia);
EXPECT_EQ(alpha_account.account_id, account[1].account_id);
EXPECT_EQ(alpha_account.email, account[1].email);
EXPECT_EQ(alpha_account.gaia, account[1].gaia);
EXPECT_EQ(beta_account.account_id, account[2].account_id);
EXPECT_EQ(beta_account.email, account[2].email);
EXPECT_EQ(beta_account.gaia, account[2].gaia);
}
TEST_F(AccountTrackerTest, GetAccountsReturnNothingWhenPrimarySignedOut) {
CoreAccountInfo primary_account = SetupPrimaryLogin();
CoreAccountInfo zeta_account = AddAccountWithToken("zeta@example.com");
CoreAccountInfo alpha_account = AddAccountWithToken("alpha@example.com");
NotifyTokenRevoked(primary_account.account_id);
std::vector<CoreAccountInfo> account = account_tracker()->GetAccounts();
EXPECT_EQ(0ul, account.size());
}
// This test exercises true login/logout, which are not possible on ChromeOS.
#if !defined(OS_CHROMEOS)
TEST_F(AccountTrackerTest, MultiLogoutRemovesAllAccounts) {
CoreAccountInfo primary_account = SetActiveAccount(kPrimaryAccountEmail);
NotifyTokenAvailable(primary_account.account_id);
CoreAccountInfo account = AddAccountWithToken("user@example.com");
observer()->Clear();
NotifyLogoutOfAllAccounts();
observer()->SortEventsByUser();
EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_OUT, primary_account),
TrackingEvent(SIGN_OUT, account)));
}
#endif
} // namespace gcm