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

#include "chrome/browser/extensions/extension_service_test_base.h"

#include <utility>

#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/bookmarks/desktop_forcing_chrome_bookmark_test_client.h"
#include "chrome/browser/bookmarks/managed_bookmark_service_factory.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/extensions/extension_garbage_collector_factory.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/external_provider_manager.h"
#include "chrome/browser/extensions/shared_module_service_factory.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/extensions/updater/extension_updater.h"
#include "chrome/browser/policy/chrome_browser_policy_connector.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/browser/signin/chrome_signin_client_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/browser/signin/test_signin_client_builder.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/trusted_vault/trusted_vault_service_factory.h"
#include "chrome/common/buildflags.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/crx_file/crx_verifier.h"
#include "components/policy/core/common/policy_service_impl.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/sync/test/test_sync_service.h"
#include "components/sync_preferences/pref_service_mock_factory.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_task_environment.h"
#include "extensions/browser/delayed_install_manager.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/load_error_reporter.h"
#include "extensions/browser/pref_names.h"
#include "extensions/browser/shared_module_service.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/extensions_client.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/app_mode/kiosk_cryptohome_remover.h"
#include "chrome/browser/ash/extensions/install_limiter.h"
#include "chrome/browser/browser_process.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "components/user_manager/fake_user_manager_delegate.h"
#include "components/user_manager/user_manager_impl.h"
#endif

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

