// Copyright (c) 2011 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.

#include "chrome/browser/chromeos/login/google_authenticator.h"

#include <string>
#include <vector>

#include "base/file_path.h"
#include "base/file_util.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "chrome/browser/chromeos/cros/mock_cryptohome_library.h"
#include "chrome/browser/chromeos/cros/mock_library_loader.h"
#include "chrome/browser/chromeos/login/client_login_response_handler.h"
#include "chrome/browser/chromeos/login/issue_response_handler.h"
#include "chrome/browser/chromeos/login/mock_auth_response_handler.h"
#include "chrome/browser/chromeos/login/mock_login_status_consumer.h"
#include "chrome/browser/chromeos/login/mock_url_fetchers.h"
#include "chrome/browser/chromeos/login/mock_user_manager.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/net/gaia/gaia_auth_fetcher_unittest.h"
#include "chrome/common/net/url_fetcher.h"
#include "chrome/test/testing_profile.h"
#include "content/browser/browser_thread.h"
#include "googleurl/src/gurl.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_request_status.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using namespace file_util;
using ::testing::AnyNumber;
using ::testing::DoAll;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::SetArgumentPointee;
using ::testing::_;

namespace chromeos {

class GoogleAuthenticatorTest : public ::testing::Test {
 public:
  GoogleAuthenticatorTest()
      : message_loop_ui_(MessageLoop::TYPE_UI),
        ui_thread_(BrowserThread::UI, &message_loop_ui_),
        username_("me@nowhere.org"),
        password_("fakepass"),
        result_("", "", "", ""),
        bytes_as_ascii_("ffff"),
        user_manager_(new MockUserManager) {
    memset(fake_hash_, 0, sizeof(fake_hash_));
    fake_hash_[0] = 10;
    fake_hash_[1] = 1;
    fake_hash_[7] = 10 << 4;
    hash_ascii_.assign("0a010000000000a0");
    hash_ascii_.append(std::string(16, '0'));

    memset(raw_bytes_, 0xff, sizeof(raw_bytes_));
  }
  ~GoogleAuthenticatorTest() {}

  virtual void SetUp() {
    chromeos::CrosLibrary::TestApi* test_api =
        chromeos::CrosLibrary::Get()->GetTestApi();

    loader_ = new MockLibraryLoader();
    ON_CALL(*loader_, Load(_))
        .WillByDefault(Return(true));
    EXPECT_CALL(*loader_, Load(_))
        .Times(AnyNumber());

    test_api->SetLibraryLoader(loader_, true);

    mock_library_ = new MockCryptohomeLibrary();
    test_api->SetCryptohomeLibrary(mock_library_, true);
  }

  // Tears down the test fixture.
  virtual void TearDown() {
    // Prevent bogus gMock leak check from firing.
    chromeos::CrosLibrary::TestApi* test_api =
        chromeos::CrosLibrary::Get()->GetTestApi();
    test_api->SetLibraryLoader(NULL, false);
    test_api->SetCryptohomeLibrary(NULL, false);
  }

  FilePath PopulateTempFile(const char* data, int data_len) {
    FilePath out;
    FILE* tmp_file = CreateAndOpenTemporaryFile(&out);
    EXPECT_NE(tmp_file, static_cast<FILE*>(NULL));
    EXPECT_EQ(WriteFile(out, data, data_len), data_len);
    EXPECT_TRUE(CloseFile(tmp_file));
    return out;
  }

  FilePath FakeLocalaccountFile(const std::string& ascii) {
    FilePath exe_dir;
    FilePath local_account_file;
    PathService::Get(base::DIR_EXE, &exe_dir);
    FILE* tmp_file = CreateAndOpenTemporaryFileInDir(exe_dir,
                                                     &local_account_file);
    int ascii_len = ascii.length();
    EXPECT_NE(tmp_file, static_cast<FILE*>(NULL));
    EXPECT_EQ(WriteFile(local_account_file, ascii.c_str(), ascii_len),
              ascii_len);
    EXPECT_TRUE(CloseFile(tmp_file));
    return local_account_file;
  }

  void ReadLocalaccountFile(GoogleAuthenticator* auth,
                            const std::string& filename) {
    BrowserThread file_thread(BrowserThread::FILE);
    file_thread.Start();

    BrowserThread::PostTask(
        BrowserThread::FILE, FROM_HERE,
        NewRunnableMethod(auth,
                          &GoogleAuthenticator::LoadLocalaccount,
                          filename));
  }

