| // 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 <vector> |
| |
| #include "base/message_loop/message_loop.h" |
| #include "base/prefs/pref_registry_simple.h" |
| #include "base/prefs/testing_pref_service.h" |
| #include "base/strings/stringprintf.h" |
| #include "components/signin/core/browser/account_tracker_service.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 { |
| |
| 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; |
| } |
| |
| 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 AccountTrackerService::AccountInfo& ids) override; |
| void OnAccountRemoved(const AccountTrackerService::AccountInfo& ids) override; |
| |
| testing::AssertionResult CheckEvents( |
| const std::vector<TrackingEvent>& events); |
| |
| std::vector<TrackingEvent> events_; |
| }; |
| |
| void AccountTrackerObserver::OnAccountUpdated( |
| const AccountTrackerService::AccountInfo& ids) { |
| events_.push_back(TrackingEvent(UPDATED, ids.account_id, ids.gaia)); |
| } |
| |
| void AccountTrackerObserver::OnAccountRemoved( |
| const AccountTrackerService::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() {} |
| |
| virtual ~AccountTrackerServiceTest() {} |
| |
| virtual 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); |
| |
| account_tracker_.reset(new AccountTrackerService()); |
| account_tracker_->Initialize(fake_oauth2_token_service_.get(), |
| &pref_service_, |
| new net::TestURLRequestContextGetter( |
| message_loop_.message_loop_proxy())); |
| account_tracker_->AddObserver(&observer_); |
| } |
| |
| virtual void TearDown() override { |
| account_tracker_->RemoveObserver(&observer_); |
| 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("{\"id\": \"%s\", \"email\": \"%s\"}", |
| AccountIdToGaiaId(account_id).c_str(), |
| AccountIdToEmail(account_id).c_str()); |
| } |
| |
| void ReturnOAuthUrlFetchSuccess(const std::string& account_id); |
| void ReturnOAuthUrlFetchFailure(const std::string& account_id); |
| |
| base::MessageLoopForIO* message_loop() { return &message_loop_; } |
| AccountTrackerService* account_tracker() { return account_tracker_.get(); } |
| AccountTrackerObserver* observer() { return &observer_; } |
| OAuth2TokenService* token_service() { |
| return fake_oauth2_token_service_.get(); |
| } |
| TestingPrefServiceSimple* pref_service() { return &pref_service_; } |
| |
| private: |
| void ReturnOAuthUrlFetchResults(int fetcher_id, |
| net::HttpStatusCode response_code, |
| const std::string& response_string); |
| |
| base::MessageLoopForIO message_loop_; |
| net::TestURLFetcherFactory test_fetcher_factory_; |
| scoped_ptr<FakeOAuth2TokenService> fake_oauth2_token_service_; |
| TestingPrefServiceSimple pref_service_; |
| scoped_ptr<AccountTrackerService> account_tracker_; |
| AccountTrackerObserver observer_; |
| }; |
| |
| 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::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) { |
| SimulateTokenAvailable("alpha"); |
| ASSERT_FALSE(account_tracker()->IsAllUserInfoFetched()); |
| ASSERT_TRUE(observer()->CheckEvents()); |
| } |
| |
| TEST_F(AccountTrackerServiceTest, TokenAvailable_Revoked) { |
| SimulateTokenAvailable("alpha"); |
| SimulateTokenRevoked("alpha"); |
| ASSERT_TRUE(account_tracker()->IsAllUserInfoFetched()); |
| ASSERT_TRUE(observer()->CheckEvents()); |
| } |
| |
| TEST_F(AccountTrackerServiceTest, TokenAvailable_UserInfo) { |
| SimulateTokenAvailable("alpha"); |
| ReturnOAuthUrlFetchSuccess("alpha"); |
| ASSERT_TRUE(account_tracker()->IsAllUserInfoFetched()); |
| ASSERT_TRUE(observer()->CheckEvents(TrackingEvent(UPDATED, "alpha"))); |
| } |
| |
| TEST_F(AccountTrackerServiceTest, TokenAvailable_UserInfo_Revoked) { |
| SimulateTokenAvailable("alpha"); |
| ReturnOAuthUrlFetchSuccess("alpha"); |
| ASSERT_TRUE(account_tracker()->IsAllUserInfoFetched()); |
| ASSERT_TRUE(observer()->CheckEvents(TrackingEvent(UPDATED, "alpha"))); |
| SimulateTokenRevoked("alpha"); |
| ASSERT_TRUE(observer()->CheckEvents(TrackingEvent(REMOVED, "alpha"))); |
| } |
| |
| TEST_F(AccountTrackerServiceTest, TokenAvailable_UserInfoFailed) { |
| SimulateTokenAvailable("alpha"); |
| ReturnOAuthUrlFetchFailure("alpha"); |
| ASSERT_TRUE(account_tracker()->IsAllUserInfoFetched()); |
| ASSERT_TRUE(observer()->CheckEvents()); |
| } |
| |
| TEST_F(AccountTrackerServiceTest, TokenAlreadyExists) { |
| SimulateTokenAvailable("alpha"); |
| AccountTrackerService tracker; |
| AccountTrackerObserver observer; |
| tracker.AddObserver(&observer); |
| tracker.Initialize(token_service(), |
| pref_service(), |
| new net::TestURLRequestContextGetter( |
| message_loop()->message_loop_proxy())); |
| ASSERT_FALSE(tracker.IsAllUserInfoFetched()); |
| ASSERT_TRUE(observer.CheckEvents()); |
| tracker.RemoveObserver(&observer); |
| tracker.Shutdown(); |
| } |
| |
| TEST_F(AccountTrackerServiceTest, TwoTokenAvailable_TwoUserInfo) { |
| SimulateTokenAvailable("alpha"); |
| SimulateTokenAvailable("beta"); |
| ReturnOAuthUrlFetchSuccess("alpha"); |
| ReturnOAuthUrlFetchSuccess("beta"); |
| ASSERT_TRUE(account_tracker()->IsAllUserInfoFetched()); |
| ASSERT_TRUE(observer()->CheckEvents(TrackingEvent(UPDATED, "alpha"), |
| TrackingEvent(UPDATED, "beta"))); |
| } |
| |
| TEST_F(AccountTrackerServiceTest, TwoTokenAvailable_OneUserInfo) { |
| SimulateTokenAvailable("alpha"); |
| SimulateTokenAvailable("beta"); |
| ReturnOAuthUrlFetchSuccess("beta"); |
| ASSERT_FALSE(account_tracker()->IsAllUserInfoFetched()); |
| ASSERT_TRUE(observer()->CheckEvents(TrackingEvent(UPDATED, "beta"))); |
| ReturnOAuthUrlFetchSuccess("alpha"); |
| ASSERT_TRUE(account_tracker()->IsAllUserInfoFetched()); |
| ASSERT_TRUE(observer()->CheckEvents(TrackingEvent(UPDATED, "alpha"))); |
| } |
| |
| TEST_F(AccountTrackerServiceTest, GetAccounts) { |
| SimulateTokenAvailable("alpha"); |
| SimulateTokenAvailable("beta"); |
| SimulateTokenAvailable("gamma"); |
| ReturnOAuthUrlFetchSuccess("alpha"); |
| ReturnOAuthUrlFetchSuccess("beta"); |
| ReturnOAuthUrlFetchSuccess("gamma"); |
| |
| std::vector<AccountTrackerService::AccountInfo> infos = |
| account_tracker()->GetAccounts(); |
| |
| EXPECT_EQ(3u, infos.size()); |
| EXPECT_EQ("alpha", infos[0].account_id); |
| EXPECT_EQ(AccountIdToGaiaId("alpha"), infos[0].gaia); |
| EXPECT_EQ(AccountIdToEmail("alpha"), infos[0].email); |
| EXPECT_EQ("beta", infos[1].account_id); |
| EXPECT_EQ(AccountIdToGaiaId("beta"), infos[1].gaia); |
| EXPECT_EQ(AccountIdToEmail("beta"), infos[1].email); |
| EXPECT_EQ("gamma", infos[2].account_id); |
| EXPECT_EQ(AccountIdToGaiaId("gamma"), infos[2].gaia); |
| EXPECT_EQ(AccountIdToEmail("gamma"), infos[2].email); |
| } |
| |
| TEST_F(AccountTrackerServiceTest, GetAccountInfo_Empty) { |
| AccountTrackerService::AccountInfo info = |
| account_tracker()->GetAccountInfo("alpha"); |
| ASSERT_EQ("", info.account_id); |
| } |
| |
| TEST_F(AccountTrackerServiceTest, GetAccountInfo_TokenAvailable) { |
| SimulateTokenAvailable("alpha"); |
| AccountTrackerService::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"); |
| AccountTrackerService::AccountInfo info = |
| account_tracker()->GetAccountInfo("alpha"); |
| ASSERT_EQ("alpha", info.account_id); |
| ASSERT_EQ(AccountIdToGaiaId("alpha"), info.gaia); |
| ASSERT_EQ(AccountIdToEmail("alpha"), info.email); |
| } |
| |
| TEST_F(AccountTrackerServiceTest, FindAccountInfoByGaiaId) { |
| SimulateTokenAvailable("alpha"); |
| ReturnOAuthUrlFetchSuccess("alpha"); |
| |
| std::string gaia_id = AccountIdToGaiaId("alpha"); |
| AccountTrackerService::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"); |
| AccountTrackerService::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(token_service(), |
| pref_service(), |
| new net::TestURLRequestContextGetter( |
| message_loop()->message_loop_proxy())); |
| SimulateTokenAvailable("alpha"); |
| ReturnOAuthUrlFetchSuccess("alpha"); |
| SimulateTokenAvailable("beta"); |
| ReturnOAuthUrlFetchSuccess("beta"); |
| tracker.Shutdown(); |
| } |
| |
| // Create a new tracker and make sure it loads the accounts corectly from |
| // persistence. |
| { |
| AccountTrackerService tracker; |
| tracker.AddObserver(observer()); |
| tracker.Initialize(token_service(), |
| pref_service(), |
| new net::TestURLRequestContextGetter( |
| message_loop()->message_loop_proxy())); |
| ASSERT_TRUE(observer()->CheckEvents(TrackingEvent(UPDATED, "alpha"), |
| TrackingEvent(UPDATED, "beta"))); |
| |
| std::vector<AccountTrackerService::AccountInfo> infos = |
| tracker.GetAccounts(); |
| ASSERT_EQ(2u, infos.size()); |
| EXPECT_EQ(AccountIdToGaiaId("alpha"), infos[0].gaia); |
| EXPECT_EQ(AccountIdToEmail("alpha"), infos[0].email); |
| EXPECT_EQ("beta", infos[1].account_id); |
| EXPECT_EQ(AccountIdToGaiaId("beta"), infos[1].gaia); |
| EXPECT_EQ(AccountIdToEmail("beta"), infos[1].email); |
| |
| // Remove account. |
| SimulateTokenRevoked("alpha"); |
| tracker.RemoveObserver(observer()); |
| tracker.Shutdown(); |
| } |
| |
| // Create a new tracker and make sure it loads the single account from |
| // persistence. |
| { |
| AccountTrackerService tracker; |
| tracker.Initialize(token_service(), |
| pref_service(), |
| new net::TestURLRequestContextGetter( |
| message_loop()->message_loop_proxy())); |
| |
| std::vector<AccountTrackerService::AccountInfo> infos = |
| tracker.GetAccounts(); |
| ASSERT_EQ(1u, infos.size()); |
| EXPECT_EQ("beta", infos[0].account_id); |
| EXPECT_EQ(AccountIdToGaiaId("beta"), infos[0].gaia); |
| EXPECT_EQ(AccountIdToEmail("beta"), infos[0].email); |
| tracker.Shutdown(); |
| } |
| } |
| |
| TEST_F(AccountTrackerServiceTest, SeedAccountInfo) { |
| std::vector<AccountTrackerService::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); |
| } |