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

#include "components/update_client/unpacker.h"

#include <iterator>
#include <utility>
#include <vector>

#include "base/containers/to_vector.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "components/crx_file/crx_verifier.h"
#include "components/services/unzip/in_process_unzipper.h"
#include "components/update_client/test_configurator.h"
#include "components/update_client/test_utils.h"
#include "components/update_client/unzip/unzip_impl.h"
#include "components/update_client/unzipper.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace update_client {

class UnpackerTest : public ::testing::TestWithParam<bool> {
 public:
  bool IsForeground() const { return GetParam(); }

 private:
  base::test::TaskEnvironment env_;
};

INSTANTIATE_TEST_SUITE_P(ForegroundAndBackground,
                         UnpackerTest,
                         ::testing::Bool());

TEST_P(UnpackerTest, UnpackFullCrx) {
  SEQUENCE_CHECKER(sequence_checker);
  base::RunLoop loop;
  Unpacker::Unpack(
      "jebgalgnebhfojomionfpkfelancnnkf", "UnpackerTest",
      base::ToVector(jebg_hash),
      GetTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"),
      base::MakeRefCounted<update_client::UnzipChromiumFactory>(
          base::BindRepeating(&unzip::LaunchInProcessUnzipper))
          ->Create(),
      crx_file::VerifierFormat::CRX3, IsForeground(),
      base::BindLambdaForTesting([&](const Unpacker::Result& result) {
        DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker);
        EXPECT_EQ(result.error, UnpackerError::kNone);
        EXPECT_EQ(result.extended_error, 0);

        base::FilePath unpack_path = result.unpack_path;
        EXPECT_TRUE(base::DirectoryExists(unpack_path));
        EXPECT_EQ(result.public_key, jebg_public_key);

        std::optional<int64_t> file_size = base::GetFileSize(
            unpack_path.Append(FILE_PATH_LITERAL("component1.dll")));
        ASSERT_TRUE(file_size.has_value());
        EXPECT_EQ(file_size.value(), 1024);
        file_size = base::GetFileSize(
            unpack_path.Append(FILE_PATH_LITERAL("manifest.json")));
        ASSERT_TRUE(file_size.has_value());
        EXPECT_EQ(file_size.value(), 169);

        EXPECT_TRUE(base::DeletePathRecursively(unpack_path));
        loop.Quit();
      }));
  loop.Run();
}

TEST_P(UnpackerTest, UnpackFileNotFound) {
  SEQUENCE_CHECKER(sequence_checker);
  base::RunLoop loop;
  Unpacker::Unpack(
      "jebgalgnebhfojomionfpkfelancnnkf", "UnpackerTest",
      base::ToVector(jebg_hash), GetTestFilePath("file_not_found.crx"), nullptr,
      crx_file::VerifierFormat::CRX3, IsForeground(),
      base::BindLambdaForTesting([&](const Unpacker::Result& result) {
        DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker);
        EXPECT_EQ(result.error, UnpackerError::kInvalidFile);
        EXPECT_EQ(result.extended_error,
                  static_cast<int>(
                      crx_file::VerifierResult::ERROR_FILE_NOT_READABLE));
        EXPECT_TRUE(result.unpack_path.empty());
        EXPECT_TRUE(result.public_key.empty());
        loop.Quit();
      }));
  loop.Run();
}

// Tests a mismatch between the public key hash and the id of the component.
TEST_P(UnpackerTest, UnpackFileHashMismatch) {
  SEQUENCE_CHECKER(sequence_checker);
  base::RunLoop loop;
  Unpacker::Unpack(
      "jebgalgnebhfojomionfpkfelancnnkf", "UnpackerTest",
      base::ToVector(abag_hash),
      GetTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), nullptr,
      crx_file::VerifierFormat::CRX3, IsForeground(),
      base::BindLambdaForTesting([&](const Unpacker::Result& result) {
        DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker);
        EXPECT_EQ(result.error, UnpackerError::kInvalidFile);
        EXPECT_EQ(result.extended_error,
                  static_cast<int>(
                      crx_file::VerifierResult::ERROR_REQUIRED_PROOF_MISSING));
        EXPECT_TRUE(result.unpack_path.empty());
        EXPECT_TRUE(result.public_key.empty());
        loop.Quit();
      }));
  loop.Run();
}

TEST_P(UnpackerTest, UnpackWithVerifiedContents) {
  SEQUENCE_CHECKER(sequence_checker);
  base::RunLoop loop;
  Unpacker::Unpack(
      "gndmhdcefbhlchkhipcnnbkcmicncehk", "UnpackerTest",
      std::vector<uint8_t>(),
      GetTestFilePath("gndmhdcefbhlchkhipcnnbkcmicncehk_22_314.crx3"),
      base::MakeRefCounted<update_client::UnzipChromiumFactory>(
          base::BindRepeating(&unzip::LaunchInProcessUnzipper))
          ->Create(),
      crx_file::VerifierFormat::CRX3, IsForeground(),
      base::BindLambdaForTesting([&](const Unpacker::Result& result) {
        DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker);
        EXPECT_EQ(result.error, UnpackerError::kNone);
        base::FilePath unpack_path = result.unpack_path;
        EXPECT_FALSE(unpack_path.empty());
        EXPECT_TRUE(base::DirectoryExists(unpack_path));
        std::optional<int64_t> file_size = base::GetFileSize(
            unpack_path.Append(FILE_PATH_LITERAL("_metadata"))
                .Append(FILE_PATH_LITERAL("verified_contents.json")));
        ASSERT_TRUE(file_size.has_value());
        EXPECT_EQ(file_size.value(), 1538);
        EXPECT_TRUE(base::DeletePathRecursively(unpack_path));
        loop.Quit();
      }));
  loop.Run();
}

}  // namespace update_client