  void PrepForLogin(GoogleAuthenticator* auth) {
    auth->set_password_hash(hash_ascii_);
    auth->set_username(username_);
    auth->set_password(password_);
    auth->SetLocalaccount("");
    auth->set_user_manager(user_manager_.get());
    ON_CALL(*user_manager_.get(), IsKnownUser(username_))
        .WillByDefault(Return(true));
  }

  void PrepForFailedLogin(GoogleAuthenticator* auth) {
    PrepForLogin(auth);
    auth->set_hosted_policy(GaiaAuthFetcher::HostedAccountsAllowed);
  }

  void CancelLogin(GoogleAuthenticator* auth) {
    BrowserThread::PostTask(
        BrowserThread::UI,
        FROM_HERE,
        NewRunnableMethod(auth,
                          &GoogleAuthenticator::CancelClientLogin));
  }

  MessageLoop message_loop_ui_;
  BrowserThread ui_thread_;

  unsigned char fake_hash_[32];
  std::string hash_ascii_;
  std::string username_;
  std::string password_;
  GaiaAuthConsumer::ClientLoginResult result_;
  // Mocks, destroyed by CrosLibrary class.
  MockCryptohomeLibrary* mock_library_;
  MockLibraryLoader* loader_;

  char raw_bytes_[2];
  std::string bytes_as_ascii_;

  scoped_ptr<MockUserManager> user_manager_;
};

TEST_F(GoogleAuthenticatorTest, SaltToAscii) {
  unsigned char fake_salt[8] = { 0 };
  fake_salt[0] = 10;
  fake_salt[1] = 1;
  fake_salt[7] = 10 << 4;
  std::vector<unsigned char> salt_v(fake_salt, fake_salt + sizeof(fake_salt));

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(NULL));

  ON_CALL(*mock_library_, GetSystemSalt())
      .WillByDefault(Return(salt_v));
  EXPECT_CALL(*mock_library_, GetSystemSalt())
      .Times(1)
      .RetiresOnSaturation();

  EXPECT_EQ("0a010000000000a0", auth->SaltAsAscii());
}

TEST_F(GoogleAuthenticatorTest, ReadLocalaccount) {
  FilePath tmp_file_path = FakeLocalaccountFile(bytes_as_ascii_);

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(NULL));
  ReadLocalaccountFile(auth.get(), tmp_file_path.BaseName().value());
  EXPECT_EQ(auth->localaccount_, bytes_as_ascii_);
  Delete(tmp_file_path, false);
}

TEST_F(GoogleAuthenticatorTest, ReadLocalaccountTrailingWS) {
  FilePath tmp_file_path =
      FakeLocalaccountFile(base::StringPrintf("%s\n",
                                              bytes_as_ascii_.c_str()));

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(NULL));
  ReadLocalaccountFile(auth.get(), tmp_file_path.BaseName().value());
  EXPECT_EQ(auth->localaccount_, bytes_as_ascii_);
  Delete(tmp_file_path, false);
}

TEST_F(GoogleAuthenticatorTest, ReadNoLocalaccount) {
  FilePath tmp_file_path = FakeLocalaccountFile(bytes_as_ascii_);
  EXPECT_TRUE(Delete(tmp_file_path, false));  // Ensure non-existent file.

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(NULL));
  ReadLocalaccountFile(auth.get(), tmp_file_path.BaseName().value());
  EXPECT_EQ(auth->localaccount_, std::string());
}

TEST_F(GoogleAuthenticatorTest, OnLoginSuccess) {
  MockConsumer consumer;
  EXPECT_CALL(consumer, OnLoginSuccess(username_, password_, _, false))
      .Times(1)
      .RetiresOnSaturation();

  EXPECT_CALL(*mock_library_, Mount(username_, hash_ascii_, _))
      .WillOnce(Return(true))
      .RetiresOnSaturation();

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  auth->set_password_hash(hash_ascii_);
  auth->set_username(username_);
  auth->set_password(password_);
  auth->OnLoginSuccess(result_, false);
}

