| // Copyright 2014 The Chromium Authors |
| // 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 <memory> |
| #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 "google_apis/gaia/gaia_id.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| const char kPrimaryAccountEmail[] = "[email protected]"; |
| |
| enum TrackingEventType { SIGN_IN, SIGN_OUT }; |
| |
| class TrackingEvent { |
| public: |
| TrackingEvent(TrackingEventType type, |
| const CoreAccountId& account_id, |
| const GaiaId& 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_.ToString().c_str()); |
| } |
| |
| private: |
| friend bool CompareByUser(TrackingEvent a, TrackingEvent b); |
| |
| TrackingEventType type_; |
| CoreAccountId account_id_; |
| GaiaId 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() = default; |
| virtual ~AccountTrackerObserver() = default; |
| |
| 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() = default; |
| |
| ~AccountTrackerTest() override = default; |
| |
| void SetUp() override { |
| account_tracker_ = |
| std::make_unique<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) { |
| // TODO(crbug.com/40067875): Delete account-tracking code, latest when |
| // ConsentLevel::kSync is cleaned up from the codebase. |
| return identity_test_env_.SetPrimaryAccount(email, |
| signin::ConsentLevel::kSync); |
| } |
| |
| // 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 !BUILDFLAG(IS_CHROMEOS) |
| void NotifyLogoutOfAllAccounts() { identity_test_env_.ClearPrimaryAccount(); } |
| #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("[email protected]"); |
| NotifyTokenRevoked(account.account_id); |
| |
| // Logout is not possible on ChromeOS. |
| #if !BUILDFLAG(IS_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 !BUILDFLAG(IS_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("[email protected]"); |
| EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_IN, account))); |
| } |
| |
| TEST_F(AccountTrackerTest, AvailableRevokeAvailable) { |
| SetupPrimaryLogin(); |
| |
| CoreAccountInfo account = AddAccountWithToken("[email protected]"); |
| 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("[email protected]"); |
| 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("[email protected]"); |
| 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("[email protected]"); |
| EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_IN, alpha_account))); |
| |
| CoreAccountInfo beta_account = AddAccountWithToken("[email protected]"); |
| 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("[email protected]"); |
| CoreAccountInfo account2 = AddAccountWithToken("[email protected]"); |
| NotifyTokenRevoked(account2.account_id); |
| NotifyTokenRevoked(account2.account_id); |
| |
| // Logout is not possible on ChromeOS. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| NotifyLogoutOfAllAccounts(); |
| #endif |
| |
| EXPECT_TRUE(observer()->CheckEvents()); |
| } |
| |
| TEST_F(AccountTrackerTest, MultiRevokePrimaryDoesNotRemoveAllAccounts) { |
| CoreAccountInfo primary_account = SetActiveAccount(kPrimaryAccountEmail); |
| NotifyTokenAvailable(primary_account.account_id); |
| CoreAccountInfo account = AddAccountWithToken("[email protected]"); |
| 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("[email protected]"); |
| CoreAccountInfo beta_account = AddAccountWithToken("[email protected]"); |
| |
| 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("[email protected]"); |
| CoreAccountInfo alpha_account = AddAccountWithToken("[email protected]"); |
| |
| 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 !BUILDFLAG(IS_CHROMEOS) |
| TEST_F(AccountTrackerTest, MultiLogoutRemovesAllAccounts) { |
| CoreAccountInfo primary_account = SetActiveAccount(kPrimaryAccountEmail); |
| NotifyTokenAvailable(primary_account.account_id); |
| CoreAccountInfo account = AddAccountWithToken("[email protected]"); |
| observer()->Clear(); |
| |
| NotifyLogoutOfAllAccounts(); |
| observer()->SortEventsByUser(); |
| EXPECT_TRUE(observer()->CheckEvents(TrackingEvent(SIGN_OUT, primary_account), |
| TrackingEvent(SIGN_OUT, account))); |
| } |
| #endif |
| |
| } // namespace gcm |