// 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 <string>

#include "base/memory/ref_counted.h"
#include "base/message_loop.h"
#include "chrome/browser/chromeos/cros/mock_library_loader.h"
#include "chrome/browser/chromeos/login/auth_attempt_state.h"
#include "chrome/browser/chromeos/login/online_attempt.h"
#include "chrome/browser/chromeos/login/mock_auth_attempt_state_resolver.h"
#include "chrome/browser/chromeos/login/mock_url_fetchers.h"
#include "chrome/browser/chromeos/login/test_attempt_state.h"
#include "chrome/common/net/gaia/gaia_auth_consumer.h"
#include "chrome/common/net/gaia/gaia_auth_fetcher_unittest.h"
#include "chrome/test/testing_profile.h"
#include "content/browser/browser_thread.h"
#include "googleurl/src/gurl.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::AnyNumber;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::_;

namespace chromeos {

class OnlineAttemptTest : public ::testing::Test {
 public:
  OnlineAttemptTest()
      : message_loop_(MessageLoop::TYPE_UI),
        ui_thread_(BrowserThread::UI, &message_loop_),
        io_thread_(BrowserThread::IO),
        state_("", "", "", "", "", false),
        resolver_(new MockAuthAttemptStateResolver) {
  }

  virtual ~OnlineAttemptTest() {}

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

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

    // Passes ownership of |loader| to CrosLibrary.
    test_api->SetLibraryLoader(loader, true);

    attempt_ = new OnlineAttempt(&state_, resolver_.get());

    io_thread_.Start();
  }

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

  void RunFailureTest(const GoogleServiceAuthError& error) {
    EXPECT_CALL(*(resolver_.get()), Resolve())
        .Times(1)
        .RetiresOnSaturation();

    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        NewRunnableMethod(attempt_.get(),
                          &OnlineAttempt::OnClientLoginFailure,
                          error));
    // Force IO thread to finish tasks so I can verify |state_|.
    io_thread_.Stop();
    EXPECT_TRUE(error == state_.online_outcome().error());
  }

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

  static void Quit() {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE, new MessageLoop::QuitTask());
  }

  static void RunThreadTest() {
    MessageLoop::current()->RunAllPending();
  }

  MessageLoop message_loop_;
  BrowserThread ui_thread_;
  BrowserThread io_thread_;
  TestAttemptState state_;
  scoped_ptr<MockAuthAttemptStateResolver> resolver_;
  scoped_refptr<OnlineAttempt> attempt_;
};

TEST_F(OnlineAttemptTest, LoginSuccess) {
  GaiaAuthConsumer::ClientLoginResult result;
  EXPECT_CALL(*(resolver_.get()), Resolve())
      .Times(1)
      .RetiresOnSaturation();

  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      NewRunnableMethod(attempt_.get(),
                        &OnlineAttempt::OnClientLoginSuccess,
                        result));
  // Force IO thread to finish tasks so I can verify |state_|.
  io_thread_.Stop();
  EXPECT_TRUE(result == state_.credentials());
}

TEST_F(OnlineAttemptTest, LoginCancelRetry) {
  GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
  TestingProfile profile;

  EXPECT_CALL(*(resolver_.get()), Resolve())
      .WillOnce(Invoke(OnlineAttemptTest::Quit))
      .RetiresOnSaturation();

  // 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 IO thread saying they've been canceled.
  MockFactory<GotCanceledFetcher> factory;
  URLFetcher::set_factory(&factory);

  attempt_->Initiate(&profile);
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      NewRunnableFunction(&OnlineAttemptTest::RunThreadTest));

  MessageLoop::current()->Run();

  EXPECT_TRUE(error == state_.online_outcome().error());
  EXPECT_EQ(LoginFailure::NETWORK_AUTH_FAILED,
            state_.online_outcome().reason());
  URLFetcher::set_factory(NULL);
}

