1 // Copyright (c) 2010 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 // This file defines a unit test for the profile's token service.
6
7 #include "chrome/browser/net/gaia/token_service_unittest.h"
8
9 #include "base/command_line.h"
10 #include "base/synchronization/waitable_event.h"
11 #include "chrome/browser/password_manager/encryptor.h"
12 #include "chrome/common/chrome_switches.h"
13 #include "chrome/common/net/gaia/gaia_auth_fetcher_unittest.h"
14 #include "chrome/common/net/gaia/gaia_constants.h"
15 #include "chrome/common/net/test_url_fetcher_factory.h"
16
TokenAvailableTracker()17 TokenAvailableTracker::TokenAvailableTracker() {}
18
~TokenAvailableTracker()19 TokenAvailableTracker::~TokenAvailableTracker() {}
20
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)21 void TokenAvailableTracker::Observe(NotificationType type,
22 const NotificationSource& source,
23 const NotificationDetails& details) {
24 TestNotificationTracker::Observe(type, source, details);
25 if (type == NotificationType::TOKEN_AVAILABLE) {
26 Details<const TokenService::TokenAvailableDetails> full = details;
27 details_ = *full.ptr();
28 }
29 }
30
TokenFailedTracker()31 TokenFailedTracker::TokenFailedTracker() {}
32
~TokenFailedTracker()33 TokenFailedTracker::~TokenFailedTracker() {}
34
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)35 void TokenFailedTracker::Observe(NotificationType type,
36 const NotificationSource& source,
37 const NotificationDetails& details) {
38 TestNotificationTracker::Observe(type, source, details);
39 if (type == NotificationType::TOKEN_REQUEST_FAILED) {
40 Details<const TokenService::TokenRequestFailedDetails> full = details;
41 details_ = *full.ptr();
42 }
43 }
44
TokenServiceTestHarness()45 TokenServiceTestHarness::TokenServiceTestHarness()
46 : ui_thread_(BrowserThread::UI, &message_loop_),
47 db_thread_(BrowserThread::DB) {
48 }
49
~TokenServiceTestHarness()50 TokenServiceTestHarness::~TokenServiceTestHarness() {}
51
SetUp()52 void TokenServiceTestHarness::SetUp() {
53 #if defined(OS_MACOSX)
54 Encryptor::UseMockKeychain(true);
55 #endif
56 credentials_.sid = "sid";
57 credentials_.lsid = "lsid";
58 credentials_.token = "token";
59 credentials_.data = "data";
60
61 ASSERT_TRUE(db_thread_.Start());
62
63 profile_.reset(new TestingProfile());
64 profile_->CreateWebDataService(false);
65 WaitForDBLoadCompletion();
66
67 success_tracker_.ListenFor(NotificationType::TOKEN_AVAILABLE,
68 Source<TokenService>(&service_));
69 failure_tracker_.ListenFor(NotificationType::TOKEN_REQUEST_FAILED,
70 Source<TokenService>(&service_));
71
72 service_.Initialize("test", profile_.get());
73
74 URLFetcher::set_factory(NULL);
75 }
76
TearDown()77 void TokenServiceTestHarness::TearDown() {
78 // You have to destroy the profile before the db_thread_ stops.
79 if (profile_.get()) {
80 profile_.reset(NULL);
81 }
82
83 db_thread_.Stop();
84 MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask);
85 MessageLoop::current()->Run();
86 }
87
WaitForDBLoadCompletion()88 void TokenServiceTestHarness::WaitForDBLoadCompletion() {
89 // The WebDB does all work on the DB thread. This will add an event
90 // to the end of the DB thread, so when we reach this task, all DB
91 // operations should be complete.
92 base::WaitableEvent done(false, false);
93 BrowserThread::PostTask(
94 BrowserThread::DB, FROM_HERE, new SignalingTask(&done));
95 done.Wait();
96
97 // Notifications should be returned from the DB thread onto the UI thread.
98 message_loop_.RunAllPending();
99 }
100
101 class TokenServiceTest : public TokenServiceTestHarness {
102 public:
SetUp()103 virtual void SetUp() {
104 TokenServiceTestHarness::SetUp();
105 service_.UpdateCredentials(credentials_);
106 }
107 };
108
TEST_F(TokenServiceTest,SanityCheck)109 TEST_F(TokenServiceTest, SanityCheck) {
110 EXPECT_TRUE(service_.HasLsid());
111 EXPECT_EQ(service_.GetLsid(), "lsid");
112 EXPECT_FALSE(service_.HasTokenForService("nonexistent service"));
113 }
114
TEST_F(TokenServiceTest,NoToken)115 TEST_F(TokenServiceTest, NoToken) {
116 EXPECT_FALSE(service_.HasTokenForService("nonexistent service"));
117 EXPECT_EQ(service_.GetTokenForService("nonexistent service"), std::string());
118 }
119
TEST_F(TokenServiceTest,NotificationSuccess)120 TEST_F(TokenServiceTest, NotificationSuccess) {
121 EXPECT_EQ(0U, success_tracker_.size());
122 EXPECT_EQ(0U, failure_tracker_.size());
123 service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
124 EXPECT_EQ(1U, success_tracker_.size());
125 EXPECT_EQ(0U, failure_tracker_.size());
126
127 TokenService::TokenAvailableDetails details = success_tracker_.details();
128 // MSVC doesn't like this comparison as EQ.
129 EXPECT_TRUE(details.service() == GaiaConstants::kSyncService);
130 EXPECT_EQ(details.token(), "token");
131 }
132
TEST_F(TokenServiceTest,NotificationFailed)133 TEST_F(TokenServiceTest, NotificationFailed) {
134 EXPECT_EQ(0U, success_tracker_.size());
135 EXPECT_EQ(0U, failure_tracker_.size());
136 GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
137 service_.OnIssueAuthTokenFailure(GaiaConstants::kSyncService, error);
138 EXPECT_EQ(0U, success_tracker_.size());
139 EXPECT_EQ(1U, failure_tracker_.size());
140
141 TokenService::TokenRequestFailedDetails details = failure_tracker_.details();
142
143 // MSVC doesn't like this comparison as EQ.
144 EXPECT_TRUE(details.service() == GaiaConstants::kSyncService);
145 EXPECT_TRUE(details.error() == error); // Struct has no print function.
146 }
147
TEST_F(TokenServiceTest,OnTokenSuccessUpdate)148 TEST_F(TokenServiceTest, OnTokenSuccessUpdate) {
149 service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
150 EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
151 EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token");
152
153 service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token2");
154 EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
155 EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token2");
156
157 service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "");
158 EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
159 EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "");
160 }
161
TEST_F(TokenServiceTest,OnTokenSuccess)162 TEST_F(TokenServiceTest, OnTokenSuccess) {
163 // Don't "start fetching", just go ahead and issue the callback.
164 service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
165 EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
166 EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
167 // Gaia returns the entire result as the token so while this is a shared
168 // result with ClientLogin, it doesn't matter, we should still get it back.
169 EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token");
170
171 // Check the second service.
172 service_.OnIssueAuthTokenSuccess(GaiaConstants::kTalkService, "token2");
173 EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kTalkService));
174 EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kTalkService), "token2");
175
176 // It didn't change.
177 EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token");
178 }
179
TEST_F(TokenServiceTest,ResetSimple)180 TEST_F(TokenServiceTest, ResetSimple) {
181 service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
182 EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
183 EXPECT_TRUE(service_.HasLsid());
184
185 service_.ResetCredentialsInMemory();
186
187 EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
188 EXPECT_FALSE(service_.HasLsid());
189 }
190
TEST_F(TokenServiceTest,ResetComplex)191 TEST_F(TokenServiceTest, ResetComplex) {
192 TestURLFetcherFactory factory;
193 URLFetcher::set_factory(&factory);
194 service_.StartFetchingTokens();
195 // You have to call delegates by hand with the test fetcher,
196 // Let's pretend only one returned.
197
198 service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "eraseme");
199 EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
200 EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService),
201 "eraseme");
202 EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
203
204 service_.ResetCredentialsInMemory();
205 EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
206 EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
207
208 // Now start using it again.
209 service_.UpdateCredentials(credentials_);
210 service_.StartFetchingTokens();
211
212 service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
213 service_.OnIssueAuthTokenSuccess(GaiaConstants::kTalkService, "token2");
214
215 EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token");
216 EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kTalkService), "token2");
217 }
218
TEST_F(TokenServiceTest,FullIntegration)219 TEST_F(TokenServiceTest, FullIntegration) {
220 MockFactory<MockFetcher> factory;
221 std::string result = "SID=sid\nLSID=lsid\nAuth=auth\n";
222 factory.set_results(result);
223 URLFetcher::set_factory(&factory);
224 EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
225 EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
226 service_.StartFetchingTokens();
227 URLFetcher::set_factory(NULL);
228
229 EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
230 EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kTalkService));
231 // Gaia returns the entire result as the token so while this is a shared
232 // result with ClientLogin, it doesn't matter, we should still get it back.
233 EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), result);
234 EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kTalkService), result);
235
236 service_.ResetCredentialsInMemory();
237 EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
238 EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
239 }
240
TEST_F(TokenServiceTest,LoadTokensIntoMemoryBasic)241 TEST_F(TokenServiceTest, LoadTokensIntoMemoryBasic) {
242 // Validate that the method sets proper data in notifications and map.
243 std::map<std::string, std::string> db_tokens;
244 std::map<std::string, std::string> memory_tokens;
245
246 service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
247 EXPECT_TRUE(db_tokens.empty());
248 EXPECT_TRUE(memory_tokens.empty());
249 EXPECT_EQ(0U, success_tracker_.size());
250
251 db_tokens[GaiaConstants::kSyncService] = "token";
252 service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
253 EXPECT_EQ(1U, success_tracker_.size());
254
255 TokenService::TokenAvailableDetails details = success_tracker_.details();
256 // MSVC doesn't like this comparison as EQ.
257 EXPECT_TRUE(details.service() == GaiaConstants::kSyncService);
258 EXPECT_EQ(details.token(), "token");
259 EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kSyncService));
260 EXPECT_EQ(memory_tokens[GaiaConstants::kSyncService], "token");
261 }
262
TEST_F(TokenServiceTest,LoadTokensIntoMemoryAdvanced)263 TEST_F(TokenServiceTest, LoadTokensIntoMemoryAdvanced) {
264 // LoadTokensIntoMemory should avoid setting tokens already in the
265 // token map.
266 std::map<std::string, std::string> db_tokens;
267 std::map<std::string, std::string> memory_tokens;
268
269 db_tokens["ignore"] = "token";
270
271 service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
272 EXPECT_TRUE(memory_tokens.empty());
273 db_tokens[GaiaConstants::kSyncService] = "pepper";
274
275 service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
276 EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kSyncService));
277 EXPECT_EQ(memory_tokens[GaiaConstants::kSyncService], "pepper");
278 EXPECT_EQ(1U, success_tracker_.size());
279 success_tracker_.Reset();
280
281 // SyncService token is already in memory. Pretend we got it off
282 // the disk as well, but an older token.
283 db_tokens[GaiaConstants::kSyncService] = "ignoreme";
284 db_tokens[GaiaConstants::kTalkService] = "tomato";
285 service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
286
287 EXPECT_EQ(2U, memory_tokens.size());
288 EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kTalkService));
289 EXPECT_EQ(memory_tokens[GaiaConstants::kTalkService], "tomato");
290 EXPECT_EQ(1U, success_tracker_.size());
291 EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kSyncService));
292 EXPECT_EQ(memory_tokens[GaiaConstants::kSyncService], "pepper");
293 }
294
TEST_F(TokenServiceTest,WebDBLoadIntegration)295 TEST_F(TokenServiceTest, WebDBLoadIntegration) {
296 service_.LoadTokensFromDB();
297 WaitForDBLoadCompletion();
298 EXPECT_EQ(0U, success_tracker_.size());
299
300 // Should result in DB write.
301 service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
302 EXPECT_EQ(1U, success_tracker_.size());
303
304 EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
305 // Clean slate.
306 service_.ResetCredentialsInMemory();
307 success_tracker_.Reset();
308 EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
309
310 service_.LoadTokensFromDB();
311 WaitForDBLoadCompletion();
312
313 EXPECT_EQ(1U, success_tracker_.size());
314 EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
315 EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
316 EXPECT_FALSE(service_.HasLsid());
317 }
318
TEST_F(TokenServiceTest,MultipleLoadResetIntegration)319 TEST_F(TokenServiceTest, MultipleLoadResetIntegration) {
320 // Should result in DB write.
321 service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
322 service_.ResetCredentialsInMemory();
323 success_tracker_.Reset();
324 EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
325
326 service_.LoadTokensFromDB();
327 WaitForDBLoadCompletion();
328
329 service_.LoadTokensFromDB(); // Should do nothing.
330 WaitForDBLoadCompletion();
331
332 EXPECT_EQ(1U, success_tracker_.size());
333 EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
334 EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
335 EXPECT_FALSE(service_.HasLsid());
336
337 // Reset it one more time so there's no surprises.
338 service_.ResetCredentialsInMemory();
339 success_tracker_.Reset();
340
341 service_.LoadTokensFromDB();
342 WaitForDBLoadCompletion();
343
344 EXPECT_EQ(1U, success_tracker_.size());
345 EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
346 }
347
348 #ifndef NDEBUG
349 class TokenServiceCommandLineTest : public TokenServiceTestHarness {
350 public:
SetUp()351 virtual void SetUp() {
352 CommandLine original_cl(*CommandLine::ForCurrentProcess());
353 CommandLine::ForCurrentProcess()->AppendSwitchASCII(
354 switches::kSetToken, "my_service:my_value");
355 TokenServiceTestHarness::SetUp();
356 service_.UpdateCredentials(credentials_);
357
358 *CommandLine::ForCurrentProcess() = original_cl;
359 }
360 };
361
TEST_F(TokenServiceCommandLineTest,TestValueOverride)362 TEST_F(TokenServiceCommandLineTest, TestValueOverride) {
363 EXPECT_TRUE(service_.HasTokenForService("my_service"));
364 EXPECT_EQ("my_value", service_.GetTokenForService("my_service"));
365 }
366 #endif // ifndef NDEBUG
367