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 <algorithm>
6#include <vector>
7
8#include "base/message_loop/message_loop.h"
9#include "base/prefs/pref_registry_simple.h"
10#include "base/prefs/testing_pref_service.h"
11#include "base/strings/stringprintf.h"
12#include "components/signin/core/browser/account_tracker_service.h"
13#include "google_apis/gaia/fake_oauth2_token_service.h"
14#include "google_apis/gaia/gaia_oauth_client.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_fetcher_delegate.h"
18#include "net/url_request/url_request_test_util.h"
19#include "testing/gtest/include/gtest/gtest.h"
20
21namespace {
22
23enum TrackingEventType {
24  UPDATED,
25  REMOVED,
26};
27
28std::string AccountIdToEmail(const std::string account_id) {
29  return account_id + "@gmail.com";
30}
31
32std::string AccountIdToGaiaId(const std::string account_id) {
33  return "gaia-" + account_id;
34}
35
36class TrackingEvent {
37 public:
38  TrackingEvent(TrackingEventType type,
39                const std::string& account_id,
40                const std::string& gaia_id)
41      : type_(type),
42        account_id_(account_id),
43        gaia_id_(gaia_id) {}
44
45  TrackingEvent(TrackingEventType type,
46                const std::string& account_id)
47      : type_(type),
48        account_id_(account_id),
49        gaia_id_(AccountIdToGaiaId(account_id)) {}
50
51  bool operator==(const TrackingEvent& event) const {
52    return type_ == event.type_ && account_id_ == event.account_id_ &&
53        gaia_id_ == event.gaia_id_;
54  }
55
56  std::string ToString() const {
57    const char * typestr = "INVALID";
58    switch (type_) {
59      case UPDATED:
60        typestr = "UPD";
61        break;
62      case REMOVED:
63        typestr = "REM";
64        break;
65    }
66    return base::StringPrintf("{ type: %s, account_id: %s, gaia: %s }",
67                              typestr,
68                              account_id_.c_str(),
69                              gaia_id_.c_str());
70  }
71
72 private:
73  friend bool CompareByUser(TrackingEvent a, TrackingEvent b);
74
75  TrackingEventType type_;
76  std::string account_id_;
77  std::string gaia_id_;
78};
79
80bool CompareByUser(TrackingEvent a, TrackingEvent b) {
81  return a.account_id_ < b.account_id_;
82}
83
84std::string Str(const std::vector<TrackingEvent>& events) {
85  std::string str = "[";
86  bool needs_comma = false;
87  for (std::vector<TrackingEvent>::const_iterator it =
88       events.begin(); it != events.end(); ++it) {
89    if (needs_comma)
90      str += ",\n ";
91    needs_comma = true;
92    str += it->ToString();
93  }
94  str += "]";
95  return str;
96}
97
98class AccountTrackerObserver : public AccountTrackerService::Observer {
99 public:
100  AccountTrackerObserver() {}
101  virtual ~AccountTrackerObserver() {}
102
103  void Clear();
104  void SortEventsByUser();
105
106  testing::AssertionResult CheckEvents();
107  testing::AssertionResult CheckEvents(const TrackingEvent& e1);
108  testing::AssertionResult CheckEvents(const TrackingEvent& e1,
109                                       const TrackingEvent& e2);
110  testing::AssertionResult CheckEvents(const TrackingEvent& e1,
111                                       const TrackingEvent& e2,
112                                       const TrackingEvent& e3);
113
114 private:
115  // AccountTrackerService::Observer implementation
116  virtual void OnAccountUpdated(
117      const AccountTrackerService::AccountInfo& ids) OVERRIDE;
118  virtual void OnAccountRemoved(
119      const AccountTrackerService::AccountInfo& ids) OVERRIDE;
120
121  testing::AssertionResult CheckEvents(
122      const std::vector<TrackingEvent>& events);
123
124  std::vector<TrackingEvent> events_;
125};
126
127void AccountTrackerObserver::OnAccountUpdated(
128    const AccountTrackerService::AccountInfo& ids) {
129  events_.push_back(TrackingEvent(UPDATED, ids.account_id, ids.gaia));
130}
131
132void AccountTrackerObserver::OnAccountRemoved(
133    const AccountTrackerService::AccountInfo& ids) {
134  events_.push_back(TrackingEvent(REMOVED, ids.account_id, ids.gaia));
135}
136
137void AccountTrackerObserver::Clear() {
138  events_.clear();
139}
140
141void AccountTrackerObserver::SortEventsByUser() {
142  std::stable_sort(events_.begin(), events_.end(), CompareByUser);
143}
144
145testing::AssertionResult AccountTrackerObserver::CheckEvents() {
146  std::vector<TrackingEvent> events;
147  return CheckEvents(events);
148}
149
150testing::AssertionResult AccountTrackerObserver::CheckEvents(
151    const TrackingEvent& e1) {
152  std::vector<TrackingEvent> events;
153  events.push_back(e1);
154  return CheckEvents(events);
155}
156
157testing::AssertionResult AccountTrackerObserver::CheckEvents(
158    const TrackingEvent& e1,
159    const TrackingEvent& e2) {
160  std::vector<TrackingEvent> events;
161  events.push_back(e1);
162  events.push_back(e2);
163  return CheckEvents(events);
164}
165
166testing::AssertionResult AccountTrackerObserver::CheckEvents(
167    const TrackingEvent& e1,
168    const TrackingEvent& e2,
169    const TrackingEvent& e3) {
170  std::vector<TrackingEvent> events;
171  events.push_back(e1);
172  events.push_back(e2);
173  events.push_back(e3);
174  return CheckEvents(events);
175}
176
177testing::AssertionResult AccountTrackerObserver::CheckEvents(
178    const std::vector<TrackingEvent>& events) {
179  std::string maybe_newline = (events.size() + events_.size()) > 2 ? "\n" : "";
180  testing::AssertionResult result(
181      (events_ == events)
182          ? testing::AssertionSuccess()
183          : (testing::AssertionFailure()
184             << "Expected " << maybe_newline << Str(events) << ", "
185             << maybe_newline << "Got " << maybe_newline << Str(events_)));
186  events_.clear();
187  return result;
188}
189
190}  // namespace
191
192class AccountTrackerServiceTest : public testing::Test {
193 public:
194  AccountTrackerServiceTest() {}
195
196  virtual ~AccountTrackerServiceTest() {}
197
198  virtual void SetUp() OVERRIDE {
199    fake_oauth2_token_service_.reset(new FakeOAuth2TokenService());
200
201    pref_service_.registry()->RegisterListPref(
202        AccountTrackerService::kAccountInfoPref);
203
204    account_tracker_.reset(new AccountTrackerService());
205    account_tracker_->Initialize(fake_oauth2_token_service_.get(),
206                                 &pref_service_,
207                                 new net::TestURLRequestContextGetter(
208                                     message_loop_.message_loop_proxy()));
209    account_tracker_->AddObserver(&observer_);
210  }
211
212  virtual void TearDown() OVERRIDE {
213    account_tracker_->RemoveObserver(&observer_);
214    account_tracker_->Shutdown();
215  }
216
217  void SimulateTokenAvailable(const std::string& account_id) {
218    fake_oauth2_token_service_->AddAccount(account_id);
219  }
220
221  void SimulateTokenRevoked(const std::string& account_id) {
222    fake_oauth2_token_service_->RemoveAccount(account_id);
223  }
224
225  // Helpers to fake access token and user info fetching
226  void IssueAccessToken(const std::string& account_id) {
227    fake_oauth2_token_service_->IssueAllTokensForAccount(
228        account_id, "access_token-" + account_id, base::Time::Max());
229  }
230
231  std::string GenerateValidTokenInfoResponse(const std::string& account_id) {
232    return base::StringPrintf("{\"id\": \"%s\", \"email\": \"%s\"}",
233                              AccountIdToGaiaId(account_id).c_str(),
234                              AccountIdToEmail(account_id).c_str());
235  }
236
237  void ReturnOAuthUrlFetchSuccess(const std::string& account_id);
238  void ReturnOAuthUrlFetchFailure(const std::string& account_id);
239
240  base::MessageLoopForIO* message_loop() { return &message_loop_; }
241  AccountTrackerService* account_tracker() { return account_tracker_.get(); }
242  AccountTrackerObserver* observer() { return &observer_; }
243  OAuth2TokenService* token_service() {
244    return fake_oauth2_token_service_.get();
245  }
246  TestingPrefServiceSimple* pref_service() { return &pref_service_; }
247
248 private:
249  void ReturnOAuthUrlFetchResults(int fetcher_id,
250                                  net::HttpStatusCode response_code,
251                                  const std::string& response_string);
252
253  base::MessageLoopForIO message_loop_;
254  net::TestURLFetcherFactory test_fetcher_factory_;
255  scoped_ptr<FakeOAuth2TokenService> fake_oauth2_token_service_;
256  TestingPrefServiceSimple pref_service_;
257  scoped_ptr<AccountTrackerService> account_tracker_;
258  AccountTrackerObserver observer_;
259};
260
261void AccountTrackerServiceTest::ReturnOAuthUrlFetchResults(
262    int fetcher_id,
263    net::HttpStatusCode response_code,
264    const std::string&  response_string) {
265  net::TestURLFetcher* fetcher =
266      test_fetcher_factory_.GetFetcherByID(fetcher_id);
267  ASSERT_TRUE(fetcher);
268  fetcher->set_response_code(response_code);
269  fetcher->SetResponseString(response_string);
270  fetcher->delegate()->OnURLFetchComplete(fetcher);
271}
272
273void AccountTrackerServiceTest::ReturnOAuthUrlFetchSuccess(
274    const std::string& account_id) {
275  IssueAccessToken(account_id);
276  ReturnOAuthUrlFetchResults(gaia::GaiaOAuthClient::kUrlFetcherId,
277                             net::HTTP_OK,
278                             GenerateValidTokenInfoResponse(account_id));
279}
280
281void AccountTrackerServiceTest::ReturnOAuthUrlFetchFailure(
282    const std::string& account_id) {
283  IssueAccessToken(account_id);
284  ReturnOAuthUrlFetchResults(
285      gaia::GaiaOAuthClient::kUrlFetcherId, net::HTTP_BAD_REQUEST, "");
286}
287
288TEST_F(AccountTrackerServiceTest, Basic) {
289}
290
291TEST_F(AccountTrackerServiceTest, TokenAvailable) {
292  SimulateTokenAvailable("alpha");
293  ASSERT_FALSE(account_tracker()->IsAllUserInfoFetched());
294  ASSERT_TRUE(observer()->CheckEvents());
295}
296
297TEST_F(AccountTrackerServiceTest, TokenAvailable_Revoked) {
298  SimulateTokenAvailable("alpha");
299  SimulateTokenRevoked("alpha");
300  ASSERT_TRUE(account_tracker()->IsAllUserInfoFetched());
301  ASSERT_TRUE(observer()->CheckEvents());
302}
303
304TEST_F(AccountTrackerServiceTest, TokenAvailable_UserInfo) {
305  SimulateTokenAvailable("alpha");
306  ReturnOAuthUrlFetchSuccess("alpha");
307  ASSERT_TRUE(account_tracker()->IsAllUserInfoFetched());
308  ASSERT_TRUE(observer()->CheckEvents(TrackingEvent(UPDATED, "alpha")));
309}
310
311TEST_F(AccountTrackerServiceTest, TokenAvailable_UserInfo_Revoked) {
312  SimulateTokenAvailable("alpha");
313  ReturnOAuthUrlFetchSuccess("alpha");
314  ASSERT_TRUE(account_tracker()->IsAllUserInfoFetched());
315  ASSERT_TRUE(observer()->CheckEvents(TrackingEvent(UPDATED, "alpha")));
316  SimulateTokenRevoked("alpha");
317  ASSERT_TRUE(observer()->CheckEvents(TrackingEvent(REMOVED, "alpha")));
318}
319
320TEST_F(AccountTrackerServiceTest, TokenAvailable_UserInfoFailed) {
321  SimulateTokenAvailable("alpha");
322  ReturnOAuthUrlFetchFailure("alpha");
323  ASSERT_TRUE(account_tracker()->IsAllUserInfoFetched());
324  ASSERT_TRUE(observer()->CheckEvents());
325}
326
327TEST_F(AccountTrackerServiceTest, TokenAlreadyExists) {
328  SimulateTokenAvailable("alpha");
329  AccountTrackerService tracker;
330  AccountTrackerObserver observer;
331  tracker.AddObserver(&observer);
332  tracker.Initialize(token_service(),
333                     pref_service(),
334                     new net::TestURLRequestContextGetter(
335                         message_loop()->message_loop_proxy()));
336  ASSERT_FALSE(tracker.IsAllUserInfoFetched());
337  ASSERT_TRUE(observer.CheckEvents());
338  tracker.RemoveObserver(&observer);
339  tracker.Shutdown();
340}
341
342TEST_F(AccountTrackerServiceTest, TwoTokenAvailable_TwoUserInfo) {
343  SimulateTokenAvailable("alpha");
344  SimulateTokenAvailable("beta");
345  ReturnOAuthUrlFetchSuccess("alpha");
346  ReturnOAuthUrlFetchSuccess("beta");
347  ASSERT_TRUE(account_tracker()->IsAllUserInfoFetched());
348  ASSERT_TRUE(observer()->CheckEvents(TrackingEvent(UPDATED, "alpha"),
349                                      TrackingEvent(UPDATED, "beta")));
350}
351
352TEST_F(AccountTrackerServiceTest, TwoTokenAvailable_OneUserInfo) {
353  SimulateTokenAvailable("alpha");
354  SimulateTokenAvailable("beta");
355  ReturnOAuthUrlFetchSuccess("beta");
356  ASSERT_FALSE(account_tracker()->IsAllUserInfoFetched());
357  ASSERT_TRUE(observer()->CheckEvents(TrackingEvent(UPDATED, "beta")));
358  ReturnOAuthUrlFetchSuccess("alpha");
359  ASSERT_TRUE(account_tracker()->IsAllUserInfoFetched());
360  ASSERT_TRUE(observer()->CheckEvents(TrackingEvent(UPDATED, "alpha")));
361}
362
363TEST_F(AccountTrackerServiceTest, GetAccounts) {
364  SimulateTokenAvailable("alpha");
365  SimulateTokenAvailable("beta");
366  SimulateTokenAvailable("gamma");
367  ReturnOAuthUrlFetchSuccess("alpha");
368  ReturnOAuthUrlFetchSuccess("beta");
369  ReturnOAuthUrlFetchSuccess("gamma");
370
371  std::vector<AccountTrackerService::AccountInfo> infos =
372      account_tracker()->GetAccounts();
373
374  EXPECT_EQ(3u, infos.size());
375  EXPECT_EQ("alpha", infos[0].account_id);
376  EXPECT_EQ(AccountIdToGaiaId("alpha"), infos[0].gaia);
377  EXPECT_EQ(AccountIdToEmail("alpha"), infos[0].email);
378  EXPECT_EQ("beta", infos[1].account_id);
379  EXPECT_EQ(AccountIdToGaiaId("beta"), infos[1].gaia);
380  EXPECT_EQ(AccountIdToEmail("beta"), infos[1].email);
381  EXPECT_EQ("gamma", infos[2].account_id);
382  EXPECT_EQ(AccountIdToGaiaId("gamma"), infos[2].gaia);
383  EXPECT_EQ(AccountIdToEmail("gamma"), infos[2].email);
384}
385
386TEST_F(AccountTrackerServiceTest, GetAccountInfo_Empty) {
387  AccountTrackerService::AccountInfo info =
388      account_tracker()->GetAccountInfo("alpha");
389  ASSERT_EQ("", info.account_id);
390}
391
392TEST_F(AccountTrackerServiceTest, GetAccountInfo_TokenAvailable) {
393  SimulateTokenAvailable("alpha");
394  AccountTrackerService::AccountInfo info =
395      account_tracker()->GetAccountInfo("alpha");
396  ASSERT_EQ("alpha", info.account_id);
397  ASSERT_EQ("", info.gaia);
398  ASSERT_EQ("", info.email);
399}
400
401TEST_F(AccountTrackerServiceTest, GetAccountInfo_TokenAvailable_UserInfo) {
402  SimulateTokenAvailable("alpha");
403  ReturnOAuthUrlFetchSuccess("alpha");
404  AccountTrackerService::AccountInfo info =
405      account_tracker()->GetAccountInfo("alpha");
406  ASSERT_EQ("alpha", info.account_id);
407  ASSERT_EQ(AccountIdToGaiaId("alpha"), info.gaia);
408  ASSERT_EQ(AccountIdToEmail("alpha"), info.email);
409}
410
411TEST_F(AccountTrackerServiceTest, FindAccountInfoByGaiaId) {
412  SimulateTokenAvailable("alpha");
413  ReturnOAuthUrlFetchSuccess("alpha");
414
415  std::string gaia_id = AccountIdToGaiaId("alpha");
416  AccountTrackerService::AccountInfo info =
417      account_tracker()->FindAccountInfoByGaiaId(gaia_id);
418  ASSERT_EQ("alpha", info.account_id);
419  ASSERT_EQ(gaia_id, info.gaia);
420
421  gaia_id = AccountIdToGaiaId("beta");
422  info = account_tracker()->FindAccountInfoByGaiaId(gaia_id);
423  ASSERT_EQ("", info.account_id);
424}
425
426TEST_F(AccountTrackerServiceTest, FindAccountInfoByEmail) {
427  SimulateTokenAvailable("alpha");
428  ReturnOAuthUrlFetchSuccess("alpha");
429
430  std::string email = AccountIdToEmail("alpha");
431  AccountTrackerService::AccountInfo info =
432      account_tracker()->FindAccountInfoByEmail(email);
433  ASSERT_EQ("alpha", info.account_id);
434  ASSERT_EQ(email, info.email);
435
436  // Should also work with "canonically-equal" email addresses.
437  info = account_tracker()->FindAccountInfoByEmail("Alpha@Gmail.COM");
438  ASSERT_EQ("alpha", info.account_id);
439  ASSERT_EQ(email, info.email);
440  info = account_tracker()->FindAccountInfoByEmail("al.pha@gmail.com");
441  ASSERT_EQ("alpha", info.account_id);
442  ASSERT_EQ(email, info.email);
443
444  email = AccountIdToEmail("beta");
445  info = account_tracker()->FindAccountInfoByEmail(email);
446  ASSERT_EQ("", info.account_id);
447}
448
449TEST_F(AccountTrackerServiceTest, Persistence) {
450  // Create a tracker and add two accounts.  This should cause the accounts
451  // to be saved to persistence.
452  {
453    AccountTrackerService tracker;
454    tracker.Initialize(token_service(),
455                       pref_service(),
456                       new net::TestURLRequestContextGetter(
457                           message_loop()->message_loop_proxy()));
458    SimulateTokenAvailable("alpha");
459    ReturnOAuthUrlFetchSuccess("alpha");
460    SimulateTokenAvailable("beta");
461    ReturnOAuthUrlFetchSuccess("beta");
462    tracker.Shutdown();
463  }
464
465  // Create a new tracker and make sure it loads the accounts corectly from
466  // persistence.
467  {
468    AccountTrackerService tracker;
469    tracker.AddObserver(observer());
470    tracker.Initialize(token_service(),
471                       pref_service(),
472                       new net::TestURLRequestContextGetter(
473                           message_loop()->message_loop_proxy()));
474    ASSERT_TRUE(observer()->CheckEvents(TrackingEvent(UPDATED, "alpha"),
475                                        TrackingEvent(UPDATED, "beta")));
476
477    std::vector<AccountTrackerService::AccountInfo> infos =
478        tracker.GetAccounts();
479    ASSERT_EQ(2u, infos.size());
480    EXPECT_EQ(AccountIdToGaiaId("alpha"), infos[0].gaia);
481    EXPECT_EQ(AccountIdToEmail("alpha"), infos[0].email);
482    EXPECT_EQ("beta", infos[1].account_id);
483    EXPECT_EQ(AccountIdToGaiaId("beta"), infos[1].gaia);
484    EXPECT_EQ(AccountIdToEmail("beta"), infos[1].email);
485
486    // Remove account.
487    SimulateTokenRevoked("alpha");
488    tracker.RemoveObserver(observer());
489    tracker.Shutdown();
490 }
491
492  // Create a new tracker and make sure it loads the single account from
493  // persistence.
494  {
495    AccountTrackerService tracker;
496    tracker.Initialize(token_service(),
497                       pref_service(),
498                       new net::TestURLRequestContextGetter(
499                           message_loop()->message_loop_proxy()));
500
501    std::vector<AccountTrackerService::AccountInfo> infos =
502        tracker.GetAccounts();
503    ASSERT_EQ(1u, infos.size());
504    EXPECT_EQ("beta", infos[0].account_id);
505    EXPECT_EQ(AccountIdToGaiaId("beta"), infos[0].gaia);
506    EXPECT_EQ(AccountIdToEmail("beta"), infos[0].email);
507    tracker.Shutdown();
508  }
509}
510