namespace extensions {

namespace {

std::unique_ptr<KeyedService> CreateTestSyncService(
    content::BrowserContext* context) {
  return std::make_unique<syncer::TestSyncService>();
}

// Create a testing profile according to |params|.
TestingProfile* BuildTestingProfile(
    TestingProfileManager& testing_profile_manager,
    ExtensionServiceTestBase::ExtensionServiceInitParams params,
    policy::PolicyService* policy_service) {
  TestingProfile::Builder profile_builder;

  base::FilePath profile_dir =
      testing_profile_manager.GetProfilePath(profile_builder.profile_name());
  if (base::File::Error error = base::File::FILE_OK;
      !base::CreateDirectoryAndGetError(profile_dir, &error)) {
    LOG(ERROR) << "Failed to create profile directory: " << error;
    return nullptr;
  }

  // If pref_file is empty, TestingProfile automatically creates
  // sync_preferences::TestingPrefServiceSyncable instance.
  if (params.prefs_content.has_value()) {
    base::FilePath prefs_path =
        profile_dir.Append(chrome::kPreferencesFilename);
    if (!base::WriteFile(prefs_path, params.prefs_content.value())) {
      LOG(ERROR) << "Failed to write a prefs file";
      return nullptr;
    }

    // Create a PrefService that only contains user defined preference values
    // and policies.
    sync_preferences::PrefServiceMockFactory factory;
    factory.SetUserPrefsFile(
        prefs_path, base::SingleThreadTaskRunner::GetCurrentDefault().get());
    factory.SetManagedPolicies(policy_service,
                               g_browser_process->browser_policy_connector());
    scoped_refptr<user_prefs::PrefRegistrySyncable> registry(
        new user_prefs::PrefRegistrySyncable);
    std::unique_ptr<sync_preferences::PrefServiceSyncable> prefs(
        factory.CreateSyncable(registry.get()));
    RegisterUserProfilePrefs(registry.get());
    profile_builder.SetPrefService(std::move(prefs));
  }

  base::FilePath extensions_install_dir =
      profile_dir.AppendASCII(kInstallDirectoryName);
  if (!base::DeletePathRecursively(extensions_install_dir)) {
    LOG(ERROR) << "Failed to clean extensions directory";
    return nullptr;
  }
  if (params.extensions_dir.empty()) {
    if (base::File::Error error = base::File::FILE_OK;
        !base::CreateDirectoryAndGetError(extensions_install_dir, &error)) {
      LOG(ERROR) << "Failed to create extensions directory: " << error;
      return nullptr;
    }
  } else {
    if (!base::CopyDirectory(params.extensions_dir, extensions_install_dir,
                             true)) {
      LOG(ERROR) << "Failed to copy extensions directory";
      return nullptr;
    }
  }

  // Only perform cleanup and copying of unpacked extensions if the path exists
  // for the test since this is less common than for packed extensions.
  if (base::PathExists(params.unpacked_extensions_dir)) {
    base::FilePath unpacked_extensions_install_dir =
        profile_dir.AppendASCII(kUnpackedInstallDirectoryName);
    if (!base::DeletePathRecursively(unpacked_extensions_install_dir)) {
      LOG(ERROR) << "Failed to clean unpacked extensions directory";
      return nullptr;
    }
    if (params.unpacked_extensions_dir.empty()) {
      if (base::File::Error error = base::File::FILE_OK;
          !base::CreateDirectoryAndGetError(unpacked_extensions_install_dir,
                                            &error)) {
        LOG(ERROR) << "Failed to create unpacked extensions directory: "
                   << error;
        return nullptr;
      }
    } else {
      if (!base::CopyDirectory(params.unpacked_extensions_dir,
                               unpacked_extensions_install_dir, true)) {
        LOG(ERROR) << "Failed to copy unpacked extensions directory";
        return nullptr;
      }
    }
  }

  if (params.profile_is_supervised) {
    profile_builder.SetIsSupervisedProfile();
  }

  if (params.profile_is_guest) {
    profile_builder.SetGuestSession();
  }

  if (params.enable_bookmark_model) {
    if (params.force_desktop_bookmark_behavior) {
      profile_builder.AddTestingFactory(
          BookmarkModelFactory::GetInstance(),
          DesktopForcingChromeBookmarkTestClient::GetTestingFactory());
    } else {
      profile_builder.AddTestingFactory(
          BookmarkModelFactory::GetInstance(),
          BookmarkModelFactory::GetDefaultFactory());
    }
    profile_builder.AddTestingFactory(
        ManagedBookmarkServiceFactory::GetInstance(),
        ManagedBookmarkServiceFactory::GetDefaultFactory());
  }

  profile_builder.AddTestingFactory(
      ChromeSigninClientFactory::GetInstance(),
      base::BindRepeating(&signin::BuildTestSigninClient));
  profile_builder.AddTestingFactories(
      IdentityTestEnvironmentProfileAdaptor::
          GetIdentityTestEnvironmentFactories());
  if (params.use_test_sync_service) {
    profile_builder.AddTestingFactory(
        SyncServiceFactory::GetInstance(),
        base::BindRepeating(&CreateTestSyncService));
  } else {
    profile_builder.AddTestingFactory(
        TrustedVaultServiceFactory::GetInstance(),
        TrustedVaultServiceFactory::GetDefaultFactory());
    profile_builder.AddTestingFactory(SyncServiceFactory::GetInstance(),
                                      SyncServiceFactory::GetDefaultFactory());
  }

  profile_builder.AddTestingFactory(
      ExtensionGarbageCollectorFactory::GetInstance(),
      base::BindRepeating(&ExtensionGarbageCollectorFactory::BuildInstanceFor));

  profile_builder.AddTestingFactories(std::move(params.testing_factories));

  auto user_name = base::UTF8ToUTF16(profile_builder.profile_name());
  return testing_profile_manager.CreateTestingProfile(
      std::move(profile_builder), user_name, /*avatar_id=*/0);
}

}  // namespace

ExtensionServiceTestBase::ExtensionServiceInitParams::
    ExtensionServiceInitParams() = default;

ExtensionServiceTestBase::ExtensionServiceInitParams::
    ExtensionServiceInitParams(ExtensionServiceInitParams&& other) = default;

ExtensionServiceTestBase::ExtensionServiceInitParams::
    ~ExtensionServiceInitParams() = default;

bool ExtensionServiceTestBase::ExtensionServiceInitParams::
    SetPrefsContentFromFile(const base::FilePath& filepath) {
  std::string content;
  if (!base::ReadFileToString(filepath, &content)) {
    return false;
  }
  prefs_content.emplace(std::move(content));
  return true;
}

bool ExtensionServiceTestBase::ExtensionServiceInitParams::
    ConfigureByTestDataDirectory(const base::FilePath& filepath) {
  if (!SetPrefsContentFromFile(filepath.Append(chrome::kPreferencesFilename))) {
    return false;
  }
  extensions_dir = filepath.AppendASCII(kInstallDirectoryName);
  unpacked_extensions_dir = filepath.AppendASCII(kUnpackedInstallDirectoryName);
  return true;
}

ExtensionServiceTestBase::ExtensionServiceTestBase()
    : ExtensionServiceTestBase(
          std::make_unique<content::BrowserTaskEnvironment>(
              base::test::TaskEnvironment::MainThreadType::IO)) {}

ExtensionServiceTestBase::ExtensionServiceTestBase(
    std::unique_ptr<content::BrowserTaskEnvironment> task_environment)
    : task_environment_(std::move(task_environment)),
#if BUILDFLAG(IS_CHROMEOS)
      user_manager_(std::make_unique<user_manager::UserManagerImpl>(
          std::make_unique<user_manager::FakeUserManagerDelegate>(),
          TestingBrowserProcess::GetGlobal()->local_state())),
#endif
      verifier_format_override_(crx_file::VerifierFormat::CRX3) {
  base::FilePath test_data_dir;
  if (!base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir)) {
    ADD_FAILURE();
    return;
  }
  data_dir_ = test_data_dir.AppendASCII("extensions");

