// Copyright 2014 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 "base/bind.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/thread_task_runner_handle.h"
#include "chrome/browser/extensions/extension_install_checker.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace extensions {

namespace {

const BlacklistState kBlacklistStateError = BLACKLISTED_MALWARE;
const char kDummyRequirementsError[] = "Requirements error";
const char kDummyPolicyError[] = "Cannot install extension";

const char kDummyPolicyError2[] = "Another policy error";
const char kDummyRequirementsError2[] = "Another requirements error";
const BlacklistState kBlacklistState2 = BLACKLISTED_SECURITY_VULNERABILITY;

}  // namespace

// Stubs most of the checks since we are interested in validating the logic in
// the install checker. This class implements a synchronous version of all
// checks.
class ExtensionInstallCheckerForTest : public ExtensionInstallChecker {
 public:
  ExtensionInstallCheckerForTest()
      : ExtensionInstallChecker(NULL),
        requirements_check_called_(false),
        blacklist_check_called_(false),
        policy_check_called_(false),
        blacklist_state_(NOT_BLACKLISTED) {}

  ~ExtensionInstallCheckerForTest() override {}

  void set_requirements_error(const std::string& error) {
    requirements_error_ = error;
  }
  void set_policy_check_error(const std::string& error) {
    policy_check_error_ = error;
  }
  void set_blacklist_state(BlacklistState state) { blacklist_state_ = state; }

  bool requirements_check_called() const { return requirements_check_called_; }
  bool blacklist_check_called() const { return blacklist_check_called_; }
  bool policy_check_called() const { return policy_check_called_; }

  void MockCheckRequirements(int sequence_number) {
    std::vector<std::string> errors;
    if (!requirements_error_.empty())
      errors.push_back(requirements_error_);
    OnRequirementsCheckDone(sequence_number, errors);
  }

  void MockCheckBlacklistState(int sequence_number) {
    OnBlacklistStateCheckDone(sequence_number, blacklist_state_);
  }

 protected:
  void CheckRequirements() override {
    requirements_check_called_ = true;
    MockCheckRequirements(current_sequence_number());
  }

  void CheckManagementPolicy() override {
    policy_check_called_ = true;
    OnManagementPolicyCheckDone(policy_check_error_.empty(),
                                policy_check_error_);
  }

  void CheckBlacklistState() override {
    blacklist_check_called_ = true;
    MockCheckBlacklistState(current_sequence_number());
  }

  void ResetResults() override {
    ExtensionInstallChecker::ResetResults();

    requirements_check_called_ = false;
    blacklist_check_called_ = false;
    policy_check_called_ = false;
  }

  bool requirements_check_called_;
  bool blacklist_check_called_;
  bool policy_check_called_;

  // Dummy errors for testing.
  std::string requirements_error_;
  std::string policy_check_error_;
  BlacklistState blacklist_state_;
};

// This class implements asynchronous mocks of the requirements and blacklist
// checks.
class ExtensionInstallCheckerAsync : public ExtensionInstallCheckerForTest {
 protected:
  void CheckRequirements() override {
    requirements_check_called_ = true;

    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE,
        base::Bind(&ExtensionInstallCheckerForTest::MockCheckRequirements,
                   base::Unretained(this), current_sequence_number()));
  }

  void CheckBlacklistState() override {
    blacklist_check_called_ = true;

    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE,
        base::Bind(&ExtensionInstallCheckerForTest::MockCheckBlacklistState,
                   base::Unretained(this), current_sequence_number()));
  }
};

class CheckObserver {
 public:
  CheckObserver() : result_(0), call_count_(0) {}

  int result() const { return result_; }
  int call_count() const { return call_count_; }

  void OnChecksComplete(int checks_failed) {
    result_ = checks_failed;
    ++call_count_;
  }

  void Wait() {
    if (call_count_)
      return;

    base::RunLoop().RunUntilIdle();
  }

 private:
  int result_;
  int call_count_;
};

class ExtensionInstallCheckerTest : public testing::Test {
 public:
  ExtensionInstallCheckerTest() {}
  ~ExtensionInstallCheckerTest() override {}

