// Copyright 2017 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 "extensions/browser/updater/extension_installer.h"

#include <memory>
#include <string>

#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.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 "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() 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_;

  DISALLOW_COPY_AND_ASSIGN(ExtensionInstallerTest);
};

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

ExtensionInstallerTest::~ExtensionInstallerTest() {}

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_TRUE(base::PathExists(root_dir.GetPath()));
  scoped_refptr<ExtensionInstaller> installer =
      base::MakeRefCounted<ExtensionInstaller>(kExtensionId, root_dir.GetPath(),
                                               false /*install_immediately*/,
                                               ExtensionInstallerCallback());

  base::FilePath installed_file;

#ifdef FILE_PATH_USES_DRIVE_LETTERS
  const std::string absolute_path = "C:\\abc\\def";
  const std::string relative_path = "abc\\..\\def\\ghi";
#else
  const std::string absolute_path = "/abc/def";
  const std::string relative_path = "/abc/../def/ghi";
#endif

  installed_file.clear();
  EXPECT_FALSE(installer->GetInstalledFile(absolute_path, &installed_file));
  installed_file.clear();
  EXPECT_FALSE(installer->GetInstalledFile(relative_path, &installed_file));
  installed_file.clear();
  EXPECT_FALSE(installer->GetInstalledFile("extension", &installed_file));

  installed_file.clear();
  base::FilePath temp_file;
  ASSERT_TRUE(base::CreateTemporaryFileInDir(root_dir.GetPath(), &temp_file));
  base::FilePath base_temp_file = temp_file.BaseName();
  EXPECT_TRUE(installer->GetInstalledFile(
      std::string(base_temp_file.value().begin(), base_temp_file.value().end()),
      &installed_file));
#ifndef FILE_PATH_USES_DRIVE_LETTERS
  // On some Win*, this test is flaky because of the way Win* constructs path.
  // For example,
  // "C:\Users\chrome-bot\AppData" is the same as "C:\Users\CHROME~1\AppData"
  EXPECT_EQ(temp_file, installed_file);
#endif
}

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, root_dir.GetPath(), true /*install_immediately*/,
          base::BindOnce(
              [](const std::string& 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::DeleteFile(unpacked_dir.GetPath(), true));
  ASSERT_FALSE(base::PathExists(unpacked_dir.GetPath()));
  installer->Install(
      unpacked_dir.GetPath(), kPublicKey,
      base::BindOnce(&ExtensionInstallerTest::InstallCompleteCallback,
                     base::Unretained(this)));

  RunThreads();

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

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, root_dir.GetPath(), false /*install_immediately*/,
          base::BindOnce([](const std::string& 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,
      base::BindOnce(&ExtensionInstallerTest::InstallCompleteCallback,
                     base::Unretained(this)));

  RunThreads();

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

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, root_dir.GetPath(), true /*install_immediately*/,
          base::BindOnce([](const std::string& 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,
      base::BindOnce(&ExtensionInstallerTest::InstallCompleteCallback,
                     base::Unretained(this)));

  RunThreads();

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

}  // namespace

}  // namespace extensions
