OLD | NEW |
| (Empty) |
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 "components/gcm_driver/fake_gcm_driver.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 | |
20 namespace gcm { | |
21 | |
22 namespace { | |
23 | |
24 const char kAccountId1[] = "account_1"; | |
25 const char kAccountId2[] = "account_2"; | |
26 | |
27 std::string AccountKeyToObfuscatedId(const std::string email) { | |
28 return "obfid-" + email; | |
29 } | |
30 | |
31 std::string GetValidTokenInfoResponse(const std::string account_key) { | |
32 return std::string("{ \"id\": \"") + AccountKeyToObfuscatedId(account_key) + | |
33 "\" }"; | |
34 } | |
35 | |
36 std::string MakeAccessToken(const std::string& account_key) { | |
37 return "access_token-" + account_key; | |
38 } | |
39 | |
40 GCMClient::AccountTokenInfo MakeAccountToken(const std::string& account_key) { | |
41 GCMClient::AccountTokenInfo token_info; | |
42 token_info.account_id = account_key; | |
43 token_info.email = account_key; | |
44 token_info.access_token = MakeAccessToken(account_key); | |
45 return token_info; | |
46 } | |
47 | |
48 void VerifyAccountTokens( | |
49 const std::vector<GCMClient::AccountTokenInfo>& expected_tokens, | |
50 const std::vector<GCMClient::AccountTokenInfo>& actual_tokens) { | |
51 EXPECT_EQ(expected_tokens.size(), actual_tokens.size()); | |
52 for (std::vector<GCMClient::AccountTokenInfo>::const_iterator | |
53 expected_iter = expected_tokens.begin(), | |
54 actual_iter = actual_tokens.begin(); | |
55 expected_iter != expected_tokens.end() && | |
56 actual_iter != actual_tokens.end(); | |
57 ++expected_iter, ++actual_iter) { | |
58 EXPECT_EQ(expected_iter->account_id, actual_iter->account_id); | |
59 EXPECT_EQ(expected_iter->email, actual_iter->email); | |
60 EXPECT_EQ(expected_iter->access_token, actual_iter->access_token); | |
61 } | |
62 } | |
63 | |
64 // This version of FakeGCMDriver is customized around handling accounts and | |
65 // connection events for testing GCMAccountTracker. | |
66 class CustomFakeGCMDriver : public FakeGCMDriver { | |
67 public: | |
68 CustomFakeGCMDriver(); | |
69 ~CustomFakeGCMDriver() override; | |
70 | |
71 // GCMDriver overrides: | |
72 void SetAccountTokens( | |
73 const std::vector<GCMClient::AccountTokenInfo>& account_tokens) override; | |
74 void AddConnectionObserver(GCMConnectionObserver* observer) override; | |
75 void RemoveConnectionObserver(GCMConnectionObserver* observer) override; | |
76 bool IsConnected() const override { return connected_; } | |
77 base::Time GetLastTokenFetchTime() override; | |
78 void SetLastTokenFetchTime(const base::Time& time) override; | |
79 | |
80 // Test results and helpers. | |
81 void SetConnected(bool connected); | |
82 void ResetResults(); | |
83 bool update_accounts_called() const { return update_accounts_called_; } | |
84 const std::vector<GCMClient::AccountTokenInfo>& accounts() const { | |
85 return accounts_; | |
86 } | |
87 const GCMConnectionObserver* last_connection_observer() const { | |
88 return last_connection_observer_; | |
89 } | |
90 const GCMConnectionObserver* last_removed_connection_observer() const { | |
91 return removed_connection_observer_; | |
92 } | |
93 | |
94 private: | |
95 bool connected_; | |
96 std::vector<GCMClient::AccountTokenInfo> accounts_; | |
97 bool update_accounts_called_; | |
98 GCMConnectionObserver* last_connection_observer_; | |
99 GCMConnectionObserver* removed_connection_observer_; | |
100 net::IPEndPoint ip_endpoint_; | |
101 base::Time last_token_fetch_time_; | |
102 | |
103 DISALLOW_COPY_AND_ASSIGN(CustomFakeGCMDriver); | |
104 }; | |
105 | |
106 CustomFakeGCMDriver::CustomFakeGCMDriver() | |
107 : connected_(true), | |
108 update_accounts_called_(false), | |
109 last_connection_observer_(NULL), | |
110 removed_connection_observer_(NULL) { | |
111 } | |
112 | |
113 CustomFakeGCMDriver::~CustomFakeGCMDriver() { | |
114 } | |
115 | |
116 void CustomFakeGCMDriver::SetAccountTokens( | |
117 const std::vector<GCMClient::AccountTokenInfo>& accounts) { | |
118 update_accounts_called_ = true; | |
119 accounts_ = accounts; | |
120 } | |
121 | |
122 void CustomFakeGCMDriver::AddConnectionObserver( | |
123 GCMConnectionObserver* observer) { | |
124 last_connection_observer_ = observer; | |
125 } | |
126 | |
127 void CustomFakeGCMDriver::RemoveConnectionObserver( | |
128 GCMConnectionObserver* observer) { | |
129 removed_connection_observer_ = observer; | |
130 } | |
131 | |
132 void CustomFakeGCMDriver::SetConnected(bool connected) { | |
133 connected_ = connected; | |
134 if (connected && last_connection_observer_) | |
135 last_connection_observer_->OnConnected(ip_endpoint_); | |
136 } | |
137 | |
138 void CustomFakeGCMDriver::ResetResults() { | |
139 accounts_.clear(); | |
140 update_accounts_called_ = false; | |
141 last_connection_observer_ = NULL; | |
142 removed_connection_observer_ = NULL; | |
143 } | |
144 | |
145 | |
146 base::Time CustomFakeGCMDriver::GetLastTokenFetchTime() { | |
147 return last_token_fetch_time_; | |
148 } | |
149 | |
150 void CustomFakeGCMDriver::SetLastTokenFetchTime(const base::Time& time) { | |
151 last_token_fetch_time_ = time; | |
152 } | |
153 | |
154 } // namespace | |
155 | |
156 class GCMAccountTrackerTest : public testing::Test { | |
157 public: | |
158 GCMAccountTrackerTest(); | |
159 ~GCMAccountTrackerTest() override; | |
160 | |
161 // Helpers to pass fake events to the tracker. Tests should have either a pair | |
162 // of Start/FinishAccountSignIn or SignInAccount per account. Don't mix. | |
163 // Call to SignOutAccount is not mandatory. | |
164 void StartAccountSignIn(const std::string& account_key); | |
165 void FinishAccountSignIn(const std::string& account_key); | |
166 void SignInAccount(const std::string& account_key); | |
167 void SignOutAccount(const std::string& account_key); | |
168 | |
169 // Helpers for dealing with OAuth2 access token requests. | |
170 void IssueAccessToken(const std::string& account_key); | |
171 void IssueExpiredAccessToken(const std::string& account_key); | |
172 void IssueError(const std::string& account_key); | |
173 | |
174 // Accessors to account tracker and gcm driver. | |
175 GCMAccountTracker* tracker() { return tracker_.get(); } | |
176 CustomFakeGCMDriver* driver() { return &driver_; } | |
177 | |
178 // Accessors to private methods of account tracker. | |
179 bool IsFetchingRequired() const; | |
180 bool IsTokenReportingRequired() const; | |
181 base::TimeDelta GetTimeToNextTokenReporting() const; | |
182 | |
183 private: | |
184 CustomFakeGCMDriver driver_; | |
185 | |
186 base::MessageLoop message_loop_; | |
187 net::TestURLFetcherFactory test_fetcher_factory_; | |
188 scoped_ptr<FakeOAuth2TokenService> fake_token_service_; | |
189 scoped_ptr<FakeIdentityProvider> fake_identity_provider_; | |
190 scoped_ptr<GCMAccountTracker> tracker_; | |
191 }; | |
192 | |
193 GCMAccountTrackerTest::GCMAccountTrackerTest() { | |
194 fake_token_service_.reset(new FakeOAuth2TokenService()); | |
195 | |
196 fake_identity_provider_.reset( | |
197 new FakeIdentityProvider(fake_token_service_.get())); | |
198 | |
199 scoped_ptr<gaia::AccountTracker> gaia_account_tracker( | |
200 new gaia::AccountTracker( | |
201 fake_identity_provider_.get(), | |
202 new net::TestURLRequestContextGetter(message_loop_.task_runner()))); | |
203 | |
204 tracker_.reset(new GCMAccountTracker(gaia_account_tracker.Pass(), &driver_)); | |
205 } | |
206 | |
207 GCMAccountTrackerTest::~GCMAccountTrackerTest() { | |
208 if (tracker_) | |
209 tracker_->Shutdown(); | |
210 } | |
211 | |
212 void GCMAccountTrackerTest::StartAccountSignIn(const std::string& account_key) { | |
213 fake_identity_provider_->LogIn(account_key); | |
214 fake_token_service_->AddAccount(account_key); | |
215 } | |
216 | |
217 void GCMAccountTrackerTest::FinishAccountSignIn( | |
218 const std::string& account_key) { | |
219 IssueAccessToken(account_key); | |
220 | |
221 net::TestURLFetcher* fetcher = test_fetcher_factory_.GetFetcherByID( | |
222 gaia::GaiaOAuthClient::kUrlFetcherId); | |
223 ASSERT_TRUE(fetcher); | |
224 fetcher->set_response_code(net::HTTP_OK); | |
225 fetcher->SetResponseString(GetValidTokenInfoResponse(account_key)); | |
226 fetcher->delegate()->OnURLFetchComplete(fetcher); | |
227 } | |
228 | |
229 void GCMAccountTrackerTest::SignInAccount(const std::string& account_key) { | |
230 StartAccountSignIn(account_key); | |
231 FinishAccountSignIn(account_key); | |
232 } | |
233 | |
234 void GCMAccountTrackerTest::SignOutAccount(const std::string& account_key) { | |
235 fake_token_service_->RemoveAccount(account_key); | |
236 } | |
237 | |
238 void GCMAccountTrackerTest::IssueAccessToken(const std::string& account_key) { | |
239 fake_token_service_->IssueAllTokensForAccount( | |
240 account_key, MakeAccessToken(account_key), base::Time::Max()); | |
241 } | |
242 | |
243 void GCMAccountTrackerTest::IssueExpiredAccessToken( | |
244 const std::string& account_key) { | |
245 fake_token_service_->IssueAllTokensForAccount( | |
246 account_key, MakeAccessToken(account_key), base::Time::Now()); | |
247 } | |
248 | |
249 void GCMAccountTrackerTest::IssueError(const std::string& account_key) { | |
250 fake_token_service_->IssueErrorForAllPendingRequestsForAccount( | |
251 account_key, | |
252 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE)); | |
253 } | |
254 | |
255 bool GCMAccountTrackerTest::IsFetchingRequired() const { | |
256 return tracker_->IsTokenFetchingRequired(); | |
257 } | |
258 | |
259 bool GCMAccountTrackerTest::IsTokenReportingRequired() const { | |
260 return tracker_->IsTokenReportingRequired(); | |
261 } | |
262 | |
263 base::TimeDelta GCMAccountTrackerTest::GetTimeToNextTokenReporting() const { | |
264 return tracker_->GetTimeToNextTokenReporting(); | |
265 } | |
266 | |
267 TEST_F(GCMAccountTrackerTest, NoAccounts) { | |
268 EXPECT_FALSE(driver()->update_accounts_called()); | |
269 tracker()->Start(); | |
270 // Callback should not be called if there where no accounts provided. | |
271 EXPECT_FALSE(driver()->update_accounts_called()); | |
272 EXPECT_TRUE(driver()->accounts().empty()); | |
273 } | |
274 | |
275 // Verifies that callback is called after a token is issued for a single account | |
276 // with a specific scope. In this scenario, the underlying account tracker is | |
277 // still working when the CompleteCollectingTokens is called for the first time. | |
278 TEST_F(GCMAccountTrackerTest, SingleAccount) { | |
279 StartAccountSignIn(kAccountId1); | |
280 | |
281 tracker()->Start(); | |
282 // We don't have any accounts to report, but given the inner account tracker | |
283 // is still working we don't make a call with empty accounts list. | |
284 EXPECT_FALSE(driver()->update_accounts_called()); | |
285 | |
286 // This concludes the work of inner account tracker. | |
287 FinishAccountSignIn(kAccountId1); | |
288 IssueAccessToken(kAccountId1); | |
289 | |
290 EXPECT_TRUE(driver()->update_accounts_called()); | |
291 | |
292 std::vector<GCMClient::AccountTokenInfo> expected_accounts; | |
293 expected_accounts.push_back(MakeAccountToken(kAccountId1)); | |
294 VerifyAccountTokens(expected_accounts, driver()->accounts()); | |
295 } | |
296 | |
297 TEST_F(GCMAccountTrackerTest, MultipleAccounts) { | |
298 StartAccountSignIn(kAccountId1); | |
299 StartAccountSignIn(kAccountId2); | |
300 | |
301 tracker()->Start(); | |
302 EXPECT_FALSE(driver()->update_accounts_called()); | |
303 | |
304 FinishAccountSignIn(kAccountId1); | |
305 IssueAccessToken(kAccountId1); | |
306 EXPECT_FALSE(driver()->update_accounts_called()); | |
307 | |
308 FinishAccountSignIn(kAccountId2); | |
309 IssueAccessToken(kAccountId2); | |
310 EXPECT_TRUE(driver()->update_accounts_called()); | |
311 | |
312 std::vector<GCMClient::AccountTokenInfo> expected_accounts; | |
313 expected_accounts.push_back(MakeAccountToken(kAccountId1)); | |
314 expected_accounts.push_back(MakeAccountToken(kAccountId2)); | |
315 VerifyAccountTokens(expected_accounts, driver()->accounts()); | |
316 } | |
317 | |
318 TEST_F(GCMAccountTrackerTest, AccountAdded) { | |
319 tracker()->Start(); | |
320 driver()->ResetResults(); | |
321 | |
322 SignInAccount(kAccountId1); | |
323 EXPECT_FALSE(driver()->update_accounts_called()); | |
324 | |
325 IssueAccessToken(kAccountId1); | |
326 EXPECT_TRUE(driver()->update_accounts_called()); | |
327 | |
328 std::vector<GCMClient::AccountTokenInfo> expected_accounts; | |
329 expected_accounts.push_back(MakeAccountToken(kAccountId1)); | |
330 VerifyAccountTokens(expected_accounts, driver()->accounts()); | |
331 } | |
332 | |
333 TEST_F(GCMAccountTrackerTest, AccountRemoved) { | |
334 SignInAccount(kAccountId1); | |
335 SignInAccount(kAccountId2); | |
336 | |
337 tracker()->Start(); | |
338 IssueAccessToken(kAccountId1); | |
339 IssueAccessToken(kAccountId2); | |
340 EXPECT_TRUE(driver()->update_accounts_called()); | |
341 | |
342 driver()->ResetResults(); | |
343 EXPECT_FALSE(driver()->update_accounts_called()); | |
344 | |
345 SignOutAccount(kAccountId2); | |
346 EXPECT_TRUE(driver()->update_accounts_called()); | |
347 | |
348 std::vector<GCMClient::AccountTokenInfo> expected_accounts; | |
349 expected_accounts.push_back(MakeAccountToken(kAccountId1)); | |
350 VerifyAccountTokens(expected_accounts, driver()->accounts()); | |
351 } | |
352 | |
353 TEST_F(GCMAccountTrackerTest, GetTokenFailed) { | |
354 SignInAccount(kAccountId1); | |
355 SignInAccount(kAccountId2); | |
356 | |
357 tracker()->Start(); | |
358 IssueAccessToken(kAccountId1); | |
359 EXPECT_FALSE(driver()->update_accounts_called()); | |
360 | |
361 IssueError(kAccountId2); | |
362 | |
363 // Failed token is not retried any more. Account marked as removed. | |
364 EXPECT_EQ(0UL, tracker()->get_pending_token_request_count()); | |
365 EXPECT_TRUE(driver()->update_accounts_called()); | |
366 | |
367 std::vector<GCMClient::AccountTokenInfo> expected_accounts; | |
368 expected_accounts.push_back(MakeAccountToken(kAccountId1)); | |
369 VerifyAccountTokens(expected_accounts, driver()->accounts()); | |
370 } | |
371 | |
372 TEST_F(GCMAccountTrackerTest, GetTokenFailedAccountRemoved) { | |
373 SignInAccount(kAccountId1); | |
374 SignInAccount(kAccountId2); | |
375 | |
376 tracker()->Start(); | |
377 IssueAccessToken(kAccountId1); | |
378 | |
379 driver()->ResetResults(); | |
380 SignOutAccount(kAccountId2); | |
381 IssueError(kAccountId2); | |
382 | |
383 EXPECT_TRUE(driver()->update_accounts_called()); | |
384 | |
385 std::vector<GCMClient::AccountTokenInfo> expected_accounts; | |
386 expected_accounts.push_back(MakeAccountToken(kAccountId1)); | |
387 VerifyAccountTokens(expected_accounts, driver()->accounts()); | |
388 } | |
389 | |
390 TEST_F(GCMAccountTrackerTest, AccountRemovedWhileRequestsPending) { | |
391 SignInAccount(kAccountId1); | |
392 SignInAccount(kAccountId2); | |
393 | |
394 tracker()->Start(); | |
395 IssueAccessToken(kAccountId1); | |
396 EXPECT_FALSE(driver()->update_accounts_called()); | |
397 | |
398 SignOutAccount(kAccountId2); | |
399 IssueAccessToken(kAccountId2); | |
400 EXPECT_TRUE(driver()->update_accounts_called()); | |
401 | |
402 std::vector<GCMClient::AccountTokenInfo> expected_accounts; | |
403 expected_accounts.push_back(MakeAccountToken(kAccountId1)); | |
404 VerifyAccountTokens(expected_accounts, driver()->accounts()); | |
405 } | |
406 | |
407 // Makes sure that tracker observes GCM connection when running. | |
408 TEST_F(GCMAccountTrackerTest, TrackerObservesConnection) { | |
409 EXPECT_EQ(NULL, driver()->last_connection_observer()); | |
410 tracker()->Start(); | |
411 EXPECT_EQ(tracker(), driver()->last_connection_observer()); | |
412 tracker()->Shutdown(); | |
413 EXPECT_EQ(tracker(), driver()->last_removed_connection_observer()); | |
414 } | |
415 | |
416 // Makes sure that token fetching happens only after connection is established. | |
417 TEST_F(GCMAccountTrackerTest, PostponeTokenFetchingUntilConnected) { | |
418 driver()->SetConnected(false); | |
419 StartAccountSignIn(kAccountId1); | |
420 tracker()->Start(); | |
421 FinishAccountSignIn(kAccountId1); | |
422 | |
423 EXPECT_EQ(0UL, tracker()->get_pending_token_request_count()); | |
424 driver()->SetConnected(true); | |
425 | |
426 EXPECT_EQ(1UL, tracker()->get_pending_token_request_count()); | |
427 } | |
428 | |
429 TEST_F(GCMAccountTrackerTest, InvalidateExpiredTokens) { | |
430 StartAccountSignIn(kAccountId1); | |
431 StartAccountSignIn(kAccountId2); | |
432 tracker()->Start(); | |
433 FinishAccountSignIn(kAccountId1); | |
434 FinishAccountSignIn(kAccountId2); | |
435 | |
436 EXPECT_EQ(2UL, tracker()->get_pending_token_request_count()); | |
437 | |
438 IssueExpiredAccessToken(kAccountId1); | |
439 IssueAccessToken(kAccountId2); | |
440 // Because the first token is expired, we expect the sanitize to kick in and | |
441 // clean it up before the SetAccessToken is called. This also means a new | |
442 // token request will be issued | |
443 EXPECT_FALSE(driver()->update_accounts_called()); | |
444 EXPECT_EQ(1UL, tracker()->get_pending_token_request_count()); | |
445 } | |
446 | |
447 // Testing for whether there are still more tokens to be fetched. Typically the | |
448 // need for token fetching triggers immediate request, unless there is no | |
449 // connection, that is why connection is set on and off in this test. | |
450 TEST_F(GCMAccountTrackerTest, IsTokenFetchingRequired) { | |
451 tracker()->Start(); | |
452 driver()->SetConnected(false); | |
453 EXPECT_FALSE(IsFetchingRequired()); | |
454 StartAccountSignIn(kAccountId1); | |
455 FinishAccountSignIn(kAccountId1); | |
456 EXPECT_TRUE(IsFetchingRequired()); | |
457 | |
458 driver()->SetConnected(true); | |
459 EXPECT_FALSE(IsFetchingRequired()); // Indicates that fetching has started. | |
460 IssueAccessToken(kAccountId1); | |
461 EXPECT_FALSE(IsFetchingRequired()); | |
462 | |
463 driver()->SetConnected(false); | |
464 StartAccountSignIn(kAccountId2); | |
465 FinishAccountSignIn(kAccountId2); | |
466 EXPECT_TRUE(IsFetchingRequired()); | |
467 | |
468 IssueExpiredAccessToken(kAccountId2); | |
469 // Make sure that if the token was expired it is still needed. | |
470 EXPECT_TRUE(IsFetchingRequired()); | |
471 } | |
472 | |
473 // Tests what is the expected time to the next token fetching. | |
474 TEST_F(GCMAccountTrackerTest, GetTimeToNextTokenReporting) { | |
475 tracker()->Start(); | |
476 // At this point the last token fetch time is never. | |
477 EXPECT_EQ(base::TimeDelta(), GetTimeToNextTokenReporting()); | |
478 | |
479 // Regular case. The tokens have been just reported. | |
480 driver()->SetLastTokenFetchTime(base::Time::Now()); | |
481 EXPECT_TRUE(GetTimeToNextTokenReporting() <= | |
482 base::TimeDelta::FromSeconds(12 * 60 * 60)); | |
483 | |
484 // A case when gcm driver is not yet initialized. | |
485 driver()->SetLastTokenFetchTime(base::Time::Max()); | |
486 EXPECT_EQ(base::TimeDelta::FromSeconds(12 * 60 * 60), | |
487 GetTimeToNextTokenReporting()); | |
488 | |
489 // A case when token reporting calculation is expected to result in more than | |
490 // 12 hours, in which case we expect exactly 12 hours. | |
491 driver()->SetLastTokenFetchTime(base::Time::Now() + | |
492 base::TimeDelta::FromDays(2)); | |
493 EXPECT_EQ(base::TimeDelta::FromSeconds(12 * 60 * 60), | |
494 GetTimeToNextTokenReporting()); | |
495 } | |
496 | |
497 // Tests conditions when token reporting is required. | |
498 TEST_F(GCMAccountTrackerTest, IsTokenReportingRequired) { | |
499 tracker()->Start(); | |
500 // Required because it is overdue. | |
501 EXPECT_TRUE(IsTokenReportingRequired()); | |
502 | |
503 // Not required because it just happened. | |
504 driver()->SetLastTokenFetchTime(base::Time::Now()); | |
505 EXPECT_FALSE(IsTokenReportingRequired()); | |
506 | |
507 SignInAccount(kAccountId1); | |
508 IssueAccessToken(kAccountId1); | |
509 driver()->ResetResults(); | |
510 // Reporting was triggered, which means testing for required will give false, | |
511 // but we have the update call. | |
512 SignOutAccount(kAccountId1); | |
513 EXPECT_TRUE(driver()->update_accounts_called()); | |
514 EXPECT_FALSE(IsTokenReportingRequired()); | |
515 } | |
516 | |
517 // TODO(fgorski): Add test for adding account after removal >> make sure it does | |
518 // not mark removal. | |
519 | |
520 } // namespace gcm | |
OLD | NEW |