// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <string>
#include <utility>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "components/update_client/update_client.h"
#include "components/update_client/update_client_errors.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/extensions_test.h"
#include "extensions/browser/updater/extension_installer.h"
#include "extensions/common/extension_id.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace extensions {

namespace {

class ExtensionInstallerTest : public ExtensionsTest {
 public:
  using UpdateClientCallback =
      extensions::ExtensionInstaller::UpdateClientCallback;
  using ExtensionInstallerCallback =
      ExtensionInstaller::ExtensionInstallerCallback;
  using Result = update_client::CrxInstaller::Result;
  using InstallError = update_client::InstallError;

  ExtensionInstallerTest();

  ExtensionInstallerTest(const ExtensionInstallerTest&) = delete;
  ExtensionInstallerTest& operator=(const ExtensionInstallerTest&) = delete;

  ~ExtensionInstallerTest() override;

  void InstallCompleteCallback(const Result& result);

 protected:
  void RunThreads();

 protected:
  const std::string kExtensionId = "ldnnhddmnhbkjipkidpdiheffobcpfmf";
  const std::string kPublicKey =
      "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8c4fBSPZ6utYoZ8NiWF/"
      "DSaimBhihjwgOsskyleFGaurhi3TDClTVSGPxNkgCzrz0wACML7M4aNjpd05qupdbR2d294j"
      "kDuI7caxEGUucpP7GJRRHnm8Sx+"
      "y0ury28n8jbN0PnInKKWcxpIXXmNQyC19HBuO3QIeUq9Dqc+7YFQIDAQAB";

  base::RunLoop run_loop_;
  Result result_;
  bool executed_;
};

ExtensionInstallerTest::ExtensionInstallerTest()
    : result_(-1), executed_(false) {}

ExtensionInstallerTest::~ExtensionInstallerTest() = default;

void ExtensionInstallerTest::InstallCompleteCallback(const Result& result) {
  result_ = result;
  executed_ = true;
  run_loop_.Quit();
}

void ExtensionInstallerTest::RunThreads() {
  run_loop_.Run();
}

TEST_F(ExtensionInstallerTest, GetInstalledFile) {
  base::ScopedTempDir root_dir;
  ASSERT_TRUE(root_dir.CreateUniqueTempDir());
  ASSERT_FALSE(base::MakeRefCounted<ExtensionInstaller>(
                   kExtensionId, false /*install_immediately*/,
                   ExtensionInstallerCallback())
                   ->GetInstalledFile("f"));
}

TEST_F(ExtensionInstallerTest, Install_InvalidUnpackedDir) {
  // The unpacked folder is not valid, the installer will return an error.
  base::ScopedTempDir root_dir;
  ASSERT_TRUE(root_dir.CreateUniqueTempDir());
  ASSERT_TRUE(base::PathExists(root_dir.GetPath()));
  scoped_refptr<ExtensionInstaller> installer =
      base::MakeRefCounted<ExtensionInstaller>(
          kExtensionId, true /*install_immediately*/,
          base::BindRepeating(
              [](const ExtensionId& extension_id, const std::string& public_key,
                 const base::FilePath& unpacked_dir, bool install_immediately,
                 UpdateClientCallback update_client_callback) {
                // This function should never be executed.
                EXPECT_TRUE(false);
              }));

  // Non-existing unpacked dir
  base::ScopedTempDir unpacked_dir;
  ASSERT_TRUE(unpacked_dir.CreateUniqueTempDir());
  ASSERT_TRUE(base::PathExists(unpacked_dir.GetPath()));
  ASSERT_TRUE(base::DeletePathRecursively(unpacked_dir.GetPath()));
  ASSERT_FALSE(base::PathExists(unpacked_dir.GetPath()));
  installer->Install(
      unpacked_dir.GetPath(), kPublicKey, nullptr, base::DoNothing(),
      base::BindOnce(&ExtensionInstallerTest::InstallCompleteCallback,
                     base::Unretained(this)));

  RunThreads();

  EXPECT_TRUE(executed_);
  EXPECT_EQ(static_cast<int>(InstallError::GENERIC_ERROR), result_.result.code);
}

TEST_F(ExtensionInstallerTest, Install_BasicInstallOperation_Error) {
  base::ScopedTempDir root_dir;
  ASSERT_TRUE(root_dir.CreateUniqueTempDir());
  ASSERT_TRUE(base::PathExists(root_dir.GetPath()));
  scoped_refptr<ExtensionInstaller> installer =
      base::MakeRefCounted<ExtensionInstaller>(
          kExtensionId, false /*install_immediately*/,
          base::BindRepeating([](const ExtensionId& extension_id,
                                 const std::string& public_key,
                                 const base::FilePath& unpacked_dir,
                                 bool install_immediately,
                                 UpdateClientCallback update_client_callback) {
            EXPECT_FALSE(install_immediately);
            std::move(update_client_callback)
                .Run(Result(InstallError::GENERIC_ERROR));
          }));

  base::ScopedTempDir unpacked_dir;
  ASSERT_TRUE(unpacked_dir.CreateUniqueTempDir());
  ASSERT_TRUE(base::PathExists(unpacked_dir.GetPath()));

  installer->Install(
      unpacked_dir.GetPath(), kPublicKey, nullptr, base::DoNothing(),
      base::BindOnce(&ExtensionInstallerTest::InstallCompleteCallback,
                     base::Unretained(this)));

  RunThreads();

  EXPECT_TRUE(executed_);
  EXPECT_EQ(static_cast<int>(InstallError::GENERIC_ERROR), result_.result.code);
}

TEST_F(ExtensionInstallerTest, Install_BasicInstallOperation_Success) {
  base::ScopedTempDir root_dir;
  ASSERT_TRUE(root_dir.CreateUniqueTempDir());
  ASSERT_TRUE(base::PathExists(root_dir.GetPath()));
  scoped_refptr<ExtensionInstaller> installer =
      base::MakeRefCounted<ExtensionInstaller>(
          kExtensionId, true /*install_immediately*/,
          base::BindRepeating([](const ExtensionId& extension_id,
                                 const std::string& public_key,
                                 const base::FilePath& unpacked_dir,
                                 bool install_immediately,
                                 UpdateClientCallback update_client_callback) {
            EXPECT_TRUE(install_immediately);
            std::move(update_client_callback).Run(Result(InstallError::NONE));
          }));

  base::ScopedTempDir unpacked_dir;
  ASSERT_TRUE(unpacked_dir.CreateUniqueTempDir());
  ASSERT_TRUE(base::PathExists(unpacked_dir.GetPath()));

  installer->Install(
      unpacked_dir.GetPath(), kPublicKey, nullptr, base::DoNothing(),
      base::BindOnce(&ExtensionInstallerTest::InstallCompleteCallback,
                     base::Unretained(this)));

  RunThreads();

  EXPECT_TRUE(executed_);
  EXPECT_EQ(static_cast<int>(InstallError::NONE), result_.result.code);
}

}  // namespace

}  // namespace extensions