  void RunSecondInvocation(ExtensionInstallCheckerForTest* checker,
                           int checks_failed) {
    EXPECT_GT(checks_failed, 0);
    EXPECT_FALSE(checker->is_running());
    ValidateExpectedCalls(ExtensionInstallChecker::CHECK_ALL, *checker);

    // Set up different return values.
    checker->set_blacklist_state(kBlacklistState2);
    checker->set_policy_check_error(kDummyPolicyError2);
    checker->set_requirements_error(kDummyRequirementsError2);

    // Run the install checker again and ensure the second set of return values
    // is received.
    checker->Start(
        ExtensionInstallChecker::CHECK_ALL,
        false /* fail fast */,
        base::Bind(&ExtensionInstallCheckerTest::ValidateSecondInvocation,
                   base::Unretained(this),
                   checker));
  }

  void ValidateSecondInvocation(ExtensionInstallCheckerForTest* checker,
                                int checks_failed) {
    EXPECT_FALSE(checker->is_running());
    EXPECT_EQ(ExtensionInstallChecker::CHECK_REQUIREMENTS |
                  ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY,
              checks_failed);
    ValidateExpectedCalls(ExtensionInstallChecker::CHECK_ALL, *checker);

    EXPECT_EQ(kBlacklistState2, checker->blacklist_state());
    ExpectPolicyError(kDummyPolicyError2, *checker);
    ExpectRequirementsError(kDummyRequirementsError2, *checker);
  }

 protected:
  void SetAllErrors(ExtensionInstallCheckerForTest* checker) {
    checker->set_blacklist_state(kBlacklistStateError);
    checker->set_policy_check_error(kDummyPolicyError);
    checker->set_requirements_error(kDummyRequirementsError);
  }

  void ValidateExpectedCalls(int call_mask,
                             const ExtensionInstallCheckerForTest& checker) {
    bool expect_blacklist_checked =
        (call_mask & ExtensionInstallChecker::CHECK_BLACKLIST) != 0;
    bool expect_requirements_checked =
        (call_mask & ExtensionInstallChecker::CHECK_REQUIREMENTS) != 0;
    bool expect_policy_checked =
        (call_mask & ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY) != 0;
    EXPECT_EQ(expect_blacklist_checked, checker.blacklist_check_called());
    EXPECT_EQ(expect_policy_checked, checker.policy_check_called());
    EXPECT_EQ(expect_requirements_checked, checker.requirements_check_called());
  }

  void ExpectRequirementsPass(const ExtensionInstallCheckerForTest& checker) {
    EXPECT_TRUE(checker.requirement_errors().empty());
  }

  void ExpectRequirementsError(const char* expected_error,
                               const ExtensionInstallCheckerForTest& checker) {
    EXPECT_FALSE(checker.requirement_errors().empty());
    EXPECT_EQ(std::string(expected_error),
              checker.requirement_errors().front());
  }

  void ExpectRequirementsError(const ExtensionInstallCheckerForTest& checker) {
    ExpectRequirementsError(kDummyRequirementsError, checker);
  }

  void ExpectBlacklistPass(const ExtensionInstallCheckerForTest& checker) {
    EXPECT_EQ(NOT_BLACKLISTED, checker.blacklist_state());
  }

  void ExpectBlacklistError(const ExtensionInstallCheckerForTest& checker) {
    EXPECT_EQ(kBlacklistStateError, checker.blacklist_state());
  }

  void ExpectPolicyPass(const ExtensionInstallCheckerForTest& checker) {
    EXPECT_TRUE(checker.policy_allows_load());
    EXPECT_TRUE(checker.policy_error().empty());
  }

  void ExpectPolicyError(const char* expected_error,
                         const ExtensionInstallCheckerForTest& checker) {
    EXPECT_FALSE(checker.policy_allows_load());
    EXPECT_FALSE(checker.policy_error().empty());
    EXPECT_EQ(std::string(expected_error), checker.policy_error());
  }

  void ExpectPolicyError(const ExtensionInstallCheckerForTest& checker) {
    ExpectPolicyError(kDummyPolicyError, checker);
  }

  void RunChecker(ExtensionInstallCheckerForTest* checker,
                  bool fail_fast,
                  int checks_to_run,
                  int expected_checks_run,
                  int expected_result) {
    CheckObserver observer;
    checker->Start(checks_to_run,
                   fail_fast,
                   base::Bind(&CheckObserver::OnChecksComplete,
                              base::Unretained(&observer)));
    observer.Wait();

    EXPECT_FALSE(checker->is_running());
    EXPECT_EQ(expected_result, observer.result());
    EXPECT_EQ(1, observer.call_count());
    ValidateExpectedCalls(expected_checks_run, *checker);
  }