TEST_F(GoogleAuthenticatorTest, MountFailure) {
  MockConsumer consumer;
  EXPECT_CALL(consumer, OnLoginFailure(_))
      .Times(1)
      .RetiresOnSaturation();

  EXPECT_CALL(*mock_library_, Mount(username_, hash_ascii_, _))
      .WillOnce(Return(false))
      .RetiresOnSaturation();

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  PrepForLogin(auth.get());
  auth->OnLoginSuccess(result_, false);
}

TEST_F(GoogleAuthenticatorTest, PasswordChange) {
  MockConsumer consumer;
  EXPECT_CALL(consumer, OnPasswordChangeDetected(result_))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(consumer, OnLoginSuccess(username_, password_, result_, false))
      .Times(1)
      .RetiresOnSaturation();

  EXPECT_CALL(*mock_library_, Mount(username_, hash_ascii_, _))
      .WillOnce(
          DoAll(SetArgumentPointee<2>(
              chromeos::kCryptohomeMountErrorKeyFailure),
                Return(false)))
      .WillOnce(Return(true))
      .RetiresOnSaturation();

  EXPECT_CALL(*mock_library_, GetSystemSalt())
      .WillOnce(Return(chromeos::CryptohomeBlob(8, 'a')))
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_library_, MigrateKey(username_, _, hash_ascii_))
      .WillOnce(Return(true))
      .RetiresOnSaturation();

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  PrepForLogin(auth.get());
  auth->OnLoginSuccess(result_, false);
  auth->RecoverEncryptedData("whaty", result_);
}

TEST_F(GoogleAuthenticatorTest, PasswordChangeWrongPassword) {
  MockConsumer consumer;
  EXPECT_CALL(consumer, OnPasswordChangeDetected(result_))
      .Times(2)
      .RetiresOnSaturation();

  EXPECT_CALL(*mock_library_, Mount(username_, hash_ascii_, _))
      .WillOnce(
          DoAll(SetArgumentPointee<2>(
              chromeos::kCryptohomeMountErrorKeyFailure),
                Return(false)))
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_library_, GetSystemSalt())
      .WillOnce(Return(chromeos::CryptohomeBlob(8, 'a')))
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_library_, MigrateKey(username_, _, hash_ascii_))
      .WillOnce(Return(false))
      .RetiresOnSaturation();

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  PrepForLogin(auth.get());
  auth->OnLoginSuccess(result_, false);
  auth->RecoverEncryptedData("whaty", result_);
}

TEST_F(GoogleAuthenticatorTest, ForgetOldData) {
  MockConsumer consumer;
  EXPECT_CALL(consumer, OnPasswordChangeDetected(result_))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(consumer, OnLoginSuccess(username_, password_, result_, false))
      .Times(1)
      .RetiresOnSaturation();

  EXPECT_CALL(*mock_library_, Mount(username_, hash_ascii_, _))
      .WillOnce(
          DoAll(SetArgumentPointee<2>(
              chromeos::kCryptohomeMountErrorKeyFailure),
                Return(false)))
      .WillOnce(Return(true))
      .RetiresOnSaturation();

  EXPECT_CALL(*mock_library_, Remove(username_))
      .WillOnce(Return(true))
      .RetiresOnSaturation();

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  PrepForLogin(auth.get());
  auth->OnLoginSuccess(result_, false);
  auth->ResyncEncryptedData(result_);
}

TEST_F(GoogleAuthenticatorTest, LoginNetFailure) {
  GoogleServiceAuthError error =
      GoogleServiceAuthError::FromConnectionError(net::ERR_CONNECTION_RESET);

  LoginFailure failure =
      LoginFailure::FromNetworkAuthFailure(error);

  MockConsumer consumer;
  EXPECT_CALL(consumer, OnLoginFailure(failure))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_library_, CheckKey(username_, hash_ascii_))
      .WillOnce(Return(false))
      .RetiresOnSaturation();

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  PrepForLogin(auth.get());
  auth->OnClientLoginFailure(error);
  message_loop_ui_.RunAllPending();
}

TEST_F(GoogleAuthenticatorTest, LoginDenied) {
  GoogleServiceAuthError client_error(
      GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);

  MockConsumer consumer;
  EXPECT_CALL(consumer, OnLoginFailure(_))
      .Times(1)
      .RetiresOnSaturation();

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  PrepForFailedLogin(auth.get());
  EXPECT_CALL(*user_manager_.get(), IsKnownUser(username_))
      .WillOnce(Return(true))
      .RetiresOnSaturation();
  auth->OnClientLoginFailure(client_error);
  message_loop_ui_.RunAllPending();
}

