// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This file defines a unit test for the profile's token service.
#include "chrome/browser/net/gaia/token_service_unittest.h"
#include "base/command_line.h"
#include "base/synchronization/waitable_event.h"
#include "chrome/browser/password_manager/encryptor.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/net/gaia/gaia_auth_fetcher_unittest.h"
#include "chrome/common/net/gaia/gaia_constants.h"
#include "chrome/common/net/test_url_fetcher_factory.h"
TokenAvailableTracker::TokenAvailableTracker() {}
TokenAvailableTracker::~TokenAvailableTracker() {}
void TokenAvailableTracker::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
TestNotificationTracker::Observe(type, source, details);
if (type == NotificationType::TOKEN_AVAILABLE) {
Details<const TokenService::TokenAvailableDetails> full = details;
details_ = *full.ptr();
}
}
TokenFailedTracker::TokenFailedTracker() {}
TokenFailedTracker::~TokenFailedTracker() {}
void TokenFailedTracker::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
TestNotificationTracker::Observe(type, source, details);
if (type == NotificationType::TOKEN_REQUEST_FAILED) {
Details<const TokenService::TokenRequestFailedDetails> full = details;
details_ = *full.ptr();
}
}
TokenServiceTestHarness::TokenServiceTestHarness()
: ui_thread_(BrowserThread::UI, &message_loop_),
db_thread_(BrowserThread::DB) {
}
TokenServiceTestHarness::~TokenServiceTestHarness() {}
void TokenServiceTestHarness::SetUp() {
#if defined(OS_MACOSX)
Encryptor::UseMockKeychain(true);
#endif
credentials_.sid = "sid";
credentials_.lsid = "lsid";
credentials_.token = "token";
credentials_.data = "data";
ASSERT_TRUE(db_thread_.Start());
profile_.reset(new TestingProfile());
profile_->CreateWebDataService(false);
WaitForDBLoadCompletion();
success_tracker_.ListenFor(NotificationType::TOKEN_AVAILABLE,
Source<TokenService>(&service_));
failure_tracker_.ListenFor(NotificationType::TOKEN_REQUEST_FAILED,
Source<TokenService>(&service_));
service_.Initialize("test", profile_.get());
URLFetcher::set_factory(NULL);
}
void TokenServiceTestHarness::TearDown() {
// You have to destroy the profile before the db_thread_ stops.
if (profile_.get()) {
profile_.reset(NULL);
}
db_thread_.Stop();
MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask);
MessageLoop::current()->Run();
}
void TokenServiceTestHarness::WaitForDBLoadCompletion() {
// The WebDB does all work on the DB thread. This will add an event
// to the end of the DB thread, so when we reach this task, all DB
// operations should be complete.
base::WaitableEvent done(false, false);
BrowserThread::PostTask(
BrowserThread::DB, FROM_HERE, new SignalingTask(&done));
done.Wait();
// Notifications should be returned from the DB thread onto the UI thread.
message_loop_.RunAllPending();
}
class TokenServiceTest : public TokenServiceTestHarness {
public:
virtual void SetUp() {
TokenServiceTestHarness::SetUp();
service_.UpdateCredentials(credentials_);
}
};
TEST_F(TokenServiceTest, SanityCheck) {
EXPECT_TRUE(service_.HasLsid());
EXPECT_EQ(service_.GetLsid(), "lsid");
EXPECT_FALSE(service_.HasTokenForService("nonexistent service"));
}
TEST_F(TokenServiceTest, NoToken) {
EXPECT_FALSE(service_.HasTokenForService("nonexistent service"));
EXPECT_EQ(service_.GetTokenForService("nonexistent service"), std::string());
}
TEST_F(TokenServiceTest, NotificationSuccess) {
EXPECT_EQ(0U, success_tracker_.size());
EXPECT_EQ(0U, failure_tracker_.size());
service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
EXPECT_EQ(1U, success_tracker_.size());
EXPECT_EQ(0U, failure_tracker_.size());
TokenService::TokenAvailableDetails details = success_tracker_.details();
// MSVC doesn't like this comparison as EQ.
EXPECT_TRUE(details.service() == GaiaConstants::kSyncService);
EXPECT_EQ(details.token(), "token");
}
TEST_F(TokenServiceTest, NotificationFailed) {
EXPECT_EQ(0U, success_tracker_.size());
EXPECT_EQ(0U, failure_tracker_.size());
GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
service_.OnIssueAuthTokenFailure(GaiaConstants::kSyncService, error);
EXPECT_EQ(0U, success_tracker_.size());
EXPECT_EQ(1U, failure_tracker_.size());
TokenService::TokenRequestFailedDetails details = failure_tracker_.details();
// MSVC doesn't like this comparison as EQ.
EXPECT_TRUE(details.service() == GaiaConstants::kSyncService);
EXPECT_TRUE(details.error() == error); // Struct has no print function.
}
TEST_F(TokenServiceTest, OnTokenSuccessUpdate) {
service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token");
service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token2");
EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token2");
service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "");
EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "");
}
TEST_F(TokenServiceTest, OnTokenSuccess) {
// Don't "start fetching", just go ahead and issue the callback.
service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
// Gaia returns the entire result as the token so while this is a shared
// result with ClientLogin, it doesn't matter, we should still get it back.
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token");
// Check the second service.
service_.OnIssueAuthTokenSuccess(GaiaConstants::kTalkService, "token2");
EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kTalkService));
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kTalkService), "token2");
// It didn't change.
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token");
}
TEST_F(TokenServiceTest, ResetSimple) {
service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
EXPECT_TRUE(service_.HasLsid());
service_.ResetCredentialsInMemory();
EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
EXPECT_FALSE(service_.HasLsid());
}
TEST_F(TokenServiceTest, ResetComplex) {
TestURLFetcherFactory factory;
URLFetcher::set_factory(&factory);
service_.StartFetchingTokens();
// You have to call delegates by hand with the test fetcher,
// Let's pretend only one returned.
service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "eraseme");
EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService),
"eraseme");
EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
service_.ResetCredentialsInMemory();
EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
// Now start using it again.
service_.UpdateCredentials(credentials_);
service_.StartFetchingTokens();
service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
service_.OnIssueAuthTokenSuccess(GaiaConstants::kTalkService, "token2");
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token");
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kTalkService), "token2");
}
TEST_F(TokenServiceTest, FullIntegration) {
MockFactory<MockFetcher> factory;
std::string result = "SID=sid\nLSID=lsid\nAuth=auth\n";
factory.set_results(result);
URLFetcher::set_factory(&factory);
EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
service_.StartFetchingTokens();
URLFetcher::set_factory(NULL);
EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kTalkService));
// Gaia returns the entire result as the token so while this is a shared
// result with ClientLogin, it doesn't matter, we should still get it back.
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), result);
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kTalkService), result);
service_.ResetCredentialsInMemory();
EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
}
TEST_F(TokenServiceTest, LoadTokensIntoMemoryBasic) {
// Validate that the method sets proper data in notifications and map.
std::map<std::string, std::string> db_tokens;
std::map<std::string, std::string> memory_tokens;
service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
EXPECT_TRUE(db_tokens.empty());
EXPECT_TRUE(memory_tokens.empty());
EXPECT_EQ(0U, success_tracker_.size());
db_tokens[GaiaConstants::kSyncService] = "token";
service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
EXPECT_EQ(1U, success_tracker_.size());
TokenService::TokenAvailableDetails details = success_tracker_.details();
// MSVC doesn't like this comparison as EQ.
EXPECT_TRUE(details.service() == GaiaConstants::kSyncService);
EXPECT_EQ(details.token(), "token");
EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kSyncService));
EXPECT_EQ(memory_tokens[GaiaConstants::kSyncService], "token");
}
TEST_F(TokenServiceTest, LoadTokensIntoMemoryAdvanced) {
// LoadTokensIntoMemory should avoid setting tokens already in the
// token map.
std::map<std::string, std::string> db_tokens;
std::map<std::string, std::string> memory_tokens;
db_tokens["ignore"] = "token";
service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
EXPECT_TRUE(memory_tokens.empty());
db_tokens[GaiaConstants::kSyncService] = "pepper";
service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kSyncService));
EXPECT_EQ(memory_tokens[GaiaConstants::kSyncService], "pepper");
EXPECT_EQ(1U, success_tracker_.size());
success_tracker_.Reset();
// SyncService token is already in memory. Pretend we got it off
// the disk as well, but an older token.
db_tokens[GaiaConstants::kSyncService] = "ignoreme";
db_tokens[GaiaConstants::kTalkService] = "tomato";
service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
EXPECT_EQ(2U, memory_tokens.size());
EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kTalkService));
EXPECT_EQ(memory_tokens[GaiaConstants::kTalkService], "tomato");
EXPECT_EQ(1U, success_tracker_.size());
EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kSyncService));
EXPECT_EQ(memory_tokens[GaiaConstants::kSyncService], "pepper");
}
TEST_F(TokenServiceTest, WebDBLoadIntegration) {
service_.LoadTokensFromDB();
WaitForDBLoadCompletion();
EXPECT_EQ(0U, success_tracker_.size());
// Should result in DB write.
service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
EXPECT_EQ(1U, success_tracker_.size());
EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
// Clean slate.
service_.ResetCredentialsInMemory();
success_tracker_.Reset();
EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
service_.LoadTokensFromDB();
WaitForDBLoadCompletion();
EXPECT_EQ(1U, success_tracker_.size());
EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
EXPECT_FALSE(service_.HasLsid());
}
TEST_F(TokenServiceTest, MultipleLoadResetIntegration) {
// Should result in DB write.
service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
service_.ResetCredentialsInMemory();
success_tracker_.Reset();
EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
service_.LoadTokensFromDB();
WaitForDBLoadCompletion();
service_.LoadTokensFromDB(); // Should do nothing.
WaitForDBLoadCompletion();
EXPECT_EQ(1U, success_tracker_.size());
EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
EXPECT_FALSE(service_.HasLsid());
// Reset it one more time so there's no surprises.
service_.ResetCredentialsInMemory();
success_tracker_.Reset();
service_.LoadTokensFromDB();
WaitForDBLoadCompletion();
EXPECT_EQ(1U, success_tracker_.size());
EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
}
#ifndef NDEBUG
class TokenServiceCommandLineTest : public TokenServiceTestHarness {
public:
virtual void SetUp() {
CommandLine original_cl(*CommandLine::ForCurrentProcess());
CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kSetToken, "my_service:my_value");
TokenServiceTestHarness::SetUp();
service_.UpdateCredentials(credentials_);
*CommandLine::ForCurrentProcess() = original_cl;
}
};
TEST_F(TokenServiceCommandLineTest, TestValueOverride) {
EXPECT_TRUE(service_.HasTokenForService("my_service"));
EXPECT_EQ("my_value", service_.GetTokenForService("my_service"));
}
#endif // ifndef NDEBUG