// Copyright 2024 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/component_updater/installer_policies/first_party_sets_component_installer_policy.h"

#include "base/check.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback_helpers.h"
#include "base/task/thread_pool.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/version.h"
#include "components/component_updater/mock_component_updater_service.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace component_updater {

namespace {

using ::testing::_;
using ::testing::IsEmpty;

std::string ReadToString(base::File file) {
  std::string contents;
  base::ScopedFILE scoped_file(base::FileToFILE(std::move(file), "r"));
  return base::ReadStreamToString(scoped_file.get(), &contents) ? contents : "";
}

}  // namespace

class FirstPartySetsComponentInstallerTest : public ::testing::Test {
 public:
  FirstPartySetsComponentInstallerTest() {
    CHECK(component_install_dir_.CreateUniqueTempDir());
  }

  void SetUp() override {
    FirstPartySetsComponentInstallerPolicy::ResetForTesting();
  }

 protected:
  base::test::TaskEnvironment env_;

  base::ScopedTempDir component_install_dir_;
};

TEST_F(FirstPartySetsComponentInstallerTest, NonexistentFile_OnComponentReady) {
  ASSERT_TRUE(base::DeleteFile(
      FirstPartySetsComponentInstallerPolicy::GetInstalledPathForTesting(
          component_install_dir_.GetPath())));

  base::test::TestFuture<base::Version, base::File> future;
  FirstPartySetsComponentInstallerPolicy(future.GetCallback(),
                                         base::TaskPriority::USER_BLOCKING)
      .ComponentReadyForTesting(
          base::Version(), component_install_dir_.GetPath(), base::DictValue());

  std::tuple<base::Version, base::File> got = future.Take();
  EXPECT_FALSE(std::get<0>(got).IsValid());
  EXPECT_FALSE(std::get<1>(got).IsValid());
}

TEST_F(FirstPartySetsComponentInstallerTest,
       NonexistentFile_OnRegistrationComplete) {
  ASSERT_TRUE(base::DeleteFile(
      FirstPartySetsComponentInstallerPolicy::GetInstalledPathForTesting(
          component_install_dir_.GetPath())));

  base::test::TestFuture<base::Version, base::File> future;
  FirstPartySetsComponentInstallerPolicy policy(
      future.GetCallback(), base::TaskPriority::USER_BLOCKING);
  policy.OnRegistrationComplete();

  std::tuple<base::Version, base::File> got = future.Take();
  EXPECT_FALSE(std::get<0>(got).IsValid());
  EXPECT_FALSE(std::get<1>(got).IsValid());

  // Only one call has any effect.
  policy.OnRegistrationComplete();
  env_.RunUntilIdle();
}

TEST_F(FirstPartySetsComponentInstallerTest, LoadsSets_OnComponentReady) {
  const base::Version version = base::Version("0.0.1");
  const std::string expectation = "some first party sets";
  base::test::TestFuture<base::Version, base::File> future;
  auto policy = std::make_unique<FirstPartySetsComponentInstallerPolicy>(
      future.GetCallback(), base::TaskPriority::USER_BLOCKING);

  ASSERT_TRUE(base::WriteFile(
      FirstPartySetsComponentInstallerPolicy::GetInstalledPathForTesting(
          component_install_dir_.GetPath()),
      expectation));

  policy->ComponentReadyForTesting(version, component_install_dir_.GetPath(),
                                   base::DictValue());

  std::tuple<base::Version, base::File> got = future.Take();
  EXPECT_TRUE(std::get<0>(got).IsValid());
  EXPECT_EQ(std::get<0>(got), version);
  EXPECT_TRUE(std::get<1>(got).IsValid());
  EXPECT_EQ(ReadToString(std::move(std::get<1>(got))), expectation);
}

// Test that when the first version of the component is installed,
// ComponentReady is a no-op, because OnRegistrationComplete already executed
// the OnceCallback.
TEST_F(FirstPartySetsComponentInstallerTest, IgnoreNewSets_NoInitialComponent) {
  base::test::TestFuture<base::Version, base::File> future;
  FirstPartySetsComponentInstallerPolicy policy(
      future.GetCallback(), base::TaskPriority::USER_BLOCKING);

  policy.OnRegistrationComplete();
  std::tuple<base::Version, base::File> got = future.Take();
  EXPECT_FALSE(std::get<0>(got).IsValid());
  EXPECT_FALSE(std::get<1>(got).IsValid());

  // Install the component, which should be ignored.
  base::ScopedTempDir install_dir;
  ASSERT_TRUE(install_dir.CreateUniqueTempDirUnderPath(
      component_install_dir_.GetPath()));
  ASSERT_TRUE(base::WriteFile(
      FirstPartySetsComponentInstallerPolicy::GetInstalledPathForTesting(
          install_dir.GetPath()),
      "first party sets content"));
  policy.ComponentReadyForTesting(base::Version("0.0.1"), install_dir.GetPath(),
                                  base::DictValue());

  env_.RunUntilIdle();
}

// Test if a component has been installed, ComponentReady will be no-op when
// newer versions are installed.
TEST_F(FirstPartySetsComponentInstallerTest, IgnoreNewSets_OnComponentReady) {
  base::test::TestFuture<base::Version, base::File> future;
  FirstPartySetsComponentInstallerPolicy policy(
      future.GetCallback(), base::TaskPriority::USER_BLOCKING);

  const base::Version version = base::Version("0.0.1");
  const std::string sets_v1 = "first party sets v1";
  base::ScopedTempDir dir_v1;
  ASSERT_TRUE(
      dir_v1.CreateUniqueTempDirUnderPath(component_install_dir_.GetPath()));
  ASSERT_TRUE(base::WriteFile(
      FirstPartySetsComponentInstallerPolicy::GetInstalledPathForTesting(
          dir_v1.GetPath()),
      sets_v1));
  policy.ComponentReadyForTesting(version, dir_v1.GetPath(), base::DictValue());

  std::tuple<base::Version, base::File> got = future.Take();
  EXPECT_TRUE(std::get<0>(got).IsValid());
  EXPECT_EQ(std::get<0>(got), version);
  EXPECT_TRUE(std::get<1>(got).IsValid());
  EXPECT_EQ(ReadToString(std::move(std::get<1>(got))), sets_v1);

  // Install newer version of the component, which should not be picked up
  // when calling ComponentReady again.
  const std::string sets_v2 = "first party sets v2";
  base::ScopedTempDir dir_v2;
  ASSERT_TRUE(
      dir_v2.CreateUniqueTempDirUnderPath(component_install_dir_.GetPath()));
  ASSERT_TRUE(base::WriteFile(
      FirstPartySetsComponentInstallerPolicy::GetInstalledPathForTesting(
          dir_v2.GetPath()),
      sets_v2));
  policy.ComponentReadyForTesting(base::Version("0.0.1"), dir_v2.GetPath(),
                                  base::DictValue());

  env_.RunUntilIdle();
}

TEST_F(FirstPartySetsComponentInstallerTest, GetInstallerAttributes) {
  FirstPartySetsComponentInstallerPolicy policy(
      base::DoNothing(), base::TaskPriority::USER_BLOCKING);

  EXPECT_THAT(policy.GetInstallerAttributesForTesting(), IsEmpty());
}

}  // namespace component_updater