  policy_service_ = std::make_unique<policy::PolicyServiceImpl>(
      std::vector<
          raw_ptr<policy::ConfigurationPolicyProvider, VectorExperimental>>{
          &policy_provider_});
  feature_list_.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{
          // Allow unpacked extensions without developer mode for testing.
          extensions_features::kExtensionDisableUnsupportedDeveloper,
          // Background Host creates a renderer host and holds it,
          // but this test is configured to use in-process renderers.
          // This combination is problematic on destruction order,
          // so to workaround it, disabling to destroy profile on browser
          // close. This should be fine, because the created renderer host
          // is destroyed with ExtensionHost destroyed with Profile.
          features::kDestroyProfileOnBrowserClose});
}

ExtensionServiceTestBase::~ExtensionServiceTestBase() = default;

void ExtensionServiceTestBase::InitializeExtensionService(
    ExtensionServiceTestBase::ExtensionServiceInitParams params) {
  CHECK(is_setup_called_);
  const bool is_first_run = params.is_first_run;
  const bool autoupdate_enabled = params.autoupdate_enabled;
  const bool extensions_enabled = params.extensions_enabled;
  const bool enable_install_limiter = params.enable_install_limiter;

  profile_ = BuildTestingProfile(*testing_profile_manager_, std::move(params),
                                 policy_service_.get());
  extensions_install_dir_ =
      profile_->GetPath().AppendASCII(kInstallDirectoryName);
  unpacked_install_dir_ =
      profile_->GetPath().AppendASCII(kUnpackedInstallDirectoryName);

  CreateExtensionService(is_first_run, autoupdate_enabled, extensions_enabled,
                         enable_install_limiter);
  registry_ = ExtensionRegistry::Get(profile());
  registrar_ = ExtensionRegistrar::Get(profile());
}

bool ExtensionServiceTestBase::ShouldAllowMV2Extensions() {
  return true;
}

void ExtensionServiceTestBase::InitializeEmptyExtensionService() {
  ExtensionServiceInitParams params;
  params.prefs_content = "";
  InitializeExtensionService(std::move(params));
}

void ExtensionServiceTestBase::InitializeGoodInstalledExtensionService() {
  ExtensionServiceInitParams params;
  ASSERT_TRUE(
      params.ConfigureByTestDataDirectory(data_dir().AppendASCII("good")));
  InitializeExtensionService(std::move(params));
}

void ExtensionServiceTestBase::InitializeExtensionServiceWithUpdater() {
  ExtensionServiceInitParams params;
  params.autoupdate_enabled = true;
  InitializeExtensionService(std::move(params));
  auto* updater = ExtensionUpdater::Get(profile());
  CHECK(updater->enabled());
  updater->Start();
}