  void DoRunAllChecksPass(ExtensionInstallCheckerForTest* checker) {
    RunChecker(checker,
               false /* fail fast */,
               ExtensionInstallChecker::CHECK_ALL,
               ExtensionInstallChecker::CHECK_ALL,
               0);

    ExpectRequirementsPass(*checker);
    ExpectPolicyPass(*checker);
    ExpectBlacklistPass(*checker);
  }

  void DoRunAllChecksFail(ExtensionInstallCheckerForTest* checker) {
    SetAllErrors(checker);
    RunChecker(checker,
               false /* fail fast */,
               ExtensionInstallChecker::CHECK_ALL,
               ExtensionInstallChecker::CHECK_ALL,
               ExtensionInstallChecker::CHECK_ALL);

    ExpectRequirementsError(*checker);
    ExpectPolicyError(*checker);
    ExpectBlacklistError(*checker);
  }

  void DoRunSubsetOfChecks(ExtensionInstallCheckerForTest* checker) {
    // Test check set 1.
    int tests_to_run = ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY |
                       ExtensionInstallChecker::CHECK_REQUIREMENTS;
    SetAllErrors(checker);
    RunChecker(checker, false, tests_to_run, tests_to_run, tests_to_run);

    ExpectRequirementsError(*checker);
    ExpectPolicyError(*checker);
    ExpectBlacklistPass(*checker);

    // Test check set 2.
    tests_to_run = ExtensionInstallChecker::CHECK_BLACKLIST |
                   ExtensionInstallChecker::CHECK_REQUIREMENTS;
    SetAllErrors(checker);
    RunChecker(checker, false, tests_to_run, tests_to_run, tests_to_run);

    ExpectRequirementsError(*checker);
    ExpectPolicyPass(*checker);
    ExpectBlacklistError(*checker);

    // Test a single check.
    tests_to_run = ExtensionInstallChecker::CHECK_BLACKLIST;
    SetAllErrors(checker);
    RunChecker(checker, false, tests_to_run, tests_to_run, tests_to_run);

    ExpectRequirementsPass(*checker);
    ExpectPolicyPass(*checker);
    ExpectBlacklistError(*checker);
  }

 private:
  // A message loop is required for the asynchronous tests.
  base::MessageLoop message_loop;
};

class ExtensionInstallCheckerMultipleInvocationTest
    : public ExtensionInstallCheckerTest {
 public:
  ExtensionInstallCheckerMultipleInvocationTest() : callback_count_(0) {}
  ~ExtensionInstallCheckerMultipleInvocationTest() override {}

  void RunSecondInvocation(ExtensionInstallCheckerForTest* checker,
                           int checks_failed) {
    ASSERT_EQ(0, callback_count_);
    ++callback_count_;
    EXPECT_FALSE(checker->is_running());
    EXPECT_GT(checks_failed, 0);
    ValidateExpectedCalls(ExtensionInstallChecker::CHECK_ALL, *checker);

    // Set up different return values.
    checker->set_blacklist_state(kBlacklistState2);
    checker->set_policy_check_error(kDummyPolicyError2);
    checker->set_requirements_error(kDummyRequirementsError2);

    // Run the install checker again and ensure the second set of return values
    // is received.
    checker->Start(ExtensionInstallChecker::CHECK_ALL,
                   false /* fail fast */,
                   base::Bind(&ExtensionInstallCheckerMultipleInvocationTest::
                                  ValidateSecondInvocation,
                              base::Unretained(this),
                              checker));
  }

  void ValidateSecondInvocation(ExtensionInstallCheckerForTest* checker,
                                int checks_failed) {
    ASSERT_EQ(1, callback_count_);
    EXPECT_FALSE(checker->is_running());
    EXPECT_EQ(ExtensionInstallChecker::CHECK_REQUIREMENTS |
                  ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY,
              checks_failed);
    ValidateExpectedCalls(ExtensionInstallChecker::CHECK_ALL, *checker);

    EXPECT_EQ(kBlacklistState2, checker->blacklist_state());
    ExpectPolicyError(kDummyPolicyError2, *checker);
    ExpectRequirementsError(kDummyRequirementsError2, *checker);
  }

 private:
  int callback_count_;
};