TEST_F(GoogleAuthenticatorTest, LoginAccountDisabled) {
  GoogleServiceAuthError client_error(
      GoogleServiceAuthError::ACCOUNT_DISABLED);

  MockConsumer consumer;
  EXPECT_CALL(consumer, OnLoginFailure(_))
      .Times(1)
      .RetiresOnSaturation();

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  PrepForFailedLogin(auth.get());
  auth->OnClientLoginFailure(client_error);
  message_loop_ui_.RunAllPending();
}

TEST_F(GoogleAuthenticatorTest, LoginAccountDeleted) {
  GoogleServiceAuthError client_error(
      GoogleServiceAuthError::ACCOUNT_DELETED);

  MockConsumer consumer;
  EXPECT_CALL(consumer, OnLoginFailure(_))
      .Times(1)
      .RetiresOnSaturation();

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  PrepForFailedLogin(auth.get());
  auth->OnClientLoginFailure(client_error);
  message_loop_ui_.RunAllPending();
}

TEST_F(GoogleAuthenticatorTest, LoginServiceUnavailable) {
  GoogleServiceAuthError client_error(
      GoogleServiceAuthError::SERVICE_UNAVAILABLE);

  MockConsumer consumer;
  EXPECT_CALL(consumer, OnLoginFailure(_))
      .Times(1)
      .RetiresOnSaturation();

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  PrepForFailedLogin(auth.get());
  auth->OnClientLoginFailure(client_error);
  message_loop_ui_.RunAllPending();
}

TEST_F(GoogleAuthenticatorTest, CaptchaErrorOutputted) {
  GoogleServiceAuthError auth_error =
      GoogleServiceAuthError::FromCaptchaChallenge(
          "CCTOKEN",
          GURL("http://www.google.com/accounts/Captcha?ctoken=CCTOKEN"),
          GURL("http://www.google.com/login/captcha"));

  LoginFailure failure = LoginFailure::FromNetworkAuthFailure(auth_error);

  MockConsumer consumer;
  EXPECT_CALL(consumer, OnLoginFailure(failure))
      .Times(1)
      .RetiresOnSaturation();

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  PrepForFailedLogin(auth.get());
  auth->OnClientLoginFailure(auth_error);
  message_loop_ui_.RunAllPending();
}

TEST_F(GoogleAuthenticatorTest, OfflineLogin) {
  GoogleServiceAuthError auth_error(
      GoogleServiceAuthError::FromConnectionError(net::ERR_CONNECTION_RESET));

  MockConsumer consumer;
  EXPECT_CALL(consumer, OnLoginSuccess(username_, password_, result_, false))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_library_, CheckKey(username_, hash_ascii_))
      .WillOnce(Return(true))
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_library_, Mount(username_, hash_ascii_, _))
      .WillOnce(Return(true))
      .RetiresOnSaturation();

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  PrepForLogin(auth.get());
  auth->OnClientLoginFailure(auth_error);
  message_loop_ui_.RunAllPending();
}

TEST_F(GoogleAuthenticatorTest, OnlineLogin) {
  MockConsumer consumer;
  EXPECT_CALL(consumer, OnLoginSuccess(username_, password_, result_, false))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_library_, Mount(username_, hash_ascii_, _))
      .WillOnce(Return(true))
      .RetiresOnSaturation();

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  PrepForLogin(auth.get());
  EXPECT_CALL(*user_manager_.get(), IsKnownUser(username_))
      .WillOnce(Return(true))
      .RetiresOnSaturation();
  auth->OnClientLoginSuccess(result_);
  message_loop_ui_.RunAllPending();
}

TEST_F(GoogleAuthenticatorTest, CheckLocalaccount) {
  GURL source(AuthResponseHandler::kTokenAuthUrl);
  net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);

  MockConsumer consumer;
  EXPECT_CALL(consumer, OnLoginSuccess(username_, std::string(), _, false))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_library_, MountForBwsi(_))
      .WillOnce(Return(true))
      .RetiresOnSaturation();

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  PrepForLogin(auth.get());
  auth->SetLocalaccount(username_);

  auth->CheckLocalaccount(LoginFailure(LoginFailure::LOGIN_TIMED_OUT));
}