void ExtensionServiceTestBase::
    InitializeExtensionServiceWithExtensionsDisabled() {
  ExtensionServiceInitParams params;
  params.extensions_enabled = false;
  InitializeExtensionService(std::move(params));
}

size_t ExtensionServiceTestBase::GetPrefKeyCount() {
  const base::DictValue& dict =
      profile()->GetPrefs()->GetDict(pref_names::kExtensions);
  return dict.size();
}

void ExtensionServiceTestBase::ValidatePrefKeyCount(size_t count) {
  EXPECT_EQ(count, GetPrefKeyCount());
}

testing::AssertionResult ExtensionServiceTestBase::ValidateBooleanPref(
    const std::string& extension_id,
    const std::string& pref_path,
    bool expected_val) {
  std::string msg =
      base::StringPrintf("while checking: %s %s == %s", extension_id.c_str(),
                         pref_path.c_str(), base::ToString(expected_val));

  PrefService* prefs = profile()->GetPrefs();
  const base::DictValue& dict = prefs->GetDict(pref_names::kExtensions);

  const base::DictValue* pref = dict.FindDict(extension_id);
  if (!pref) {
    return testing::AssertionFailure()
           << "extension pref does not exist " << msg;
  }

  std::optional<bool> val = pref->FindBoolByDottedPath(pref_path);
  if (!val.has_value()) {
    return testing::AssertionFailure()
           << pref_path << " pref not found " << msg;
  }

  return expected_val == val.value() ? testing::AssertionSuccess()
                                     : testing::AssertionFailure()
                                           << "base::Value is incorrect "
                                           << msg;
}

void ExtensionServiceTestBase::ValidateIntegerPref(
    const std::string& extension_id,
    const std::string& pref_path,
    int expected_val) {
  std::string msg = base::StringPrintf(
      "while checking: %s %s == %s", extension_id.c_str(), pref_path.c_str(),
      base::NumberToString(expected_val).c_str());

  PrefService* prefs = profile()->GetPrefs();
  const base::DictValue& dict = prefs->GetDict(pref_names::kExtensions);
  const base::DictValue* pref = dict.FindDict(extension_id);
  ASSERT_TRUE(pref) << msg;
  EXPECT_EQ(expected_val, pref->FindIntByDottedPath(pref_path)) << msg;
}

void ExtensionServiceTestBase::ValidateStringPref(
    const std::string& extension_id,
    const std::string& pref_path,
    const std::string& expected_val) {
  std::string msg = base::StringPrintf("while checking: %s.manifest.%s == %s",
                                       extension_id.c_str(), pref_path.c_str(),
                                       expected_val.c_str());

  const base::DictValue& dict =
      profile()->GetPrefs()->GetDict(pref_names::kExtensions);
  std::string manifest_path = extension_id + ".manifest";
  const base::DictValue* pref = dict.FindDictByDottedPath(manifest_path);
  ASSERT_TRUE(pref) << msg;
  const std::string* val = pref->FindStringByDottedPath(pref_path);
  ASSERT_TRUE(val) << msg;
  EXPECT_EQ(expected_val, *val) << msg;
}

void ExtensionServiceTestBase::SetUp() {
  is_setup_called_ = true;
  CHECK(temp_dir_.CreateUniqueTempDir());

  LoadErrorReporter::GetInstance()->ClearErrors();

  // Force TabManager/TabLifecycleUnitSource creation.
  g_browser_process->resource_coordinator_parts();

  testing_profile_manager_ = std::make_unique<TestingProfileManager>(
      TestingBrowserProcess::GetGlobal());
  CHECK(testing_profile_manager_->SetUp());

  // Update the webstore update url. Some tests leave it set to a non-default
  // webstore_update_url_. This can make extension_urls::IsWebstoreUpdateUrl
  // return a false negative.
  ExtensionsClient::Get()->InitializeWebStoreUrls(
      base::CommandLine::ForCurrentProcess());

#if BUILDFLAG(IS_CHROMEOS)
  // TODO(b/308107135) own KioskController instead of KioskAppManager.
  // A test might have initialized a `KioskAppManager` already.
  if (!ash::KioskChromeAppManager::IsInitialized()) {
    kiosk_cryptohome_remover_ = std::make_unique<ash::KioskCryptohomeRemover>(
        TestingBrowserProcess::GetGlobal()->local_state());
    kiosk_chrome_app_manager_ = std::make_unique<ash::KioskChromeAppManager>(
        TestingBrowserProcess::GetGlobal()->local_state(),
        TestingBrowserProcess::GetGlobal()->shared_url_loader_factory(),
        kiosk_cryptohome_remover_.get());
  }
#endif

  if (ShouldAllowMV2Extensions()) {
    mv2_enabler_.emplace();
  }
}

