blob: b371d9289816f973d3a4333307c1ddf3dfb5c46f [file] [log] [blame]
// Copyright (c) 2012 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/signin/token_service_unittest.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/run_loop.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/signin/fake_profile_oauth2_token_service.h"
#include "chrome/browser/signin/fake_signin_manager.h"
#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/signin/token_service_factory.h"
#include "chrome/browser/webdata/token_web_data.h"
#include "chrome/common/chrome_switches.h"
#include "components/webdata/encryptor/encryptor.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/mock_url_fetcher_factory.h"
#include "net/url_request/test_url_fetcher_factory.h"
TokenAvailableTracker::TokenAvailableTracker() {}
TokenAvailableTracker::~TokenAvailableTracker() {}
void TokenAvailableTracker::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
TestNotificationTracker::Observe(type, source, details);
if (type == chrome::NOTIFICATION_TOKEN_AVAILABLE) {
content::Details<const TokenService::TokenAvailableDetails> full = details;
details_ = *full.ptr();
}
}
TokenFailedTracker::TokenFailedTracker() {}
TokenFailedTracker::~TokenFailedTracker() {}
void TokenFailedTracker::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
TestNotificationTracker::Observe(type, source, details);
if (type == chrome::NOTIFICATION_TOKEN_REQUEST_FAILED) {
content::Details<const TokenService::TokenRequestFailedDetails> full =
details;
details_ = *full.ptr();
}
}
TokenServiceTestHarness::TokenServiceTestHarness()
: signin_manager_(NULL), service_(NULL) {
}
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";
oauth_token_ = "oauth";
oauth_secret_ = "secret";
profile_ = CreateProfile().Pass();
profile_->CreateWebDataService();
// Force the loading of the WebDataService.
TokenWebData::FromBrowserContext(profile_.get());
base::RunLoop().RunUntilIdle();
service_ = TokenServiceFactory::GetForProfile(profile_.get());
success_tracker_.ListenFor(chrome::NOTIFICATION_TOKEN_AVAILABLE,
content::Source<TokenService>(service_));
failure_tracker_.ListenFor(chrome::NOTIFICATION_TOKEN_REQUEST_FAILED,
content::Source<TokenService>(service_));
service()->Initialize("test", profile_.get());
}
scoped_ptr<TestingProfile> TokenServiceTestHarness::CreateProfile() {
return make_scoped_ptr(new TestingProfile());
}
void TokenServiceTestHarness::CreateSigninManager(const std::string& username) {
signin_manager_ =
static_cast<FakeSigninManagerBase*>(
SigninManagerFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), FakeSigninManagerBase::Build));
signin_manager_->Initialize(profile(), NULL);
signin_manager_->SetAuthenticatedUsername(username);
}
void TokenServiceTestHarness::TearDown() {
// You have to destroy the profile before the threads are shut down.
profile_.reset();
}
void TokenServiceTestHarness::UpdateCredentialsOnService() {
service()->UpdateCredentials(credentials_);
}
class TokenServiceTest : public TokenServiceTestHarness {
public:
virtual void SetUp() OVERRIDE {
TokenServiceTestHarness::SetUp();
UpdateCredentialsOnService();
}
virtual scoped_ptr<TestingProfile> CreateProfile() OVERRIDE;
protected:
void TestLoadSingleToken(
std::map<std::string, std::string>* db_tokens,
std::map<std::string, std::string>* memory_tokens,
const std::string& service_name) {
std::string token = service_name + "_token";
(*db_tokens)[service_name] = token;
size_t prev_success_size = success_tracker()->size();
service()->LoadTokensIntoMemory(*db_tokens, memory_tokens);
// Check notification.
EXPECT_EQ(prev_success_size + 1, success_tracker()->size());
TokenService::TokenAvailableDetails details = success_tracker()->details();
EXPECT_EQ(details.service(), service_name);
EXPECT_EQ(details.token(), token);
// Check memory tokens.
EXPECT_EQ(1U, memory_tokens->count(service_name));
EXPECT_EQ((*memory_tokens)[service_name], token);
}
};
scoped_ptr<TestingProfile> TokenServiceTest::CreateProfile() {
TestingProfile::Builder builder;
builder.AddTestingFactory(ProfileOAuth2TokenServiceFactory::GetInstance(),
FakeProfileOAuth2TokenService::Build);
return builder.Build();
}
TEST_F(TokenServiceTest, SanityCheck) {
EXPECT_FALSE(service()->HasTokenForService("nonexistent service"));
EXPECT_FALSE(service()->TokensLoadedFromDB());
}
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, NotificationOAuthLoginTokenSuccess) {
EXPECT_EQ(0U, success_tracker()->size());
EXPECT_EQ(0U, failure_tracker()->size());
CreateSigninManager("test@gmail.com");
service()->OnClientOAuthSuccess(
GaiaAuthConsumer::ClientOAuthResult("rt1", "at1", 3600));
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::kGaiaOAuth2LoginRefreshToken);
EXPECT_EQ(details.token(), "rt1");
}
TEST_F(TokenServiceTest, NotificationFailed) {
EXPECT_EQ(0U, success_tracker()->size());
EXPECT_EQ(0U, failure_tracker()->size());
CreateSigninManager("test@gmail.com");
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, NotificationOAuthLoginTokenFailed) {
EXPECT_EQ(0U, success_tracker()->size());
EXPECT_EQ(0U, failure_tracker()->size());
CreateSigninManager("test@gmail.com");
GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
service()->OnClientOAuthFailure(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::kGaiaOAuth2LoginRefreshToken);
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,
std::string());
EXPECT_TRUE(service()->HasTokenForService(GaiaConstants::kSyncService));
EXPECT_EQ(service()->GetTokenForService(GaiaConstants::kSyncService), "");
}
TEST_F(TokenServiceTest, OnOAuth2LoginTokenSuccessUpdate) {
CreateSigninManager("test@gmail.com");
EXPECT_FALSE(service()->HasOAuthLoginToken());
service()->OnClientOAuthSuccess(
GaiaAuthConsumer::ClientOAuthResult("rt1", "at1", 3600));
EXPECT_TRUE(service()->HasOAuthLoginToken());
EXPECT_EQ(service()->GetOAuth2LoginRefreshToken(), "rt1");
service()->OnClientOAuthSuccess(
GaiaAuthConsumer::ClientOAuthResult("rt2", "at2", 3600));
EXPECT_TRUE(service()->HasOAuthLoginToken());
EXPECT_EQ(service()->GetOAuth2LoginRefreshToken(), "rt2");
service()->OnClientOAuthSuccess(
GaiaAuthConsumer::ClientOAuthResult("rt3", "at3", 3600));
EXPECT_TRUE(service()->HasOAuthLoginToken());
EXPECT_EQ(service()->GetOAuth2LoginRefreshToken(), "rt3");
}
TEST_F(TokenServiceTest, OnTokenSuccess) {
CreateSigninManager("test@gmail.com");
// Don't "start fetching", just go ahead and issue the callback.
service()->OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
EXPECT_TRUE(service()->HasTokenForService(GaiaConstants::kSyncService));
// 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");
}
TEST_F(TokenServiceTest, Reset) {
CreateSigninManager("test@gmail.com");
net::TestURLFetcherFactory 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");
service()->ResetCredentialsInMemory();
EXPECT_FALSE(service()->HasTokenForService(GaiaConstants::kSyncService));
// Now start using it again.
UpdateCredentialsOnService();
service()->StartFetchingTokens();
service()->OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
EXPECT_EQ(service()->GetTokenForService(GaiaConstants::kSyncService),
"token");
}
TEST_F(TokenServiceTest, FullIntegration) {
std::string result = "SID=sid\nLSID=lsid\nAuth=auth\n";
{
MockURLFetcherFactory<MockFetcher> factory;
factory.set_results(result);
EXPECT_FALSE(service()->HasTokenForService(GaiaConstants::kSyncService));
service()->StartFetchingTokens();
}
EXPECT_TRUE(service()->HasTokenForService(GaiaConstants::kSyncService));
// 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);
service()->ResetCredentialsInMemory();
EXPECT_FALSE(service()->HasTokenForService(GaiaConstants::kSyncService));
}
TEST_F(TokenServiceTest, LoadTokensIntoMemoryBasic) {
CreateSigninManager("test@gmail.com");
// 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;
EXPECT_FALSE(service()->TokensLoadedFromDB());
service()->LoadTokensIntoMemory(db_tokens, &memory_tokens);
EXPECT_TRUE(db_tokens.empty());
EXPECT_TRUE(memory_tokens.empty());
EXPECT_EQ(0U, success_tracker()->size());
std::vector<std::string> services;
TokenService::GetServiceNames(&services);
for (std::vector<std::string>::const_iterator i = services.begin();
i != services.end(); ++i) {
const std::string& service = *i;
TestLoadSingleToken(&db_tokens, &memory_tokens, service);
}
std::string service = GaiaConstants::kGaiaOAuth2LoginRefreshToken;
TestLoadSingleToken(&db_tokens, &memory_tokens, service);
}
TEST_F(TokenServiceTest, LoadTokensIntoMemoryAdvanced) {
CreateSigninManager("test@gmail.com");
// 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";
service()->LoadTokensIntoMemory(db_tokens, &memory_tokens);
EXPECT_EQ(1U, memory_tokens.size());
EXPECT_EQ(0U, success_tracker()->size());
EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kSyncService));
EXPECT_EQ(memory_tokens[GaiaConstants::kSyncService], "pepper");
}
TEST_F(TokenServiceTest, WebDBLoadIntegration) {
CreateSigninManager("test@gmail.com");
service()->LoadTokensFromDB();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(service()->TokensLoadedFromDB());
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();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1U, success_tracker()->size());
EXPECT_TRUE(service()->HasTokenForService(GaiaConstants::kSyncService));
}
TEST_F(TokenServiceTest, MultipleLoadResetIntegration) {
CreateSigninManager("test@gmail.com");
// Should result in DB write.
service()->OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
service()->ResetCredentialsInMemory();
success_tracker()->Reset();
EXPECT_FALSE(service()->HasTokenForService(GaiaConstants::kSyncService));
EXPECT_FALSE(service()->TokensLoadedFromDB());
service()->LoadTokensFromDB();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(service()->TokensLoadedFromDB());
service()->LoadTokensFromDB(); // Should do nothing.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(service()->TokensLoadedFromDB());
EXPECT_EQ(1U, success_tracker()->size());
EXPECT_TRUE(service()->HasTokenForService(GaiaConstants::kSyncService));
// Reset it one more time so there's no surprises.
service()->ResetCredentialsInMemory();
EXPECT_FALSE(service()->TokensLoadedFromDB());
success_tracker()->Reset();
service()->LoadTokensFromDB();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(service()->TokensLoadedFromDB());
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();
UpdateCredentialsOnService();
*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