TEST_F(GoogleAuthenticatorTest, LocalaccountLogin) {
  // This test checks the logic that governs asynchronously reading the
  // localaccount name off disk and trying to authenticate against it
  // simultaneously.
  MockConsumer consumer;
  EXPECT_CALL(consumer, OnLoginSuccess(username_, std::string(), _, false))
      .WillOnce(Invoke(MockConsumer::OnSuccessQuit))
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_library_, MountForBwsi(_))
      .WillOnce(Return(true))
      .RetiresOnSaturation();
  // Enable the test to terminate (and fail), even if the login fails.
  ON_CALL(consumer, OnLoginFailure(_))
      .WillByDefault(Invoke(MockConsumer::OnFailQuitAndFail));

  // Manually prep for login, so that localaccount isn't set for us.
  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  auth->set_password_hash(hash_ascii_);
  auth->set_username(username_);

  // First, force a check of username_ against the localaccount -- which we
  // haven't yet gotten off disk.
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      NewRunnableMethod(auth.get(),
                        &GoogleAuthenticator::CheckLocalaccount,
                        LoginFailure(LoginFailure::LOGIN_TIMED_OUT)));
  message_loop_ui_.RunAllPending();
  // The foregoing has now rescheduled itself in a few ms because we don't
  // yet have the localaccount loaded off disk.

  // Now, cause the FILE thread to go load the localaccount off disk.
  FilePath tmp_file_path = FakeLocalaccountFile(username_);
  ReadLocalaccountFile(auth.get(), tmp_file_path.BaseName().value());

  // Run remaining events, until OnLoginSuccess or OnLoginFailure is called.
  message_loop_ui_.Run();

  // Cleanup.
  Delete(tmp_file_path, false);
}

TEST_F(GoogleAuthenticatorTest, FullLogin) {
  chromeos::CryptohomeBlob salt_v(fake_hash_, fake_hash_ + sizeof(fake_hash_));

  MockConsumer consumer;
  EXPECT_CALL(consumer, OnLoginSuccess(username_,
                                       password_,
                                       Eq(result_),
                                       false))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_library_, Mount(username_, _, _))
      .WillOnce(Return(true))
      .RetiresOnSaturation();

  EXPECT_CALL(*mock_library_, GetSystemSalt())
      .WillOnce(Return(salt_v))
      .RetiresOnSaturation();

  TestingProfile profile;

  MockFactory<MockFetcher> factory;
  URLFetcher::set_factory(&factory);

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  EXPECT_CALL(*user_manager_.get(), IsKnownUser(username_))
      .WillOnce(Return(true))
      .RetiresOnSaturation();
  auth->set_user_manager(user_manager_.get());
  auth->AuthenticateToLogin(
      &profile, username_, password_, std::string(), std::string());

  URLFetcher::set_factory(NULL);
  message_loop_ui_.RunAllPending();
}

TEST_F(GoogleAuthenticatorTest, FullHostedLoginFailure) {
  chromeos::CryptohomeBlob salt_v(fake_hash_, fake_hash_ + sizeof(fake_hash_));

  LoginFailure failure_details =
      LoginFailure::FromNetworkAuthFailure(
          GoogleServiceAuthError(
              GoogleServiceAuthError::HOSTED_NOT_ALLOWED));

  MockConsumer consumer;
  EXPECT_CALL(consumer, OnLoginFailure(failure_details))
      .WillOnce(Invoke(MockConsumer::OnFailQuit))
      .RetiresOnSaturation();
  // A failure case, but we still want the test to finish gracefully.
  ON_CALL(consumer, OnLoginSuccess(username_, password_, _, _))
      .WillByDefault(Invoke(MockConsumer::OnSuccessQuitAndFail));

  EXPECT_CALL(*mock_library_, GetSystemSalt())
      .WillOnce(Return(salt_v))
      .RetiresOnSaturation();

  TestingProfile profile;

  MockFactory<HostedFetcher> factory_invalid;
  URLFetcher::set_factory(&factory_invalid);

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  auth->set_user_manager(user_manager_.get());
  EXPECT_CALL(*user_manager_.get(), IsKnownUser(username_))
      .WillOnce(Return(false))
      .WillOnce(Return(false))
      .RetiresOnSaturation();
  auth->AuthenticateToLogin(
      &profile, username_, hash_ascii_, std::string(), std::string());

  // For when |auth| tries to load the localaccount file.
  BrowserThread file_thread(BrowserThread::FILE);
  file_thread.Start();

  // Run the UI thread until we exit it gracefully.
  message_loop_ui_.Run();
  URLFetcher::set_factory(NULL);
}