void ExtensionServiceTestBase::TearDown() {
  if (profile_) {
    content::StoragePartitionConfig default_storage_partition_config =
        content::StoragePartitionConfig::CreateDefault(profile());
    auto* partition = profile_->GetStoragePartition(
        default_storage_partition_config, /*can_create=*/false);
    if (partition) {
      partition->WaitForDeletionTasksForTesting();
    }
  }
  policy_provider_.Shutdown();
#if BUILDFLAG(IS_CHROMEOS)
  kiosk_chrome_app_manager_.reset();
#endif
  DeleteProfile();
  testing_profile_manager_.reset();
}

void ExtensionServiceTestBase::SetUpTestSuite() {
  // Safe to call multiple times.
  LoadErrorReporter::Init(false);  // no noisy errors.
}

// These are declared in the .cc so that all inheritors don't need to know
// that TestingProfile derives Profile derives BrowserContext.
content::BrowserContext* ExtensionServiceTestBase::browser_context() {
  return profile();
}

Profile* ExtensionServiceTestBase::profile() {
  return profile_.get();
}

TestingProfile* ExtensionServiceTestBase::testing_profile() {
  return profile_.get();
}

void ExtensionServiceTestBase::DeleteProfile() {
  registrar_ = nullptr;
  registry_ = nullptr;
  service_ = nullptr;
  extensions_install_dir_ = base::FilePath();
  unpacked_install_dir_ = base::FilePath();
  profile_ = nullptr;
  testing_profile_manager_->DeleteAllTestingProfiles();
}

void ExtensionServiceTestBase::SetGuestSessionOnProfile(bool guest_session) {
  profile_->SetGuestSession(guest_session);
}

sync_preferences::TestingPrefServiceSyncable*
ExtensionServiceTestBase::testing_pref_service() {
  return profile_->GetTestingPrefService();
}

void ExtensionServiceTestBase::CreateExtensionService(
    bool is_first_run,
    bool autoupdate_enabled,
    bool extensions_enabled,
    bool enable_install_limiter) {
  TestExtensionSystem* system =
      static_cast<TestExtensionSystem*>(ExtensionSystem::Get(profile()));
  if (!is_first_run) {
    ExtensionPrefs::Get(profile())->SetAlertSystemFirstRun();
  }

  service_ = system->CreateExtensionService(
      base::CommandLine::ForCurrentProcess(), extensions_install_dir_,
      unpacked_install_dir_, autoupdate_enabled, extensions_enabled);

  ComponentLoader::Get(profile())->set_ignore_allowlist_for_testing(true);

  // When we start up, we want to make sure there is no external provider,
  // since the ExtensionService on Windows will use the Registry as a default
  // provider and if there is something already registered there then it will
  // interfere with the tests. Those tests that need an external provider
  // will register one specifically.
  ExternalProviderManager::Get(profile())->ClearProvidersForTesting();

  DelayedInstallManager::Get(profile())->RegisterInstallGate(
      ExtensionPrefs::DelayReason::kWaitForImports,
      SharedModuleServiceFactory::GetForBrowserContext(profile()));

#if BUILDFLAG(IS_CHROMEOS)
  if (!enable_install_limiter) {
    auto* install_limiter =
        InstallLimiter::Get(profile()->GetOriginalProfile());
    if (install_limiter) {
      install_limiter->DisableForTest();
    }
  }
#endif
}

}  // namespace extensions
