1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/services/gcm/gcm_account_tracker.h"
6
7#include <map>
8#include <string>
9
10#include "base/memory/scoped_ptr.h"
11#include "base/message_loop/message_loop.h"
12#include "google_apis/gaia/fake_identity_provider.h"
13#include "google_apis/gaia/fake_oauth2_token_service.h"
14#include "google_apis/gaia/google_service_auth_error.h"
15#include "net/http/http_status_code.h"
16#include "net/url_request/test_url_fetcher_factory.h"
17#include "net/url_request/url_request_test_util.h"
18#include "testing/gtest/include/gtest/gtest.h"
19
20namespace gcm {
21
22namespace {
23
24const char kAccountId1[] = "account_1";
25const char kAccountId2[] = "account_2";
26
27std::string AccountKeyToObfuscatedId(const std::string email) {
28  return "obfid-" + email;
29}
30
31std::string GetValidTokenInfoResponse(const std::string account_key) {
32  return std::string("{ \"id\": \"") + AccountKeyToObfuscatedId(account_key) +
33         "\" }";
34}
35
36std::string MakeAccessToken(const std::string& account_key) {
37  return "access_token-" + account_key;
38}
39
40}  // namespace
41
42class GCMAccountTrackerTest : public testing::Test {
43 public:
44  GCMAccountTrackerTest();
45  virtual ~GCMAccountTrackerTest();
46
47  // Callback for the account tracker.
48  void UpdateAccounts(const std::map<std::string, std::string>& accounts);
49
50  // Helpers to pass fake events to the tracker. Tests should have either a pair
51  // of Start/FinishAccountSignIn or SignInAccount per account. Don't mix.
52  // Call to SignOutAccount is not mandatory.
53  void StartAccountSignIn(const std::string& account_key);
54  void FinishAccountSignIn(const std::string& account_key);
55  void SignInAccount(const std::string& account_key);
56  void SignOutAccount(const std::string& account_key);
57
58  // Helpers for dealing with OAuth2 access token requests.
59  void IssueAccessToken(const std::string& account_key);
60  void IssueError(const std::string& account_key);
61
62  // Test results and helpers.
63  void ResetResults();
64  bool update_accounts_called() const { return update_accounts_called_; }
65  const std::map<std::string, std::string>& accounts() const {
66    return accounts_;
67  }
68
69  // Accessor to account tracker.
70  GCMAccountTracker* tracker() { return tracker_.get(); }
71
72 private:
73  std::map<std::string, std::string> accounts_;
74  bool update_accounts_called_;
75
76  base::MessageLoop message_loop_;
77  net::TestURLFetcherFactory test_fetcher_factory_;
78  scoped_ptr<FakeOAuth2TokenService> fake_token_service_;
79  scoped_ptr<FakeIdentityProvider> fake_identity_provider_;
80  scoped_ptr<GCMAccountTracker> tracker_;
81};
82
83GCMAccountTrackerTest::GCMAccountTrackerTest()
84    : update_accounts_called_(false) {
85  fake_token_service_.reset(new FakeOAuth2TokenService());
86
87  fake_identity_provider_.reset(
88      new FakeIdentityProvider(fake_token_service_.get()));
89
90  scoped_ptr<gaia::AccountTracker> gaia_account_tracker(
91      new gaia::AccountTracker(fake_identity_provider_.get(),
92                               new net::TestURLRequestContextGetter(
93                                   message_loop_.message_loop_proxy())));
94
95  tracker_.reset(new GCMAccountTracker(
96      gaia_account_tracker.Pass(),
97      base::Bind(&GCMAccountTrackerTest::UpdateAccounts,
98                 base::Unretained(this))));
99}
100
101GCMAccountTrackerTest::~GCMAccountTrackerTest() {
102  if (tracker_)
103    tracker_->Shutdown();
104}
105
106void GCMAccountTrackerTest::UpdateAccounts(
107    const std::map<std::string, std::string>& accounts) {
108  update_accounts_called_ = true;
109  accounts_ = accounts;
110}
111
112void GCMAccountTrackerTest::ResetResults() {
113  accounts_.clear();
114  update_accounts_called_ = false;
115}
116
117void GCMAccountTrackerTest::StartAccountSignIn(const std::string& account_key) {
118  fake_identity_provider_->LogIn(account_key);
119  fake_token_service_->AddAccount(account_key);
120}
121
122void GCMAccountTrackerTest::FinishAccountSignIn(
123    const std::string& account_key) {
124  IssueAccessToken(account_key);
125
126  net::TestURLFetcher* fetcher = test_fetcher_factory_.GetFetcherByID(
127      gaia::GaiaOAuthClient::kUrlFetcherId);
128  ASSERT_TRUE(fetcher);
129  fetcher->set_response_code(net::HTTP_OK);
130  fetcher->SetResponseString(GetValidTokenInfoResponse(account_key));
131  fetcher->delegate()->OnURLFetchComplete(fetcher);
132}
133
134void GCMAccountTrackerTest::SignInAccount(const std::string& account_key) {
135  StartAccountSignIn(account_key);
136  FinishAccountSignIn(account_key);
137}
138
139void GCMAccountTrackerTest::SignOutAccount(const std::string& account_key) {
140  fake_token_service_->RemoveAccount(account_key);
141}
142
143void GCMAccountTrackerTest::IssueAccessToken(const std::string& account_key) {
144  fake_token_service_->IssueAllTokensForAccount(
145      account_key, MakeAccessToken(account_key), base::Time::Max());
146}
147
148void GCMAccountTrackerTest::IssueError(const std::string& account_key) {
149  fake_token_service_->IssueErrorForAllPendingRequestsForAccount(
150      account_key,
151      GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
152}
153
154TEST_F(GCMAccountTrackerTest, NoAccounts) {
155  EXPECT_FALSE(update_accounts_called());
156  tracker()->Start();
157  // Callback should not be called if there where no accounts provided.
158  EXPECT_FALSE(update_accounts_called());
159  EXPECT_TRUE(accounts().empty());
160  tracker()->Stop();
161}
162
163// Verifies that callback is called after a token is issued for a single account
164// with a specific scope. In this scenario, the underlying account tracker is
165// still working when the CompleteCollectingTokens is called for the first time.
166TEST_F(GCMAccountTrackerTest, SingleAccount) {
167  StartAccountSignIn(kAccountId1);
168
169  tracker()->Start();
170  // We don't have any accounts to report, but given the inner account tracker
171  // is still working we don't make a call with empty accounts list.
172  EXPECT_FALSE(update_accounts_called());
173
174  // This concludes the work of inner account tracker.
175  FinishAccountSignIn(kAccountId1);
176  IssueAccessToken(kAccountId1);
177
178  EXPECT_TRUE(update_accounts_called());
179
180  std::map<std::string, std::string> expected_accounts;
181  expected_accounts[kAccountId1] = MakeAccessToken(kAccountId1);
182  EXPECT_EQ(expected_accounts, accounts());
183  tracker()->Stop();
184}
185
186TEST_F(GCMAccountTrackerTest, MultipleAccounts) {
187  StartAccountSignIn(kAccountId1);
188  StartAccountSignIn(kAccountId2);
189
190  tracker()->Start();
191  EXPECT_FALSE(update_accounts_called());
192
193  FinishAccountSignIn(kAccountId1);
194  IssueAccessToken(kAccountId1);
195  EXPECT_FALSE(update_accounts_called());
196
197  FinishAccountSignIn(kAccountId2);
198  IssueAccessToken(kAccountId2);
199  EXPECT_TRUE(update_accounts_called());
200
201  std::map<std::string, std::string> expected_accounts;
202  expected_accounts[kAccountId1] = MakeAccessToken(kAccountId1);
203  expected_accounts[kAccountId2] = MakeAccessToken(kAccountId2);
204  EXPECT_EQ(expected_accounts, accounts());
205
206  tracker()->Stop();
207}
208
209TEST_F(GCMAccountTrackerTest, AccountAdded) {
210  tracker()->Start();
211  ResetResults();
212
213  SignInAccount(kAccountId1);
214  EXPECT_FALSE(update_accounts_called());
215
216  IssueAccessToken(kAccountId1);
217  EXPECT_TRUE(update_accounts_called());
218
219  std::map<std::string, std::string> expected_accounts;
220  expected_accounts[kAccountId1] = MakeAccessToken(kAccountId1);
221  EXPECT_EQ(expected_accounts, accounts());
222
223  tracker()->Stop();
224}
225
226TEST_F(GCMAccountTrackerTest, AccountRemoved) {
227  SignInAccount(kAccountId1);
228  SignInAccount(kAccountId2);
229
230  tracker()->Start();
231  IssueAccessToken(kAccountId1);
232  IssueAccessToken(kAccountId2);
233  EXPECT_TRUE(update_accounts_called());
234
235  ResetResults();
236  EXPECT_FALSE(update_accounts_called());
237
238  SignOutAccount(kAccountId2);
239  EXPECT_TRUE(update_accounts_called());
240
241  std::map<std::string, std::string> expected_accounts;
242  expected_accounts[kAccountId1] = MakeAccessToken(kAccountId1);
243  EXPECT_EQ(expected_accounts, accounts());
244
245  tracker()->Stop();
246}
247
248TEST_F(GCMAccountTrackerTest, GetTokenFailed) {
249  SignInAccount(kAccountId1);
250  SignInAccount(kAccountId2);
251
252  tracker()->Start();
253  IssueAccessToken(kAccountId1);
254  EXPECT_FALSE(update_accounts_called());
255
256  IssueError(kAccountId2);
257  EXPECT_TRUE(update_accounts_called());
258
259  std::map<std::string, std::string> expected_accounts;
260  expected_accounts[kAccountId1] = MakeAccessToken(kAccountId1);
261  EXPECT_EQ(expected_accounts, accounts());
262
263  tracker()->Stop();
264}
265
266TEST_F(GCMAccountTrackerTest, GetTokenFailedAccountRemoved) {
267  SignInAccount(kAccountId1);
268  SignInAccount(kAccountId2);
269
270  tracker()->Start();
271  IssueAccessToken(kAccountId1);
272  IssueError(kAccountId2);
273
274  ResetResults();
275  SignOutAccount(kAccountId2);
276  EXPECT_TRUE(update_accounts_called());
277
278  std::map<std::string, std::string> expected_accounts;
279  expected_accounts[kAccountId1] = MakeAccessToken(kAccountId1);
280  EXPECT_EQ(expected_accounts, accounts());
281
282  tracker()->Stop();
283}
284
285TEST_F(GCMAccountTrackerTest, AccountRemovedWhileRequestsPending) {
286  SignInAccount(kAccountId1);
287  SignInAccount(kAccountId2);
288
289  tracker()->Start();
290  IssueAccessToken(kAccountId1);
291  EXPECT_FALSE(update_accounts_called());
292
293  SignOutAccount(kAccountId2);
294  IssueAccessToken(kAccountId2);
295  EXPECT_TRUE(update_accounts_called());
296
297  std::map<std::string, std::string> expected_accounts;
298  expected_accounts[kAccountId1] = MakeAccessToken(kAccountId1);
299  EXPECT_EQ(expected_accounts, accounts());
300
301  tracker()->Stop();
302}
303
304// TODO(fgorski): Add test for adding account after removal >> make sure it does
305// not mark removal.
306
307}  // namespace gcm
308