TEST_F(GoogleAuthenticatorTest, CancelLogin) {
  chromeos::CryptohomeBlob salt_v(fake_hash_, fake_hash_ + sizeof(fake_hash_));

  MockConsumer consumer;
  // The expected case.
  EXPECT_CALL(consumer, OnLoginFailure(_))
      .WillOnce(Invoke(MockConsumer::OnFailQuit))
      .RetiresOnSaturation();

  // A failure case, but we still want the test to finish gracefully.
  ON_CALL(consumer, OnLoginSuccess(username_, password_, _, _))
      .WillByDefault(Invoke(MockConsumer::OnSuccessQuitAndFail));

  // Stuff we expect to happen along the way.
  EXPECT_CALL(*mock_library_, GetSystemSalt())
      .WillOnce(Return(salt_v))
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_library_, CheckKey(username_, _))
      .WillOnce(Return(false))
      .RetiresOnSaturation();

  TestingProfile profile;

  // This is how we inject fake URLFetcher objects, with a factory.
  // This factory creates fake URLFetchers that Start() a fake fetch attempt
  // and then come back on the UI thread after a small delay.  They expect to
  // be canceled before they come back, and the test will fail if they are not.
  MockFactory<ExpectCanceledFetcher> factory;
  URLFetcher::set_factory(&factory);

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  // For when |auth| tries to load the localaccount file.
  BrowserThread file_thread(BrowserThread::FILE);
  file_thread.Start();

  // Start an authentication attempt, which will kick off a URL "fetch" that
  // we expect to cancel before it completes.
  auth->AuthenticateToLogin(
      &profile, username_, hash_ascii_, std::string(), std::string());

  // Post a task to cancel the login attempt.
  CancelLogin(auth.get());

  URLFetcher::set_factory(NULL);

  // Run the UI thread until we exit it gracefully.
  message_loop_ui_.Run();
}

TEST_F(GoogleAuthenticatorTest, CancelLoginAlreadyGotLocalaccount) {
  chromeos::CryptohomeBlob salt_v(fake_hash_, fake_hash_ + sizeof(fake_hash_));

  MockConsumer consumer;
  // The expected case.
  EXPECT_CALL(consumer, OnLoginFailure(_))
      .WillOnce(Invoke(MockConsumer::OnFailQuit))
      .RetiresOnSaturation();

  // A failure case, but we still want the test to finish gracefully.
  ON_CALL(consumer, OnLoginSuccess(username_, password_, _, _))
      .WillByDefault(Invoke(MockConsumer::OnSuccessQuitAndFail));

  // Stuff we expect to happen along the way.
  EXPECT_CALL(*mock_library_, GetSystemSalt())
      .WillOnce(Return(salt_v))
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_library_, CheckKey(username_, _))
      .WillOnce(Return(false))
      .RetiresOnSaturation();

  TestingProfile profile;

  // This is how we inject fake URLFetcher objects, with a factory.
  // This factory creates fake URLFetchers that Start() a fake fetch attempt
  // and then come back on the UI thread after a small delay.  They expect to
  // be canceled before they come back, and the test will fail if they are not.
  MockFactory<ExpectCanceledFetcher> factory;
  URLFetcher::set_factory(&factory);

  scoped_refptr<GoogleAuthenticator> auth(new GoogleAuthenticator(&consumer));
  // This time, instead of allowing |auth| to go get the localaccount file
  // itself, we simulate the case where the file is already loaded, which
  // happens when this isn't the first login since chrome started.
  ReadLocalaccountFile(auth.get(), "");

  // Start an authentication attempt, which will kick off a URL "fetch" that
  // we expect to cancel before it completes.
  auth->AuthenticateToLogin(
      &profile, username_, hash_ascii_, std::string(), std::string());

  // Post a task to cancel the login attempt.
  CancelLogin(auth.get());

  URLFetcher::set_factory(NULL);

  // Run the UI thread until we exit it gracefully.
  message_loop_ui_.Run();
}

}  // namespace chromeos