TEST_F(OnlineAttemptTest, LoginTimeout) {
  LoginFailure error(LoginFailure::LOGIN_TIMED_OUT);
  TestingProfile profile;

  EXPECT_CALL(*(resolver_.get()), Resolve())
      .WillOnce(Invoke(OnlineAttemptTest::Quit))
      .RetiresOnSaturation();

  // 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 IO thread saying they've been canceled.
  MockFactory<ExpectCanceledFetcher> factory;
  URLFetcher::set_factory(&factory);

  attempt_->Initiate(&profile);
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      NewRunnableFunction(&OnlineAttemptTest::RunThreadTest));

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

  MessageLoop::current()->Run();

  EXPECT_EQ(LoginFailure::LOGIN_TIMED_OUT, state_.online_outcome().reason());
  URLFetcher::set_factory(NULL);
}

TEST_F(OnlineAttemptTest, HostedLoginRejected) {
  LoginFailure error(
      LoginFailure::FromNetworkAuthFailure(
          GoogleServiceAuthError(
              GoogleServiceAuthError::HOSTED_NOT_ALLOWED)));
  TestingProfile profile;

  EXPECT_CALL(*(resolver_.get()), Resolve())
      .WillOnce(Invoke(OnlineAttemptTest::Quit))
      .RetiresOnSaturation();

  // This is how we inject fake URLFetcher objects, with a factory.
  MockFactory<HostedFetcher> factory;
  URLFetcher::set_factory(&factory);

  TestAttemptState local_state("", "", "", "", "", true);
  attempt_ = new OnlineAttempt(&local_state, resolver_.get());
  attempt_->Initiate(&profile);
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      NewRunnableFunction(&OnlineAttemptTest::RunThreadTest));

  MessageLoop::current()->Run();

  EXPECT_EQ(error, local_state.online_outcome());
  EXPECT_EQ(LoginFailure::NETWORK_AUTH_FAILED,
            local_state.online_outcome().reason());
  URLFetcher::set_factory(NULL);
}

TEST_F(OnlineAttemptTest, FullLogin) {
  TestingProfile profile;

  EXPECT_CALL(*(resolver_.get()), Resolve())
      .WillOnce(Invoke(OnlineAttemptTest::Quit))
      .RetiresOnSaturation();

  // This is how we inject fake URLFetcher objects, with a factory.
  MockFactory<SuccessFetcher> factory;
  URLFetcher::set_factory(&factory);

  TestAttemptState local_state("", "", "", "", "", true);
  attempt_ = new OnlineAttempt(&local_state, resolver_.get());
  attempt_->Initiate(&profile);
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      NewRunnableFunction(&OnlineAttemptTest::RunThreadTest));

  MessageLoop::current()->Run();

  EXPECT_EQ(LoginFailure::None(), local_state.online_outcome());
  URLFetcher::set_factory(NULL);
}

TEST_F(OnlineAttemptTest, LoginNetFailure) {
  RunFailureTest(
      GoogleServiceAuthError::FromConnectionError(net::ERR_CONNECTION_RESET));
}

TEST_F(OnlineAttemptTest, LoginDenied) {
  RunFailureTest(
      GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
}

TEST_F(OnlineAttemptTest, LoginAccountDisabled) {
  RunFailureTest(
      GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED));
}

TEST_F(OnlineAttemptTest, LoginAccountDeleted) {
  RunFailureTest(
      GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED));
}

TEST_F(OnlineAttemptTest, LoginServiceUnavailable) {
  RunFailureTest(
      GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
}

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

TEST_F(OnlineAttemptTest, TwoFactorSuccess) {
  EXPECT_CALL(*(resolver_.get()), Resolve())
      .Times(1)
      .RetiresOnSaturation();
  GoogleServiceAuthError error(GoogleServiceAuthError::TWO_FACTOR);
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      NewRunnableMethod(attempt_.get(),
                        &OnlineAttempt::OnClientLoginFailure,
                        error));

  // Force IO thread to finish tasks so I can verify |state_|.
  io_thread_.Stop();
  EXPECT_TRUE(GoogleServiceAuthError::None() ==
              state_.online_outcome().error());
  EXPECT_TRUE(GaiaAuthConsumer::ClientLoginResult() == state_.credentials());
}

}  // namespace chromeos