// Test the case where all tests pass.
TEST_F(ExtensionInstallCheckerTest, AllSucceeded) {
  ExtensionInstallCheckerForTest sync_checker;
  DoRunAllChecksPass(&sync_checker);

  ExtensionInstallCheckerAsync async_checker;
  DoRunAllChecksPass(&async_checker);
}

// Test the case where all tests fail.
TEST_F(ExtensionInstallCheckerTest, AllFailed) {
  ExtensionInstallCheckerForTest sync_checker;
  DoRunAllChecksFail(&sync_checker);

  ExtensionInstallCheckerAsync async_checker;
  DoRunAllChecksFail(&async_checker);
}

// Test running only a subset of tests.
TEST_F(ExtensionInstallCheckerTest, RunSubsetOfChecks) {
  ExtensionInstallCheckerForTest sync_checker;
  ExtensionInstallCheckerAsync async_checker;
  DoRunSubsetOfChecks(&sync_checker);
  DoRunSubsetOfChecks(&async_checker);
}

// Test fail fast with synchronous callbacks.
TEST_F(ExtensionInstallCheckerTest, FailFastSync) {
  // This test assumes some internal knowledge of the implementation - that
  // the policy check runs first.
  ExtensionInstallCheckerForTest checker;
  SetAllErrors(&checker);
  RunChecker(&checker,
             true /* fail fast */,
             ExtensionInstallChecker::CHECK_ALL,
             ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY,
             ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY);

  ExpectRequirementsPass(checker);
  ExpectPolicyError(checker);
  ExpectBlacklistPass(checker);

  // This test assumes some internal knowledge of the implementation - that
  // the requirements check runs before the blacklist check.
  SetAllErrors(&checker);
  RunChecker(&checker,
             true /* fail fast */,
             ExtensionInstallChecker::CHECK_REQUIREMENTS |
                 ExtensionInstallChecker::CHECK_BLACKLIST,
             ExtensionInstallChecker::CHECK_REQUIREMENTS,
             ExtensionInstallChecker::CHECK_REQUIREMENTS);

  ExpectRequirementsError(checker);
  ExpectPolicyPass(checker);
  ExpectBlacklistPass(checker);
}

// Test fail fast with asynchronous callbacks.
TEST_F(ExtensionInstallCheckerTest, FailFastAsync) {
  // This test assumes some internal knowledge of the implementation - that
  // the requirements check runs before the blacklist check. Both checks should
  // be called, but the requirements check callback arrives first and the
  // blacklist result will be discarded.
  ExtensionInstallCheckerAsync checker;
  SetAllErrors(&checker);

  // The policy check is synchronous and needs to pass for the other tests to
  // run.
  checker.set_policy_check_error(std::string());

  RunChecker(&checker,
             true /* fail fast */,
             ExtensionInstallChecker::CHECK_ALL,
             ExtensionInstallChecker::CHECK_ALL,
             ExtensionInstallChecker::CHECK_REQUIREMENTS);

  ExpectRequirementsError(checker);
  ExpectPolicyPass(checker);
  ExpectBlacklistPass(checker);
}

// Test multiple invocations of the install checker. Wait for all checks to
// complete.
TEST_F(ExtensionInstallCheckerMultipleInvocationTest, CompleteAll) {
  ExtensionInstallCheckerAsync checker;
  SetAllErrors(&checker);

  // Start the second check as soon as the callback of the first run is invoked.
  checker.Start(
      ExtensionInstallChecker::CHECK_ALL,
      false /* fail fast */,
      base::Bind(
          &ExtensionInstallCheckerMultipleInvocationTest::RunSecondInvocation,
          base::Unretained(this),
          &checker));
  base::RunLoop().RunUntilIdle();
}

// Test multiple invocations of the install checker and fail fast.
TEST_F(ExtensionInstallCheckerMultipleInvocationTest, FailFast) {
  ExtensionInstallCheckerAsync checker;
  SetAllErrors(&checker);

  // The policy check is synchronous and needs to pass for the other tests to
  // run.
  checker.set_policy_check_error(std::string());

  // Start the second check as soon as the callback of the first run is invoked.
  checker.Start(
      ExtensionInstallChecker::CHECK_ALL,
      true /* fail fast */,
      base::Bind(
          &ExtensionInstallCheckerMultipleInvocationTest::RunSecondInvocation,
          base::Unretained(this),
          &checker));
  base::RunLoop().RunUntilIdle();
}

}  // namespace extensions