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 17 TokenAvailableTracker::TokenAvailableTracker() {} 18 19 TokenAvailableTracker::~TokenAvailableTracker() {} 20 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 31 TokenFailedTracker::TokenFailedTracker() {} 32 33 TokenFailedTracker::~TokenFailedTracker() {} 34 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 45 TokenServiceTestHarness::TokenServiceTestHarness() 46 : ui_thread_(BrowserThread::UI, &message_loop_), 47 db_thread_(BrowserThread::DB) { 48 } 49 50 TokenServiceTestHarness::~TokenServiceTestHarness() {} 51 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 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 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: 103 virtual void SetUp() { 104 TokenServiceTestHarness::SetUp(); 105 service_.UpdateCredentials(credentials_); 106 } 107 }; 108 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 115 TEST_F(TokenServiceTest, NoToken) { 116 EXPECT_FALSE(service_.HasTokenForService("nonexistent service")); 117 EXPECT_EQ(service_.GetTokenForService("nonexistent service"), std::string()); 118 } 119 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 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 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 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 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 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 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 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 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 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 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: 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 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