summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcourage@chromium.org <courage@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-19 20:56:50 +0000
committercourage@chromium.org <courage@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-19 20:56:50 +0000
commit0d02c33dbad9f55c60f0054453f602d7516bf99c (patch)
tree52c6190866392739525c141ca86845db46f159c9
parent07d78a4e24a21674cda43d56df4a32607a24ee08 (diff)
downloadchromium_src-0d02c33dbad9f55c60f0054453f602d7516bf99c.zip
chromium_src-0d02c33dbad9f55c60f0054453f602d7516bf99c.tar.gz
chromium_src-0d02c33dbad9f55c60f0054453f602d7516bf99c.tar.bz2
Add IdentityProvider-based AccountTracker to google_apis
This is a re-implementation of extensions::AccountTracker, built on IdentityProvider instead of SigninManager and ProfileOAuth2TokenService. Removing the dependency on chrome/browser will allow GCM to use this code. BUG=374988 Review URL: https://codereview.chromium.org/336253002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@278476 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--google_apis/gaia/account_tracker.cc293
-rw-r--r--google_apis/gaia/account_tracker.h149
-rw-r--r--google_apis/gaia/account_tracker_unittest.cc816
-rw-r--r--google_apis/gaia/fake_oauth2_token_service.cc42
-rw-r--r--google_apis/gaia/fake_oauth2_token_service.h22
-rw-r--r--google_apis/google_apis.gyp3
6 files changed, 1324 insertions, 1 deletions
diff --git a/google_apis/gaia/account_tracker.cc b/google_apis/gaia/account_tracker.cc
new file mode 100644
index 0000000..65d2836
--- /dev/null
+++ b/google_apis/gaia/account_tracker.cc
@@ -0,0 +1,293 @@
+// 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 "google_apis/gaia/account_tracker.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace gaia {
+
+AccountTracker::AccountTracker(
+ IdentityProvider* identity_provider,
+ net::URLRequestContextGetter* request_context_getter)
+ : identity_provider_(identity_provider),
+ request_context_getter_(request_context_getter),
+ shutdown_called_(false) {
+ identity_provider_->AddObserver(this);
+ identity_provider_->GetTokenService()->AddObserver(this);
+}
+
+AccountTracker::~AccountTracker() {
+ DCHECK(shutdown_called_);
+}
+
+void AccountTracker::Shutdown() {
+ shutdown_called_ = true;
+ STLDeleteValues(&user_info_requests_);
+ identity_provider_->GetTokenService()->RemoveObserver(this);
+ identity_provider_->RemoveObserver(this);
+}
+
+void AccountTracker::AddObserver(Observer* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void AccountTracker::RemoveObserver(Observer* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+std::vector<AccountIds> AccountTracker::GetAccounts() const {
+ const std::string active_account_id =
+ identity_provider_->GetActiveAccountId();
+ std::vector<AccountIds> accounts;
+
+ for (std::map<std::string, AccountState>::const_iterator it =
+ accounts_.begin();
+ it != accounts_.end();
+ ++it) {
+ const AccountState& state = it->second;
+ bool is_visible = state.is_signed_in && !state.ids.gaia.empty();
+
+ if (it->first == active_account_id) {
+ if (is_visible)
+ accounts.insert(accounts.begin(), state.ids);
+ else
+ return std::vector<AccountIds>();
+
+ } else if (is_visible) {
+ accounts.push_back(state.ids);
+ }
+ }
+ return accounts;
+}
+
+AccountIds AccountTracker::FindAccountIdsByGaiaId(const std::string& gaia_id) {
+ for (std::map<std::string, AccountState>::const_iterator it =
+ accounts_.begin();
+ it != accounts_.end();
+ ++it) {
+ const AccountState& state = it->second;
+ if (state.ids.gaia == gaia_id) {
+ return state.ids;
+ }
+ }
+
+ return AccountIds();
+}
+
+void AccountTracker::OnRefreshTokenAvailable(const std::string& account_id) {
+ // Ignore refresh tokens if there is no active account ID at all.
+ if (identity_provider_->GetActiveAccountId().empty())
+ return;
+
+ DVLOG(1) << "AVAILABLE " << account_id;
+ UpdateSignInState(account_id, true);
+}
+
+void AccountTracker::OnRefreshTokenRevoked(const std::string& account_id) {
+ DVLOG(1) << "REVOKED " << account_id;
+ UpdateSignInState(account_id, false);
+}
+
+void AccountTracker::OnActiveAccountLogin() {
+ std::vector<std::string> accounts =
+ identity_provider_->GetTokenService()->GetAccounts();
+
+ DVLOG(1) << "LOGIN " << accounts.size() << " accounts available.";
+
+ for (std::vector<std::string>::const_iterator it = accounts.begin();
+ it != accounts.end();
+ ++it) {
+ OnRefreshTokenAvailable(*it);
+ }
+}
+
+void AccountTracker::OnActiveAccountLogout() {
+ DVLOG(1) << "LOGOUT";
+ StopTrackingAllAccounts();
+}
+
+void AccountTracker::SetAccountStateForTest(AccountIds ids, bool is_signed_in) {
+ accounts_[ids.account_key].ids = ids;
+ accounts_[ids.account_key].is_signed_in = is_signed_in;
+
+ DVLOG(1) << "SetAccountStateForTest " << ids.account_key << ":"
+ << is_signed_in;
+
+ if (VLOG_IS_ON(1)) {
+ for (std::map<std::string, AccountState>::const_iterator it =
+ accounts_.begin();
+ it != accounts_.end();
+ ++it) {
+ DVLOG(1) << it->first << ":" << it->second.is_signed_in;
+ }
+ }
+}
+
+void AccountTracker::NotifyAccountAdded(const AccountState& account) {
+ DCHECK(!account.ids.gaia.empty());
+ FOR_EACH_OBSERVER(
+ Observer, observer_list_, OnAccountAdded(account.ids));
+}
+
+void AccountTracker::NotifyAccountRemoved(const AccountState& account) {
+ DCHECK(!account.ids.gaia.empty());
+ FOR_EACH_OBSERVER(
+ Observer, observer_list_, OnAccountRemoved(account.ids));
+}
+
+void AccountTracker::NotifySignInChanged(const AccountState& account) {
+ DCHECK(!account.ids.gaia.empty());
+ FOR_EACH_OBSERVER(Observer,
+ observer_list_,
+ OnAccountSignInChanged(account.ids, account.is_signed_in));
+}
+
+void AccountTracker::UpdateSignInState(const std::string account_key,
+ bool is_signed_in) {
+ StartTrackingAccount(account_key);
+ AccountState& account = accounts_[account_key];
+ bool needs_gaia_id = account.ids.gaia.empty();
+ bool was_signed_in = account.is_signed_in;
+ account.is_signed_in = is_signed_in;
+
+ if (needs_gaia_id && is_signed_in)
+ StartFetchingUserInfo(account_key);
+
+ if (!needs_gaia_id && (was_signed_in != is_signed_in))
+ NotifySignInChanged(account);
+}
+
+void AccountTracker::StartTrackingAccount(const std::string account_key) {
+ if (!ContainsKey(accounts_, account_key)) {
+ DVLOG(1) << "StartTracking " << account_key;
+ AccountState account_state;
+ account_state.ids.account_key = account_key;
+ account_state.ids.email = account_key;
+ account_state.is_signed_in = false;
+ accounts_.insert(make_pair(account_key, account_state));
+ }
+}
+
+void AccountTracker::StopTrackingAccount(const std::string account_key) {
+ DVLOG(1) << "StopTracking " << account_key;
+ if (ContainsKey(accounts_, account_key)) {
+ AccountState& account = accounts_[account_key];
+ if (!account.ids.gaia.empty()) {
+ UpdateSignInState(account_key, false);
+ NotifyAccountRemoved(account);
+ }
+ accounts_.erase(account_key);
+ }
+
+ if (ContainsKey(user_info_requests_, account_key))
+ DeleteFetcher(user_info_requests_[account_key]);
+}
+
+void AccountTracker::StopTrackingAllAccounts() {
+ while (!accounts_.empty())
+ StopTrackingAccount(accounts_.begin()->first);
+}
+
+void AccountTracker::StartFetchingUserInfo(const std::string account_key) {
+ if (ContainsKey(user_info_requests_, account_key))
+ DeleteFetcher(user_info_requests_[account_key]);
+
+ DVLOG(1) << "StartFetching " << account_key;
+ AccountIdFetcher* fetcher =
+ new AccountIdFetcher(identity_provider_->GetTokenService(),
+ request_context_getter_.get(),
+ this,
+ account_key);
+ user_info_requests_[account_key] = fetcher;
+ fetcher->Start();
+}
+
+void AccountTracker::OnUserInfoFetchSuccess(AccountIdFetcher* fetcher,
+ const std::string& gaia_id) {
+ const std::string& account_key = fetcher->account_key();
+ DCHECK(ContainsKey(accounts_, account_key));
+ AccountState& account = accounts_[account_key];
+
+ account.ids.gaia = gaia_id;
+ NotifyAccountAdded(account);
+
+ if (account.is_signed_in)
+ NotifySignInChanged(account);
+
+ DeleteFetcher(fetcher);
+}
+
+void AccountTracker::OnUserInfoFetchFailure(AccountIdFetcher* fetcher) {
+ LOG(WARNING) << "Failed to get UserInfo for " << fetcher->account_key();
+ std::string key = fetcher->account_key();
+ DeleteFetcher(fetcher);
+ StopTrackingAccount(key);
+}
+
+void AccountTracker::DeleteFetcher(AccountIdFetcher* fetcher) {
+ DVLOG(1) << "DeleteFetcher " << fetcher->account_key();
+ const std::string& account_key = fetcher->account_key();
+ DCHECK(ContainsKey(user_info_requests_, account_key));
+ DCHECK_EQ(fetcher, user_info_requests_[account_key]);
+ user_info_requests_.erase(account_key);
+ delete fetcher;
+}
+
+AccountIdFetcher::AccountIdFetcher(
+ OAuth2TokenService* token_service,
+ net::URLRequestContextGetter* request_context_getter,
+ AccountTracker* tracker,
+ const std::string& account_key)
+ : OAuth2TokenService::Consumer("gaia_account_tracker"),
+ token_service_(token_service),
+ request_context_getter_(request_context_getter),
+ tracker_(tracker),
+ account_key_(account_key) {
+}
+
+AccountIdFetcher::~AccountIdFetcher() {}
+
+void AccountIdFetcher::Start() {
+ login_token_request_ = token_service_->StartRequest(
+ account_key_, OAuth2TokenService::ScopeSet(), this);
+}
+
+void AccountIdFetcher::OnGetTokenSuccess(
+ const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) {
+ DCHECK_EQ(request, login_token_request_.get());
+
+ gaia_oauth_client_.reset(new gaia::GaiaOAuthClient(request_context_getter_));
+
+ const int kMaxGetUserIdRetries = 3;
+ gaia_oauth_client_->GetUserId(access_token, kMaxGetUserIdRetries, this);
+}
+
+void AccountIdFetcher::OnGetTokenFailure(
+ const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) {
+ LOG(ERROR) << "OnGetTokenFailure: " << error.ToString();
+ DCHECK_EQ(request, login_token_request_.get());
+ tracker_->OnUserInfoFetchFailure(this);
+}
+
+void AccountIdFetcher::OnGetUserIdResponse(const std::string& gaia_id) {
+ tracker_->OnUserInfoFetchSuccess(this, gaia_id);
+}
+
+void AccountIdFetcher::OnOAuthError() {
+ LOG(ERROR) << "OnOAuthError";
+ tracker_->OnUserInfoFetchFailure(this);
+}
+
+void AccountIdFetcher::OnNetworkError(int response_code) {
+ LOG(ERROR) << "OnNetworkError " << response_code;
+ tracker_->OnUserInfoFetchFailure(this);
+}
+
+} // namespace gaia
diff --git a/google_apis/gaia/account_tracker.h b/google_apis/gaia/account_tracker.h
new file mode 100644
index 0000000..5a29ea6
--- /dev/null
+++ b/google_apis/gaia/account_tracker.h
@@ -0,0 +1,149 @@
+// 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.
+
+#ifndef GOOGLE_APIS_GAIA_ACCOUNT_TRACKER_H_
+#define GOOGLE_APIS_GAIA_ACCOUNT_TRACKER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "google_apis/gaia/gaia_oauth_client.h"
+#include "google_apis/gaia/identity_provider.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+
+class GoogleServiceAuthError;
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace gaia {
+
+struct AccountIds {
+ std::string account_key; // The account ID used by OAuth2TokenService.
+ std::string gaia;
+ std::string email;
+};
+
+class AccountIdFetcher;
+
+// The AccountTracker keeps track of what accounts exist on the
+// profile and the state of their credentials. The tracker fetches the
+// gaia ID of each account it knows about.
+//
+// The AccountTracker maintains these invariants:
+// 1. Events are only fired after the gaia ID has been fetched.
+// 2. Add/Remove and SignIn/SignOut pairs are always generated in order.
+// 3. SignIn follows Add, and there will be a SignOut between SignIn & Remove.
+// 4. If there is no primary account, there are no other accounts.
+class AccountTracker : public OAuth2TokenService::Observer,
+ public IdentityProvider::Observer {
+ public:
+ AccountTracker(IdentityProvider* identity_provider,
+ net::URLRequestContextGetter* request_context_getter);
+ virtual ~AccountTracker();
+
+ class Observer {
+ public:
+ virtual void OnAccountAdded(const AccountIds& ids) = 0;
+ virtual void OnAccountRemoved(const AccountIds& ids) = 0;
+ virtual void OnAccountSignInChanged(const AccountIds& ids,
+ bool is_signed_in) = 0;
+ };
+
+ void Shutdown();
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Returns the list of accounts that are signed in, and for which gaia IDs
+ // have been fetched. The primary account for the profile will be first
+ // in the vector. Additional accounts will be in order of their gaia IDs.
+ std::vector<AccountIds> GetAccounts() const;
+ AccountIds FindAccountIdsByGaiaId(const std::string& gaia_id);
+
+ // OAuth2TokenService::Observer implementation.
+ virtual void OnRefreshTokenAvailable(const std::string& account_key) OVERRIDE;
+ virtual void OnRefreshTokenRevoked(const std::string& account_key) OVERRIDE;
+
+ void OnUserInfoFetchSuccess(AccountIdFetcher* fetcher,
+ const std::string& gaia_id);
+ void OnUserInfoFetchFailure(AccountIdFetcher* fetcher);
+
+ // IdentityProvider::Observer implementation.
+ virtual void OnActiveAccountLogin() OVERRIDE;
+ virtual void OnActiveAccountLogout() OVERRIDE;
+
+ // Sets the state of an account. Does not fire notifications.
+ void SetAccountStateForTest(AccountIds ids, bool is_signed_in);
+
+ IdentityProvider* identity_provider() { return identity_provider_; }
+
+ private:
+ struct AccountState {
+ AccountIds ids;
+ bool is_signed_in;
+ };
+
+ void NotifyAccountAdded(const AccountState& account);
+ void NotifyAccountRemoved(const AccountState& account);
+ void NotifySignInChanged(const AccountState& account);
+
+ void UpdateSignInState(const std::string account_key, bool is_signed_in);
+
+ void StartTrackingAccount(const std::string account_key);
+ void StopTrackingAccount(const std::string account_key);
+ void StopTrackingAllAccounts();
+ void StartFetchingUserInfo(const std::string account_key);
+ void DeleteFetcher(AccountIdFetcher* fetcher);
+
+ IdentityProvider* identity_provider_; // Not owned.
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+ std::map<std::string, AccountIdFetcher*> user_info_requests_;
+ std::map<std::string, AccountState> accounts_;
+ ObserverList<Observer> observer_list_;
+ bool shutdown_called_;
+};
+
+class AccountIdFetcher : public OAuth2TokenService::Consumer,
+ public gaia::GaiaOAuthClient::Delegate {
+ public:
+ AccountIdFetcher(OAuth2TokenService* token_service,
+ net::URLRequestContextGetter* request_context_getter,
+ AccountTracker* tracker,
+ const std::string& account_key);
+ virtual ~AccountIdFetcher();
+
+ const std::string& account_key() { return account_key_; }
+
+ void Start();
+
+ // OAuth2TokenService::Consumer implementation.
+ virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) OVERRIDE;
+ virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) OVERRIDE;
+
+ // gaia::GaiaOAuthClient::Delegate implementation.
+ virtual void OnGetUserIdResponse(const std::string& gaia_id) OVERRIDE;
+ virtual void OnOAuthError() OVERRIDE;
+ virtual void OnNetworkError(int response_code) OVERRIDE;
+
+ private:
+ OAuth2TokenService* token_service_;
+ net::URLRequestContextGetter* request_context_getter_;
+ AccountTracker* tracker_;
+ const std::string account_key_;
+
+ scoped_ptr<OAuth2TokenService::Request> login_token_request_;
+ scoped_ptr<gaia::GaiaOAuthClient> gaia_oauth_client_;
+};
+
+} // namespace extensions
+
+#endif // GOOGLE_APIS_GAIA_ACCOUNT_TRACKER_H_
diff --git a/google_apis/gaia/account_tracker_unittest.cc b/google_apis/gaia/account_tracker_unittest.cc
new file mode 100644
index 0000000..fe8346e
--- /dev/null
+++ b/google_apis/gaia/account_tracker_unittest.cc
@@ -0,0 +1,816 @@
+// 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 "google_apis/gaia/account_tracker.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "google_apis/gaia/fake_identity_provider.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 char kPrimaryAccountKey[] = "primary_account@example.com";
+
+enum TrackingEventType {
+ ADDED,
+ REMOVED,
+ SIGN_IN,
+ SIGN_OUT
+};
+
+std::string AccountKeyToObfuscatedId(const std::string email) {
+ return "obfid-" + email;
+}
+
+class TrackingEvent {
+ public:
+ TrackingEvent(TrackingEventType type,
+ const std::string& account_key,
+ const std::string& gaia_id)
+ : type_(type),
+ account_key_(account_key),
+ gaia_id_(gaia_id) {}
+
+ TrackingEvent(TrackingEventType type,
+ const std::string& account_key)
+ : type_(type),
+ account_key_(account_key),
+ gaia_id_(AccountKeyToObfuscatedId(account_key)) {}
+
+ bool operator==(const TrackingEvent& event) const {
+ return type_ == event.type_ && account_key_ == event.account_key_ &&
+ gaia_id_ == event.gaia_id_;
+ }
+
+ std::string ToString() const {
+ const char * typestr = "INVALID";
+ switch (type_) {
+ case ADDED:
+ typestr = "ADD";
+ break;
+ case REMOVED:
+ typestr = "REM";
+ break;
+ case SIGN_IN:
+ typestr = " IN";
+ break;
+ case SIGN_OUT:
+ typestr = "OUT";
+ break;
+ }
+ return base::StringPrintf("{ type: %s, email: %s, gaia: %s }",
+ typestr,
+ account_key_.c_str(),
+ gaia_id_.c_str());
+ }
+
+ private:
+ friend bool CompareByUser(TrackingEvent a, TrackingEvent b);
+
+ TrackingEventType type_;
+ std::string account_key_;
+ std::string gaia_id_;
+};
+
+bool CompareByUser(TrackingEvent a, TrackingEvent b) {
+ return a.account_key_ < b.account_key_;
+}
+
+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;
+}
+
+} // namespace
+
+namespace gaia {
+
+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
+ virtual void OnAccountAdded(const AccountIds& ids) OVERRIDE;
+ virtual void OnAccountRemoved(const AccountIds& ids) OVERRIDE;
+ virtual void OnAccountSignInChanged(const AccountIds& ids, bool is_signed_in)
+ OVERRIDE;
+
+ private:
+ testing::AssertionResult CheckEvents(
+ const std::vector<TrackingEvent>& events);
+
+ std::vector<TrackingEvent> events_;
+};
+
+void AccountTrackerObserver::OnAccountAdded(const AccountIds& ids) {
+ events_.push_back(TrackingEvent(ADDED, ids.email, ids.gaia));
+}
+
+void AccountTrackerObserver::OnAccountRemoved(const AccountIds& ids) {
+ events_.push_back(TrackingEvent(REMOVED, ids.email, ids.gaia));
+}
+
+void AccountTrackerObserver::OnAccountSignInChanged(const AccountIds& ids,
+ bool is_signed_in) {
+ events_.push_back(
+ TrackingEvent(is_signed_in ? SIGN_IN : SIGN_OUT, ids.email, 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 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 IdentityAccountTrackerTest : public testing::Test {
+ public:
+ IdentityAccountTrackerTest() {}
+
+ virtual ~IdentityAccountTrackerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+
+ fake_oauth2_token_service_.reset(new FakeOAuth2TokenService());
+
+ fake_identity_provider_.reset(
+ new FakeIdentityProvider(fake_oauth2_token_service_.get()));
+
+ account_tracker_.reset(
+ new AccountTracker(fake_identity_provider_.get(),
+ new net::TestURLRequestContextGetter(
+ message_loop_.message_loop_proxy())));
+ account_tracker_->AddObserver(&observer_);
+ }
+
+ virtual 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.
+
+ void NotifyLogin(const std::string account_key) {
+ identity_provider()->LogIn(account_key);
+ }
+
+ void NotifyLogout() { identity_provider()->LogOut(); }
+
+ void NotifyTokenAvailable(const std::string& username) {
+ fake_oauth2_token_service_->AddAccount(username);
+ }
+
+ void NotifyTokenRevoked(const std::string& username) {
+ fake_oauth2_token_service_->RemoveAccount(username);
+ }
+
+ // Helpers to fake access token and user info fetching
+ void IssueAccessToken(const std::string& username) {
+ fake_oauth2_token_service_->IssueAllTokensForAccount(
+ username, "access_token-" + username, base::Time::Max());
+ }
+
+ std::string GetValidTokenInfoResponse(const std::string account_key) {
+ return std::string("{ \"id\": \"") + AccountKeyToObfuscatedId(account_key) +
+ "\" }";
+ }
+
+ void ReturnOAuthUrlFetchResults(int fetcher_id,
+ net::HttpStatusCode response_code,
+ const std::string& response_string);
+
+ void ReturnOAuthUrlFetchSuccess(const std::string& account_key);
+ void ReturnOAuthUrlFetchFailure(const std::string& account_key);
+
+ void SetupPrimaryLogin() {
+ // Initial setup for tests that start with a signed in profile.
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ observer()->Clear();
+ }
+
+ std::string active_account_id() {
+ return identity_provider()->GetActiveAccountId();
+ }
+
+ private:
+ FakeIdentityProvider* identity_provider() {
+ return static_cast<FakeIdentityProvider*>(
+ account_tracker_->identity_provider());
+ }
+
+ base::MessageLoopForIO message_loop_; // net:: stuff needs IO message loop.
+ net::TestURLFetcherFactory test_fetcher_factory_;
+ scoped_ptr<FakeOAuth2TokenService> fake_oauth2_token_service_;
+ scoped_ptr<FakeIdentityProvider> fake_identity_provider_;
+
+ scoped_ptr<AccountTracker> account_tracker_;
+ AccountTrackerObserver observer_;
+};
+
+void IdentityAccountTrackerTest::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 IdentityAccountTrackerTest::ReturnOAuthUrlFetchSuccess(
+ const std::string& account_key) {
+ IssueAccessToken(account_key);
+ ReturnOAuthUrlFetchResults(gaia::GaiaOAuthClient::kUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenInfoResponse(account_key));
+}
+
+void IdentityAccountTrackerTest::ReturnOAuthUrlFetchFailure(
+ const std::string& account_key) {
+ IssueAccessToken(account_key);
+ ReturnOAuthUrlFetchResults(
+ gaia::GaiaOAuthClient::kUrlFetcherId, net::HTTP_BAD_REQUEST, "");
+}
+
+// Primary tests just involve the Active account
+
+TEST_F(IdentityAccountTrackerTest, PrimaryNoEventsBeforeLogin) {
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ NotifyLogout();
+ EXPECT_TRUE(observer()->CheckEvents());
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryLoginThenTokenAvailable) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(ADDED, kPrimaryAccountKey),
+ TrackingEvent(SIGN_IN, kPrimaryAccountKey)));
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryTokenAvailableThenLogin) {
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ NotifyLogin(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(ADDED, kPrimaryAccountKey),
+ TrackingEvent(SIGN_IN, kPrimaryAccountKey)));
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryTokenAvailableAndRevokedThenLogin) {
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ NotifyLogin(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(ADDED, kPrimaryAccountKey),
+ TrackingEvent(SIGN_IN, kPrimaryAccountKey)));
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryRevokeThenLogout) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ observer()->Clear();
+
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(SIGN_OUT, kPrimaryAccountKey)));
+
+ NotifyLogout();
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(REMOVED, kPrimaryAccountKey)));
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryRevokeThenLogin) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ observer()->Clear();
+
+ NotifyLogin(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents());
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryRevokeThenTokenAvailable) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ observer()->Clear();
+
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(SIGN_IN, kPrimaryAccountKey)));
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryLogoutThenRevoke) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ observer()->Clear();
+
+ NotifyLogout();
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(SIGN_OUT, kPrimaryAccountKey),
+ TrackingEvent(REMOVED, kPrimaryAccountKey)));
+
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents());
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryLogoutFetchCancelAvailable) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ // TokenAvailable kicks off a fetch. Logout without satisfying it.
+ NotifyLogout();
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, kPrimaryAccountKey),
+ TrackingEvent(SIGN_IN, kPrimaryAccountKey)));
+}
+
+// Non-primary accounts
+
+TEST_F(IdentityAccountTrackerTest, Available) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "user@example.com"),
+ TrackingEvent(SIGN_IN, "user@example.com")));
+}
+
+TEST_F(IdentityAccountTrackerTest, Revoke) {
+ SetupPrimaryLogin();
+
+ account_tracker()->OnRefreshTokenRevoked("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents());
+}
+
+TEST_F(IdentityAccountTrackerTest, AvailableRevokeAvailable) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ NotifyTokenRevoked("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "user@example.com"),
+ TrackingEvent(SIGN_IN, "user@example.com"),
+ TrackingEvent(SIGN_OUT, "user@example.com")));
+
+ NotifyTokenAvailable("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(SIGN_IN, "user@example.com")));
+}
+
+TEST_F(IdentityAccountTrackerTest, AvailableRevokeAvailableWithPendingFetch) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("user@example.com");
+ NotifyTokenRevoked("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "user@example.com"),
+ TrackingEvent(SIGN_IN, "user@example.com")));
+}
+
+TEST_F(IdentityAccountTrackerTest, AvailableRevokeRevoke) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ NotifyTokenRevoked("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "user@example.com"),
+ TrackingEvent(SIGN_IN, "user@example.com"),
+ TrackingEvent(SIGN_OUT, "user@example.com")));
+
+ NotifyTokenRevoked("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents());
+}
+
+TEST_F(IdentityAccountTrackerTest, AvailableAvailable) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "user@example.com"),
+ TrackingEvent(SIGN_IN, "user@example.com")));
+
+ NotifyTokenAvailable("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents());
+}
+
+TEST_F(IdentityAccountTrackerTest, TwoAccounts) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("alpha@example.com");
+ ReturnOAuthUrlFetchSuccess("alpha@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "alpha@example.com"),
+ TrackingEvent(SIGN_IN, "alpha@example.com")));
+
+ NotifyTokenAvailable("beta@example.com");
+ ReturnOAuthUrlFetchSuccess("beta@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "beta@example.com"),
+ TrackingEvent(SIGN_IN, "beta@example.com")));
+
+ NotifyTokenRevoked("alpha@example.com");
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(SIGN_OUT, "alpha@example.com")));
+
+ NotifyTokenRevoked("beta@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(SIGN_OUT, "beta@example.com")));
+}
+
+TEST_F(IdentityAccountTrackerTest, AvailableTokenFetchFailAvailable) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchFailure("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "user@example.com"),
+ TrackingEvent(SIGN_IN, "user@example.com")));
+}
+
+TEST_F(IdentityAccountTrackerTest, MultiSignOutSignIn) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("alpha@example.com");
+ ReturnOAuthUrlFetchSuccess("alpha@example.com");
+ NotifyTokenAvailable("beta@example.com");
+ ReturnOAuthUrlFetchSuccess("beta@example.com");
+
+ observer()->SortEventsByUser();
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "alpha@example.com"),
+ TrackingEvent(SIGN_IN, "alpha@example.com"),
+ TrackingEvent(ADDED, "beta@example.com"),
+ TrackingEvent(SIGN_IN, "beta@example.com")));
+
+ NotifyLogout();
+ observer()->SortEventsByUser();
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(SIGN_OUT, "alpha@example.com"),
+ TrackingEvent(REMOVED, "alpha@example.com"),
+ TrackingEvent(SIGN_OUT, "beta@example.com"),
+ TrackingEvent(REMOVED, "beta@example.com"),
+ TrackingEvent(SIGN_OUT, kPrimaryAccountKey),
+ TrackingEvent(REMOVED, kPrimaryAccountKey)));
+
+ // No events fire at all while profile is signed out.
+ NotifyTokenRevoked("alpha@example.com");
+ NotifyTokenAvailable("gamma@example.com");
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ // Signing the profile in again will resume tracking all accounts.
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess("beta@example.com");
+ ReturnOAuthUrlFetchSuccess("gamma@example.com");
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ observer()->SortEventsByUser();
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "beta@example.com"),
+ TrackingEvent(SIGN_IN, "beta@example.com"),
+ TrackingEvent(ADDED, "gamma@example.com"),
+ TrackingEvent(SIGN_IN, "gamma@example.com"),
+ TrackingEvent(ADDED, kPrimaryAccountKey),
+ TrackingEvent(SIGN_IN, kPrimaryAccountKey)));
+
+ // Revoking the primary token does not affect other accounts.
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(SIGN_OUT, kPrimaryAccountKey)));
+
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(SIGN_IN, kPrimaryAccountKey)));
+}
+
+// Primary/non-primary interactions
+
+TEST_F(IdentityAccountTrackerTest, MultiNoEventsBeforeLogin) {
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ NotifyTokenAvailable("user@example.com");
+ NotifyTokenRevoked("user@example.com");
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ NotifyLogout();
+ EXPECT_TRUE(observer()->CheckEvents());
+}
+
+TEST_F(IdentityAccountTrackerTest, MultiLogoutRemovesAllAccounts) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ observer()->Clear();
+
+ NotifyLogout();
+ observer()->SortEventsByUser();
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(SIGN_OUT, kPrimaryAccountKey),
+ TrackingEvent(REMOVED, kPrimaryAccountKey),
+ TrackingEvent(SIGN_OUT, "user@example.com"),
+ TrackingEvent(REMOVED, "user@example.com")));
+}
+
+TEST_F(IdentityAccountTrackerTest, MultiRevokePrimaryDoesNotRemoveAllAccounts) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ observer()->Clear();
+
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ observer()->SortEventsByUser();
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(SIGN_OUT, kPrimaryAccountKey)));
+}
+
+TEST_F(IdentityAccountTrackerTest, GetAccountsPrimary) {
+ SetupPrimaryLogin();
+
+ std::vector<AccountIds> ids = account_tracker()->GetAccounts();
+ EXPECT_EQ(1ul, ids.size());
+ EXPECT_EQ(kPrimaryAccountKey, ids[0].account_key);
+ EXPECT_EQ(AccountKeyToObfuscatedId(kPrimaryAccountKey), ids[0].gaia);
+}
+
+TEST_F(IdentityAccountTrackerTest, GetAccountsSignedOut) {
+ std::vector<AccountIds> ids = account_tracker()->GetAccounts();
+ EXPECT_EQ(0ul, ids.size());
+}
+
+TEST_F(IdentityAccountTrackerTest, GetAccountsOnlyReturnAccountsWithTokens) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("alpha@example.com");
+ NotifyTokenAvailable("beta@example.com");
+ ReturnOAuthUrlFetchSuccess("beta@example.com");
+
+ std::vector<AccountIds> ids = account_tracker()->GetAccounts();
+ EXPECT_EQ(2ul, ids.size());
+ EXPECT_EQ(kPrimaryAccountKey, ids[0].account_key);
+ EXPECT_EQ(AccountKeyToObfuscatedId(kPrimaryAccountKey), ids[0].gaia);
+ EXPECT_EQ("beta@example.com", ids[1].account_key);
+ EXPECT_EQ(AccountKeyToObfuscatedId("beta@example.com"), ids[1].gaia);
+}
+
+TEST_F(IdentityAccountTrackerTest, GetAccountsSortOrder) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("zeta@example.com");
+ ReturnOAuthUrlFetchSuccess("zeta@example.com");
+ NotifyTokenAvailable("alpha@example.com");
+ ReturnOAuthUrlFetchSuccess("alpha@example.com");
+
+ // The primary account will be first in the vector. Remaining accounts
+ // will be sorted by gaia ID.
+ std::vector<AccountIds> ids = account_tracker()->GetAccounts();
+ EXPECT_EQ(3ul, ids.size());
+ EXPECT_EQ(kPrimaryAccountKey, ids[0].account_key);
+ EXPECT_EQ(AccountKeyToObfuscatedId(kPrimaryAccountKey), ids[0].gaia);
+ EXPECT_EQ("alpha@example.com", ids[1].account_key);
+ EXPECT_EQ(AccountKeyToObfuscatedId("alpha@example.com"), ids[1].gaia);
+ EXPECT_EQ("zeta@example.com", ids[2].account_key);
+ EXPECT_EQ(AccountKeyToObfuscatedId("zeta@example.com"), ids[2].gaia);
+}
+
+TEST_F(IdentityAccountTrackerTest,
+ GetAccountsReturnNothingWhenPrimarySignedOut) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("zeta@example.com");
+ ReturnOAuthUrlFetchSuccess("zeta@example.com");
+ NotifyTokenAvailable("alpha@example.com");
+ ReturnOAuthUrlFetchSuccess("alpha@example.com");
+
+ NotifyTokenRevoked(kPrimaryAccountKey);
+
+ std::vector<AccountIds> ids = account_tracker()->GetAccounts();
+ EXPECT_EQ(0ul, ids.size());
+}
+
+TEST_F(IdentityAccountTrackerTest, FindAccountIdsByGaiaIdPrimary) {
+ SetupPrimaryLogin();
+
+ AccountIds ids = account_tracker()->FindAccountIdsByGaiaId(
+ AccountKeyToObfuscatedId(kPrimaryAccountKey));
+ EXPECT_EQ(kPrimaryAccountKey, ids.account_key);
+ EXPECT_EQ(kPrimaryAccountKey, ids.email);
+ EXPECT_EQ(AccountKeyToObfuscatedId(kPrimaryAccountKey), ids.gaia);
+}
+
+TEST_F(IdentityAccountTrackerTest, FindAccountIdsByGaiaIdNotFound) {
+ SetupPrimaryLogin();
+
+ AccountIds ids = account_tracker()->FindAccountIdsByGaiaId(
+ AccountKeyToObfuscatedId("notfound@example.com"));
+ EXPECT_TRUE(ids.account_key.empty());
+ EXPECT_TRUE(ids.email.empty());
+ EXPECT_TRUE(ids.gaia.empty());
+}
+
+TEST_F(IdentityAccountTrackerTest,
+ FindAccountIdsByGaiaIdReturnEmptyWhenPrimarySignedOut) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("zeta@example.com");
+ ReturnOAuthUrlFetchSuccess("zeta@example.com");
+ NotifyTokenAvailable("alpha@example.com");
+ ReturnOAuthUrlFetchSuccess("alpha@example.com");
+
+ NotifyTokenRevoked(kPrimaryAccountKey);
+
+ AccountIds ids =
+ account_tracker()->FindAccountIdsByGaiaId(kPrimaryAccountKey);
+ EXPECT_TRUE(ids.account_key.empty());
+ EXPECT_TRUE(ids.email.empty());
+ EXPECT_TRUE(ids.gaia.empty());
+
+ ids = account_tracker()->FindAccountIdsByGaiaId("alpha@example.com");
+ EXPECT_TRUE(ids.account_key.empty());
+ EXPECT_TRUE(ids.email.empty());
+ EXPECT_TRUE(ids.gaia.empty());
+}
+
+} // namespace gaia
diff --git a/google_apis/gaia/fake_oauth2_token_service.cc b/google_apis/gaia/fake_oauth2_token_service.cc
index f23f349..0b01404 100644
--- a/google_apis/gaia/fake_oauth2_token_service.cc
+++ b/google_apis/gaia/fake_oauth2_token_service.cc
@@ -4,11 +4,22 @@
#include "google_apis/gaia/fake_oauth2_token_service.h"
-FakeOAuth2TokenService::FakeOAuth2TokenService() : request_context_(NULL) {}
+FakeOAuth2TokenService::PendingRequest::PendingRequest() {
+}
+
+FakeOAuth2TokenService::PendingRequest::~PendingRequest() {
+}
+
+FakeOAuth2TokenService::FakeOAuth2TokenService() : request_context_(NULL) {
+}
FakeOAuth2TokenService::~FakeOAuth2TokenService() {
}
+std::vector<std::string> FakeOAuth2TokenService::GetAccounts() {
+ return std::vector<std::string>(account_ids_.begin(), account_ids_.end());
+}
+
void FakeOAuth2TokenService::FetchOAuth2Token(
RequestImpl* request,
const std::string& account_id,
@@ -16,6 +27,13 @@ void FakeOAuth2TokenService::FetchOAuth2Token(
const std::string& client_id,
const std::string& client_secret,
const ScopeSet& scopes) {
+ PendingRequest pending_request;
+ pending_request.account_id = account_id;
+ pending_request.client_id = client_id;
+ pending_request.client_secret = client_secret;
+ pending_request.scopes = scopes;
+ pending_request.request = request->AsWeakPtr();
+ pending_requests_.push_back(pending_request);
}
void FakeOAuth2TokenService::InvalidateOAuth2Token(
@@ -36,8 +54,30 @@ bool FakeOAuth2TokenService::RefreshTokenIsAvailable(
void FakeOAuth2TokenService::AddAccount(const std::string& account_id) {
account_ids_.insert(account_id);
+ FireRefreshTokenAvailable(account_id);
}
+void FakeOAuth2TokenService::RemoveAccount(const std::string& account_id) {
+ account_ids_.erase(account_id);
+ FireRefreshTokenRevoked(account_id);
+}
+
+void FakeOAuth2TokenService::IssueAllTokensForAccount(
+ const std::string& account_id,
+ const std::string& access_token,
+ const base::Time& expiration) {
+
+ // Walk the requests and notify the callbacks.
+ for (std::vector<PendingRequest>::iterator it = pending_requests_.begin();
+ it != pending_requests_.end(); ++it) {
+ if (it->request && (account_id == it->account_id)) {
+ it->request->InformConsumer(
+ GoogleServiceAuthError::AuthErrorNone(), access_token, expiration);
+ }
+ }
+}
+
+
OAuth2AccessTokenFetcher* FakeOAuth2TokenService::CreateAccessTokenFetcher(
const std::string& account_id,
net::URLRequestContextGetter* getter,
diff --git a/google_apis/gaia/fake_oauth2_token_service.h b/google_apis/gaia/fake_oauth2_token_service.h
index 8c267e5..3bdb675 100644
--- a/google_apis/gaia/fake_oauth2_token_service.h
+++ b/google_apis/gaia/fake_oauth2_token_service.h
@@ -9,6 +9,7 @@
#include <string>
#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
#include "google_apis/gaia/oauth2_token_service.h"
namespace net {
@@ -21,7 +22,15 @@ class FakeOAuth2TokenService : public OAuth2TokenService {
FakeOAuth2TokenService();
virtual ~FakeOAuth2TokenService();
+ virtual std::vector<std::string> GetAccounts() OVERRIDE;
+
void AddAccount(const std::string& account_id);
+ void RemoveAccount(const std::string& account_id);
+
+ // Helper routines to issue tokens for pending requests.
+ void IssueAllTokensForAccount(const std::string& account_id,
+ const std::string& access_token,
+ const base::Time& expiration);
void set_request_context(net::URLRequestContextGetter* request_context) {
request_context_ = request_context;
@@ -45,6 +54,17 @@ class FakeOAuth2TokenService : public OAuth2TokenService {
OVERRIDE;
private:
+ struct PendingRequest {
+ PendingRequest();
+ ~PendingRequest();
+
+ std::string account_id;
+ std::string client_id;
+ std::string client_secret;
+ ScopeSet scopes;
+ base::WeakPtr<RequestImpl> request;
+ };
+
// OAuth2TokenService overrides.
virtual net::URLRequestContextGetter* GetRequestContext() OVERRIDE;
@@ -54,6 +74,8 @@ class FakeOAuth2TokenService : public OAuth2TokenService {
OAuth2AccessTokenConsumer* consumer) OVERRIDE;
std::set<std::string> account_ids_;
+ std::vector<PendingRequest> pending_requests_;
+
net::URLRequestContextGetter* request_context_; // weak
DISALLOW_COPY_AND_ASSIGN(FakeOAuth2TokenService);
diff --git a/google_apis/google_apis.gyp b/google_apis/google_apis.gyp
index a3dbb03..77dfce9 100644
--- a/google_apis/google_apis.gyp
+++ b/google_apis/google_apis.gyp
@@ -95,6 +95,8 @@
'drive/task_util.h',
'drive/time_util.cc',
'drive/time_util.h',
+ 'gaia/account_tracker.cc',
+ 'gaia/account_tracker.h',
'gaia/gaia_auth_consumer.cc',
'gaia/gaia_auth_consumer.h',
'gaia/gaia_auth_fetcher.cc',
@@ -168,6 +170,7 @@
'drive/request_sender_unittest.cc',
'drive/request_util_unittest.cc',
'drive/time_util_unittest.cc',
+ 'gaia/account_tracker_unittest.cc',
'gaia/gaia_auth_fetcher_unittest.cc',
'gaia/gaia_auth_util_unittest.cc',
'gaia/gaia_oauth_client_unittest.cc',