// Copyright 2018 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 <optional>

#include "base/check_deref.h"
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/content_verifier_test_utils.h"
#include "chrome/browser/extensions/extension_management_test_util.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/external_provider_manager.h"
#include "chrome/browser/extensions/updater/chrome_update_client_config.h"
#include "chrome/browser/extensions/updater/extension_update_client_base_browsertest.h"
#include "chrome/browser/extensions/updater/extension_updater.h"
#include "chrome/browser/extensions/updater/test_update_client_event_waiter.h"
#include "chrome/browser/lifetime/termination_notification.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/update_client/net/url_loader_post_interceptor.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/content_verifier/content_verifier.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/external_install_info.h"
#include "extensions/browser/mock_external_provider.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/browser/updater/extension_downloader.h"
#include "extensions/browser/updater/manifest_fetch_data.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension_updater_uma.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/mojom/manifest.mojom-shared.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
#endif

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

using extensions::mojom::ManifestLocation;

namespace extensions {

namespace {

const char kExtensionId[] = "aohghmighlieiainnegkcijnfilokake";

}  // namespace

class UpdateServiceTest : public ExtensionUpdateClientBaseTest {
 public:
  UpdateServiceTest() = default;
  ~UpdateServiceTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    ExtensionUpdateClientBaseTest::SetUpCommandLine(command_line);
    command_line->AppendSwitchASCII(
        switches::kExtensionContentVerification,
        switches::kExtensionContentVerificationEnforce);
  }

  bool ShouldEnableContentVerification() override { return true; }

  void ExpectProfileKeepAlive(bool expected) {
    if (!base::FeatureList::IsEnabled(features::kDestroyProfileOnBrowserClose))
      return;
    EXPECT_EQ(expected,
              g_browser_process->profile_manager()->HasKeepAliveForTesting(
                  profile(), ProfileKeepAliveOrigin::kExtensionUpdater));
  }

  std::optional<base::DictValue> GetRequest(size_t index) {
    const std::vector<
        update_client::URLLoaderPostInterceptor::InterceptedRequest>& requests =
        update_interceptor_->GetRequests();
    if (requests.size() < index) {
      return std::nullopt;
    }

    const std::string update_request = std::get<0>(requests[index]);
    std::optional<base::Value> root = base::JSONReader::Read(
        update_request, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
    if (!root) {
      return std::nullopt;
    }

    return std::move(root.value()).TakeDict();
  }

  base::DictValue GetApp(const base::DictValue& root, size_t index) {
    const base::ListValue* app_list =
        root.FindDict("request")->FindList("apps");
    EXPECT_GT(app_list->size(), index);
    return CHECK_DEREF(app_list)[index].Clone().TakeDict();
  }

  base::DictValue GetFirstApp(const base::DictValue& root) {
    return GetApp(root, 0);
  }

  ExtensionUpdater* extension_updater() {
    return ExtensionUpdater::Get(profile());
  }
};

IN_PROC_BROWSER_TEST_F(UpdateServiceTest, NoUpdate) {
  // Verify that UpdateService runs correctly when there's no update.
  base::ScopedAllowBlockingForTesting allow_io;
  base::HistogramTester histogram_tester;

  // Mock a no-update response.
  ASSERT_TRUE(update_interceptor_->ExpectRequest(
      std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
      test_data_dir_.AppendASCII("updater/updatecheck_reply_noupdate_1.json")));

  const base::FilePath crx_path = test_data_dir_.AppendASCII("updater/v1.crx");
  const Extension* extension =
      InstallExtension(crx_path, 1, ManifestLocation::kExternalPolicyDownload);
  ASSERT_TRUE(extension);
  EXPECT_EQ(kExtensionId, extension->id());

  extensions::ExtensionUpdater::CheckParams params;
  params.ids = {kExtensionId};
  extension_updater()->CheckNow(std::move(params));

  // UpdateService should emit a not-updated event.
  EXPECT_EQ(update_client::ComponentState::kUpToDate,
            WaitOnComponentUpdaterCompleteEvent(kExtensionId));

  ASSERT_EQ(1, update_interceptor_->GetCount())
      << update_interceptor_->GetRequestsAsString();

  // No update, thus no download nor ping activities.
  EXPECT_EQ(0, get_interceptor_count());
  EXPECT_EQ(0, ping_interceptor_->GetCount())
      << ping_interceptor_->GetRequestsAsString();

  const std::optional<base::DictValue> root = GetRequest(0);
  ASSERT_TRUE(root);
  const base::DictValue& app = GetFirstApp(root.value());
  EXPECT_EQ(kExtensionId, CHECK_DEREF(app.FindString("appid")));
  EXPECT_EQ("0.10", CHECK_DEREF(app.FindString("version")));
  EXPECT_TRUE(app.FindBool("enabled").value_or(false));
}

IN_PROC_BROWSER_TEST_F(UpdateServiceTest, UpdateCheckError) {
  // Verify that UpdateService works correctly when there's an error in the
  // update check phase.
  base::ScopedAllowBlockingForTesting allow_io;
  base::HistogramTester histogram_tester;

  // Mock an update check error.
    ASSERT_TRUE(update_interceptor_->ExpectRequest(
        std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
        net::HTTP_FORBIDDEN));

  const base::FilePath crx_path = test_data_dir_.AppendASCII("updater/v1.crx");
  const Extension* extension =
      InstallExtension(crx_path, 1, ManifestLocation::kExternalPolicyDownload);
  ASSERT_TRUE(extension);
  EXPECT_EQ(kExtensionId, extension->id());

  extensions::ExtensionUpdater::CheckParams params;
  params.ids = {kExtensionId};
  extension_updater()->CheckNow(std::move(params));

  // UpdateService should emit an error update event.
  EXPECT_EQ(update_client::ComponentState::kUpdateError,
            WaitOnComponentUpdaterCompleteEvent(kExtensionId));

  ASSERT_EQ(1, update_interceptor_->GetCount())
      << update_interceptor_->GetRequestsAsString();

  // Error, thus no download nor ping activities.
  EXPECT_EQ(0, get_interceptor_count());
  EXPECT_EQ(0, ping_interceptor_->GetCount())
      << ping_interceptor_->GetRequestsAsString();

  const std::optional<base::DictValue> root = GetRequest(0);
  ASSERT_TRUE(root);
  const base::DictValue& app = GetFirstApp(root.value());
  EXPECT_EQ(kExtensionId, CHECK_DEREF(app.FindString("appid")));
  EXPECT_EQ("0.10", CHECK_DEREF(app.FindString("version")));
  EXPECT_TRUE(app.FindBool("enabled").value_or(false));
}

IN_PROC_BROWSER_TEST_F(UpdateServiceTest, TwoUpdateCheckErrors) {
  // Verify that the UMA counters are emitted properly when there are 2 update
  // checks with different number of extensions, both of which result in errors.
  base::ScopedAllowBlockingForTesting allow_io;
  base::HistogramTester histogram_tester;

  // Mock update check errors.
    ASSERT_TRUE(update_interceptor_->ExpectRequest(
        std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
        net::HTTP_NOT_MODIFIED));
    ASSERT_TRUE(update_interceptor_->ExpectRequest(
        std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
        net::HTTP_USE_PROXY));

  const base::FilePath crx_path1 = test_data_dir_.AppendASCII("updater/v1.crx");
  const base::FilePath crx_path2 = test_data_dir_.AppendASCII("updater/v2.crx");
  const Extension* extension1 =
      InstallExtension(crx_path1, 1, ManifestLocation::kExternalPolicyDownload);
  const Extension* extension2 =
      InstallExtension(crx_path2, 1, ManifestLocation::kExternalPolicyDownload);
  ASSERT_TRUE(extension1 && extension2);

  base::RunLoop run_loop1;
  extensions::ExtensionUpdater::CheckParams params1;
  params1.ids = {extension1->id(), extension2->id()};
  params1.callback = run_loop1.QuitClosure();
  extension_updater()->CheckNow(std::move(params1));
  run_loop1.Run();

  base::RunLoop run_loop2;
  extensions::ExtensionUpdater::CheckParams params2;
  params2.ids = {extension1->id()};
  params2.callback = run_loop2.QuitClosure();
  extension_updater()->CheckNow(std::move(params2));
  run_loop2.Run();

  ASSERT_EQ(2, update_interceptor_->GetCount())
      << update_interceptor_->GetRequestsAsString();

  // Error, thus no download nor ping activities.
  EXPECT_EQ(0, get_interceptor_count());
  EXPECT_EQ(0, ping_interceptor_->GetCount())
      << ping_interceptor_->GetRequestsAsString();
}

IN_PROC_BROWSER_TEST_F(UpdateServiceTest, SuccessfulUpdate) {
  base::ScopedAllowBlockingForTesting allow_io;
  base::HistogramTester histogram_tester;

  // Mock an update response.
    const base::FilePath update_response =
        test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.json");
    const base::FilePath ping_response =
        test_data_dir_.AppendASCII("updater/ping_reply_1.json");
    ASSERT_TRUE(update_interceptor_->ExpectRequest(
        std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
        update_response));
    ASSERT_TRUE(ping_interceptor_->ExpectRequest(
        std::make_unique<update_client::PartialMatch>(R"("eventtype":)"),
        ping_response));

  const base::FilePath crx_path = test_data_dir_.AppendASCII("updater/v1.crx");
  set_interceptor_hook(base::BindLambdaForTesting(
      [&](content::URLLoaderInterceptor::RequestParams* params) {
        if (params->url_request.url.GetPath() != "/download/v1.crx") {
          return false;
        }

        content::URLLoaderInterceptor::WriteResponse(crx_path,
                                                     params->client.get());
        return true;
      }));

  ExpectProfileKeepAlive(false);

  const Extension* extension =
      InstallExtension(crx_path, 1, ManifestLocation::kExternalPolicyDownload);
  ASSERT_TRUE(extension);
  EXPECT_EQ(kExtensionId, extension->id());

  base::RunLoop run_loop;

  extensions::ExtensionUpdater::CheckParams params;
  params.ids = {kExtensionId};
  params.callback = run_loop.QuitClosure();
  extension_updater()->CheckNow(std::move(params));

  ExpectProfileKeepAlive(true);

  EXPECT_EQ(update_client::ComponentState::kUpdated,
            WaitOnComponentUpdaterCompleteEvent(kExtensionId));

  run_loop.Run();

  ASSERT_EQ(1, update_interceptor_->GetCount())
      << update_interceptor_->GetRequestsAsString();
  EXPECT_EQ(1, get_interceptor_count());

  const std::optional<base::DictValue> root = GetRequest(0);
  ASSERT_TRUE(root);
  const base::DictValue& app = GetFirstApp(root.value());
  EXPECT_EQ(kExtensionId, CHECK_DEREF(app.FindString("appid")));
  EXPECT_EQ("0.10", CHECK_DEREF(app.FindString("version")));
  EXPECT_TRUE(app.FindBool("enabled").value_or(false));
}

IN_PROC_BROWSER_TEST_F(UpdateServiceTest, PolicyCorrupted) {
  base::ScopedAllowBlockingForTesting allow_io;

  ExtensionSystem* system = ExtensionSystem::Get(profile());
  const base::FilePath update_response =
      test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.json");
  const base::FilePath ping_response =
      test_data_dir_.AppendASCII("updater/ping_reply_1.json");
  ASSERT_TRUE(update_interceptor_->ExpectRequest(
      std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
      update_response));
  ASSERT_TRUE(ping_interceptor_->ExpectRequest(
      std::make_unique<update_client::PartialMatch>(R"("eventtype":)"),
      ping_response));

  const base::FilePath crx_path = test_data_dir_.AppendASCII("updater/v1.crx");
  set_interceptor_hook(base::BindLambdaForTesting(
      [&](content::URLLoaderInterceptor::RequestParams* params) {
        if (params->url_request.url.GetPath() != "/download/v1.crx") {
          return false;
        }

        content::URLLoaderInterceptor::WriteResponse(crx_path,
                                                     params->client.get());
        return true;
      }));

  // Setup fake policy and update check objects.
  content_verifier_test::ForceInstallProvider policy(kExtensionId);
  system->management_policy()->RegisterProvider(&policy);
  ExternalProviderManager* external_provider_manager =
      ExternalProviderManager::Get(profile());
  auto external_provider = std::make_unique<MockExternalProvider>(
      external_provider_manager, ManifestLocation::kExternalPolicyDownload);
  external_provider->UpdateOrAddExtension(
      std::make_unique<ExternalInstallInfoUpdateUrl>(
          kExtensionId, std::string() /* install_parameter */,
          extension_urls::GetWebstoreUpdateUrl(),
          ManifestLocation::kExternalPolicyDownload, 0 /* creation_flags */,
          true /* mark_acknowledged */));
  external_provider_manager->AddProviderForTesting(
      std::move(external_provider));

  const Extension* extension =
      InstallExtension(crx_path, 1, ManifestLocation::kExternalPolicyDownload);
  ASSERT_TRUE(extension);
  EXPECT_EQ(kExtensionId, extension->id());

  TestExtensionRegistryObserver registry_observer(
      ExtensionRegistry::Get(profile()), kExtensionId);
  ContentVerifier* verifier = system->content_verifier();
  verifier->VerifyFailedForTest(kExtensionId, ContentVerifyJob::HASH_MISMATCH);

  // Make sure the extension first got disabled due to corruption.
  EXPECT_TRUE(registry_observer.WaitForExtensionUnloaded());
  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
  DisableReasonSet reasons = prefs->GetDisableReasons(kExtensionId);
  EXPECT_TRUE(reasons.contains(disable_reason::DISABLE_CORRUPTED));

  // Make sure the extension then got re-installed, and that after reinstall it
  // is no longer disabled due to corruption.
  EXPECT_EQ(update_client::ComponentState::kUpdated,
            WaitOnComponentUpdaterCompleteEvent(kExtensionId));

  reasons = prefs->GetDisableReasons(kExtensionId);
  EXPECT_FALSE(reasons.contains(disable_reason::DISABLE_CORRUPTED));

  ASSERT_EQ(1, update_interceptor_->GetCount())
      << update_interceptor_->GetRequestsAsString();
  EXPECT_EQ(1, get_interceptor_count());

  // Make sure that the update check request is formed correctly when the
  // extension is corrupted:
  // - version="0.0.0.0"
  // - installsource="reinstall"
  // - installedby="policy"
  // - enabled="0"
  // - <disabled reason="1024"/>
  const std::optional<base::DictValue> root = GetRequest(0);
  ASSERT_TRUE(root);
  const base::DictValue& app = GetFirstApp(root.value());
  EXPECT_EQ(kExtensionId, CHECK_DEREF(app.FindString("appid")));
  EXPECT_EQ("0.0.0.0", CHECK_DEREF(app.FindString("version")));
  EXPECT_EQ("reinstall", CHECK_DEREF(app.FindString("installsource")));
  EXPECT_EQ("policy", CHECK_DEREF(app.FindString("installedby")));
  EXPECT_FALSE(app.FindBool("enabled").value_or(true));
  const base::DictValue& disabled =
      CHECK_DEREF(app.FindList("disabled"))[0].GetDict();
  EXPECT_EQ(disable_reason::DISABLE_CORRUPTED, disabled.FindInt("reason"));
}

IN_PROC_BROWSER_TEST_F(UpdateServiceTest, UninstallExtensionWhileUpdating) {
  // This test is to verify that the extension updater engine (update client)
  // works correctly when an extension is uninstalled when the extension updater
  // is in progress.
  base::ScopedAllowBlockingForTesting allow_io;

  const base::FilePath crx_path = test_data_dir_.AppendASCII("updater/v1.crx");

  const Extension* extension =
      InstallExtension(crx_path, 1, ManifestLocation::kExternalPolicyDownload);
  ASSERT_TRUE(extension);
  EXPECT_EQ(kExtensionId, extension->id());

  base::RunLoop run_loop;

  extensions::ExtensionUpdater::CheckParams params;
  params.ids = {kExtensionId};
  params.callback = run_loop.QuitClosure();
  extension_updater()->CheckNow(std::move(params));

  // Uninstall the extension right before the message loop is executed to
  // emulate uninstalling an extension in the middle of an extension update.
  extension_registrar()->UninstallExtension(
      kExtensionId, extensions::UNINSTALL_REASON_COMPONENT_REMOVED, nullptr);

  // Update client should issue an update error event for this extension.
  ASSERT_EQ(update_client::ComponentState::kUpdateError,
            WaitOnComponentUpdaterCompleteEvent(kExtensionId));

  run_loop.Run();

  EXPECT_EQ(0, update_interceptor_->GetCount())
      << update_interceptor_->GetRequestsAsString();
  EXPECT_EQ(0, get_interceptor_count());
}

class PolicyUpdateServiceTest : public ExtensionUpdateClientBaseTest {
 public:
  PolicyUpdateServiceTest() = default;
  ~PolicyUpdateServiceTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    ExtensionUpdateClientBaseTest::SetUpCommandLine(command_line);
    command_line->AppendSwitchASCII(
        switches::kExtensionContentVerification,
        switches::kExtensionContentVerificationEnforce);
  }

  void SetUpInProcessBrowserTestFixture() override {
    ExtensionUpdateClientBaseTest::SetUpInProcessBrowserTestFixture();

    policy_provider_.SetDefaultReturns(
        /*is_initialization_complete_return=*/true,
        /*is_first_policy_load_complete_return=*/true);

    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
        &policy_provider_);
    // ExtensionManagementPolicyUpdater requires a single-threaded context to
    // call RunLoop::RunUntilIdle internally, and it isn't ready at this setup
    // moment.
    base::test::TaskEnvironment env;
    ExtensionManagementPolicyUpdater management_policy(&policy_provider_);
    management_policy.SetIndividualExtensionAutoInstalled(
        id_, extension_urls::kChromeWebstoreUpdateURL, true /* forced */);

    // The policy will force the new install of an extension, which the
    // component updater doesn't support yet. We still rely on the old updater
    // to install a new extension.
    const base::FilePath crx_path =
        test_data_dir_.AppendASCII("updater/v1.crx");
    ExtensionDownloader::set_test_delegate(&downloader_);
    downloader_.AddResponse(id_, "0.10", crx_path);
  }

  void SetUpNetworkInterceptors() override {
    ExtensionUpdateClientBaseTest::SetUpNetworkInterceptors();

    // On some platforms (e.g. Android) this method is called before
    // test_data_dir_ is initialized.
    if (test_data_dir_.empty()) {
      base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_);
      test_data_dir_ = test_data_dir_.AppendASCII("extensions");
    }

    const base::FilePath crx_path =
        test_data_dir_.AppendASCII("updater/v1.crx");
    set_interceptor_hook(base::BindLambdaForTesting(
        [=](content::URLLoaderInterceptor::RequestParams* params) {
          if (params->url_request.url.GetPath() != "/download/v1.crx") {
            return false;
          }

          content::URLLoaderInterceptor::WriteResponse(crx_path,
                                                       params->client.get());
          return true;
        }));
    const base::FilePath update_response =
        test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.json");
    const base::FilePath ping_response =
        test_data_dir_.AppendASCII("updater/ping_reply_1.json");

    ASSERT_TRUE(update_interceptor_->ExpectRequest(
        std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
        update_response));
    ASSERT_TRUE(update_interceptor_->ExpectRequest(
        std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
        update_response));
    ASSERT_TRUE(update_interceptor_->ExpectRequest(
        std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
        update_response));
    ASSERT_TRUE(update_interceptor_->ExpectRequest(
        std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
        update_response));
    ASSERT_TRUE(ping_interceptor_->ExpectRequest(
        std::make_unique<update_client::PartialMatch>(R"("eventtype":)"),
        ping_response));
    ASSERT_TRUE(ping_interceptor_->ExpectRequest(
        std::make_unique<update_client::PartialMatch>(R"("eventtype":)"),
        ping_response));
    ASSERT_TRUE(ping_interceptor_->ExpectRequest(
        std::make_unique<update_client::PartialMatch>(R"("eventtype":)"),
        ping_response));
    ASSERT_TRUE(ping_interceptor_->ExpectRequest(
        std::make_unique<update_client::PartialMatch>(R"("eventtype":)"),
        ping_response));
  }

  std::vector<GURL> GetUpdateUrls() const override {
    return {
        https_server_for_update_.GetURL("/policy-updatehost/service/update")};
  }

  std::vector<GURL> GetPingUrls() const override {
    return {https_server_for_ping_.GetURL("/policy-pinghost/service/ping")};
  }

 protected:
  std::optional<base::DictValue> GetRequest(size_t index) {
    const std::vector<
        update_client::URLLoaderPostInterceptor::InterceptedRequest>& requests =
        update_interceptor_->GetRequests();
    if (requests.size() < index) {
      return std::nullopt;
    }

    const std::string update_request = std::get<0>(requests[index]);
    std::optional<base::Value> root = base::JSONReader::Read(
        update_request, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
    if (!root) {
      return std::nullopt;
    }

    return std::move(root.value()).TakeDict();
  }

  base::DictValue GetApp(const base::DictValue& root, size_t index) {
    const base::ListValue* app_list =
        root.FindDict("request")->FindList("apps");
    EXPECT_GT(app_list->size(), index);
    return CHECK_DEREF(app_list)[index].Clone().TakeDict();
  }

  base::DictValue GetFirstApp(const base::DictValue& root) {
    return GetApp(root, 0);
  }

  // The id of the extension we want to have force-installed.
  std::string id_ = "aohghmighlieiainnegkcijnfilokake";

 private:
#if BUILDFLAG(IS_CHROMEOS)
  // Set up managed environment.
  std::unique_ptr<ash::ScopedStubInstallAttributes> install_attributes_ =
      std::make_unique<ash::ScopedStubInstallAttributes>(
          ash::StubInstallAttributes::CreateCloudManaged("fake-domain.com",
                                                         "fake-id"));
#endif
  testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;
  content_verifier_test::DownloaderTestDelegate downloader_;
};

// Tests that if CheckForExternalUpdates() fails, then we retry reinstalling
// corrupted policy extensions. For example: if network is unavailable,
// CheckForExternalUpdates() will fail.
#if BUILDFLAG(IS_ANDROID)
// TODO(https://crbug.com/469417243): Fails on desktop android.
#define MAYBE_FailedUpdateRetries DISABLED_FailedUpdateRetries
#else
#define MAYBE_FailedUpdateRetries FailedUpdateRetries
#endif
IN_PROC_BROWSER_TEST_F(PolicyUpdateServiceTest, MAYBE_FailedUpdateRetries) {
  ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
  ContentVerifier* verifier =
      ExtensionSystem::Get(profile())->content_verifier();
  TestExtensionRegistryObserver install_observer(registry, id_);

  // Wait for the extension to be installed by the policy we set up in
  // SetUpInProcessBrowserTestFixture, but only if it's not already installed.
  if (!registry->GetInstalledExtension(id_)) {
    TestExtensionRegistryObserver registry_observer(registry, id_);
    EXPECT_TRUE(registry_observer.WaitForExtensionInstalled());
  }

  content_verifier_test::DelayTracker delay_tracker;
  TestExtensionRegistryObserver registry_observer(registry, id_);
  {
    base::AutoReset<bool> disable_scope =
        ExternalProviderManager::DisableExternalUpdatesForTesting();

    verifier->VerifyFailedForTest(id_, ContentVerifyJob::HASH_MISMATCH);
    EXPECT_TRUE(registry_observer.WaitForExtensionUnloaded());

    const std::vector<base::TimeDelta>& calls = delay_tracker.calls();
    ASSERT_EQ(1u, calls.size());
    EXPECT_EQ(base::TimeDelta(), delay_tracker.calls()[0]);

    delay_tracker.Proceed();
  }

  // Register the waiter before proceeding to trigger the reinstall.
  TestUpdateClientEventWaiter waiter(id_);
  AddUpdateClientObserver(&waiter);

  // Update ExtensionService again without disabling external updates.
  // The extension should now get installed.
  delay_tracker.StopWatching();
  delay_tracker.Proceed();

  EXPECT_EQ(update_client::ComponentState::kUpdated, waiter.Wait());
  RemoveUpdateClientObserver(&waiter);

  ASSERT_EQ(1, update_interceptor_->GetCount())
      << update_interceptor_->GetRequestsAsString();
  EXPECT_EQ(1, get_interceptor_count());

  // Make sure that the update check request is formed correctly when the
  // extension is corrupted:
  // - version="0.0.0.0"
  // - installsource="reinstall"
  // - installedby="policy"
  // - enabled="0"
  // - <disabled reason="1024"/>
  const std::optional<base::DictValue> root = GetRequest(0);
  ASSERT_TRUE(root);
  const base::DictValue& app = GetFirstApp(root.value());
  EXPECT_EQ(id_, CHECK_DEREF(app.FindString("appid")));
  EXPECT_EQ("0.0.0.0", CHECK_DEREF(app.FindString("version")));
  EXPECT_EQ("reinstall", CHECK_DEREF(app.FindString("installsource")));
  EXPECT_EQ("policy", CHECK_DEREF(app.FindString("installedby")));
  EXPECT_FALSE(app.FindBool("enabled").value_or(true));
  const base::DictValue& disabled =
      CHECK_DEREF(app.FindList("disabled"))[0].GetDict();
  EXPECT_EQ(disable_reason::DISABLE_CORRUPTED, disabled.FindInt("reason"));
}

#if BUILDFLAG(IS_ANDROID)
// TODO(https://crbug.com/469417243): Fails on desktop android.
#define MAYBE_Backoff DISABLED_Backoff
#else
#define MAYBE_Backoff Backoff
#endif
IN_PROC_BROWSER_TEST_F(PolicyUpdateServiceTest, MAYBE_Backoff) {
  ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
  ContentVerifier* verifier =
      ExtensionSystem::Get(profile())->content_verifier();

  // Wait for the extension to be installed by the policy we set up in
  // SetUpInProcessBrowserTestFixture, but only if it's not already installed.
  if (!registry->GetInstalledExtension(id_)) {
    TestExtensionRegistryObserver registry_observer(registry, id_);
    EXPECT_TRUE(registry_observer.WaitForExtensionInstalled());
  }

  // Setup to intercept reinstall action, so we can see what the delay would
  // have been for the real action.
  content_verifier_test::DelayTracker delay_tracker;

  // Do 4 iterations of disabling followed by reinstall.
  const size_t iterations = 4;
  for (size_t i = 0; i < iterations; i++) {
    TestExtensionRegistryObserver registry_observer(registry, id_);
    verifier->VerifyFailedForTest(id_, ContentVerifyJob::HASH_MISMATCH);
    EXPECT_TRUE(registry_observer.WaitForExtensionUnloaded());

    // Register the waiter before triggering the reinstallation
    TestUpdateClientEventWaiter waiter(id_);
    AddUpdateClientObserver(&waiter);

    // Resolve the request to |delay_tracker|, so the reinstallation can
    // proceed.
    delay_tracker.Proceed();

    // Wait for the reinstallation event to complete safely
    EXPECT_EQ(update_client::ComponentState::kUpdated, waiter.Wait());
    RemoveUpdateClientObserver(&waiter);
  }

  ASSERT_EQ(4, update_interceptor_->GetCount())
      << update_interceptor_->GetRequestsAsString();
  // Only one download because retries are cached.
  EXPECT_EQ(1, get_interceptor_count());

  const std::vector<base::TimeDelta>& calls = delay_tracker.calls();

  // After |delay_tracker| resolves the 4 (|iterations|) reinstallation
  // requests, it will get an additional request (right away) for retrying
  // reinstallation.
  // Note: the additional request in non-test environment will arrive with
  // a (backoff) delay. But during test, |delay_tracker| issues the request
  // immediately.
  ASSERT_EQ(iterations, calls.size() - 1);
  // Assert that the first reinstall action happened with a delay of 0, and
  // then kept growing each additional time.
  EXPECT_EQ(base::TimeDelta(), delay_tracker.calls()[0]);
  for (size_t i = 1; i < delay_tracker.calls().size(); i++) {
    EXPECT_LT(calls[i - 1], calls[i]);
  }
}

// TODO(crbug.com/316940720): Flaky on Chrome OS MSAN bot. Also flaky on desktop
// Android. Crashes during test shutdown in ~CrxInstaller.
#if (defined(ADDRESS_SANITIZER) && BUILDFLAG(IS_CHROMEOS)) || \
    BUILDFLAG(IS_ANDROID)
#define MAYBE_PRE_PolicyCorruptedOnStartup DISABLED_PRE_PolicyCorruptedOnStartup
#define MAYBE_PolicyCorruptedOnStartup DISABLED_PolicyCorruptedOnStartup
#else
#define MAYBE_PRE_PolicyCorruptedOnStartup PRE_PolicyCorruptedOnStartup
#define MAYBE_PolicyCorruptedOnStartup PolicyCorruptedOnStartup
#endif
// We want to test what happens at startup with a corruption-disabled policy
// force installed extension. So we set that up in the PRE test here.
IN_PROC_BROWSER_TEST_F(PolicyUpdateServiceTest,
                       MAYBE_PRE_PolicyCorruptedOnStartup) {
  // This is to not allow any corrupted resintall to proceed.
  content_verifier_test::DelayTracker delay_tracker;
  ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
  TestExtensionRegistryObserver registry_observer(registry, id_);

  // Wait for the extension to be installed by policy we set up in
  // SetUpInProcessBrowserTestFixture but only if the extension is not yet
  // installed.
  if (!registry->GetInstalledExtension(id_)) {
    EXPECT_TRUE(registry_observer.WaitForExtensionInstalled());
  }

  // Simulate corruption of the extension so that we can test what happens
  // at startup in the non-PRE test.
  ContentVerifier* verifier =
      ExtensionSystem::Get(profile())->content_verifier();
  verifier->VerifyFailedForTest(id_, ContentVerifyJob::HASH_MISMATCH);
  EXPECT_TRUE(registry_observer.WaitForExtensionUnloaded());

  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
  DisableReasonSet reasons = prefs->GetDisableReasons(id_);
  EXPECT_TRUE(reasons.contains(disable_reason::DISABLE_CORRUPTED));
  EXPECT_EQ(1u, delay_tracker.calls().size());

  EXPECT_EQ(0, update_interceptor_->GetCount())
      << update_interceptor_->GetRequestsAsString();
  EXPECT_EQ(0, get_interceptor_count());

#if BUILDFLAG(IS_ANDROID)
  // Android does not perform a graceful shutdown in browser tests, so we have
  // to explicitly flush extension preferences to disk.
  profile()->GetPrefs()->CommitPendingWrite();

  // Ensure writes on other threads (e.g. from GetExtensionFileTaskRunner())
  // have a chance to complete (e.g. StateStore and CrxInstaller).
  base::ThreadPoolInstance::Get()->FlushForTesting();
#endif  // BUILDFLAG(IS_ANDROID)
}

// Now actually test what happens on the next startup after the PRE test above.
IN_PROC_BROWSER_TEST_F(PolicyUpdateServiceTest,
                       MAYBE_PolicyCorruptedOnStartup) {
  // Depdending on timing, the extension may have already been reinstalled
  // between SetUpInProcessBrowserTestFixture and now (usually not during local
  // testing on a developer machine, but sometimes on a heavily loaded system
  // such as the build waterfall / trybots). If the reinstall didn't already
  // happen, wait for it.

  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
  ExtensionRegistry* registry = ExtensionRegistry::Get(profile());

  // Wait for the extension to be installed. If it's already installed (because
  // the startup update finished quickly), this skips waiting.
  if (!registry->GetInstalledExtension(id_)) {
    TestExtensionRegistryObserver observer(registry, id_);
    observer.WaitForExtensionInstalled();
  }

  // Wait for the extension to be reinstalled so it isn't corrupted anymore.
  DisableReasonSet disable_reasons = prefs->GetDisableReasons(id_);
  if (disable_reasons.contains(disable_reason::DISABLE_CORRUPTED)) {
    EXPECT_EQ(update_client::ComponentState::kUpdated,
              WaitOnComponentUpdaterCompleteEvent(id_));
    disable_reasons = prefs->GetDisableReasons(id_);
  }

  EXPECT_FALSE(disable_reasons.contains(disable_reason::DISABLE_CORRUPTED));
  EXPECT_TRUE(registry->enabled_extensions().Contains(id_));

  ASSERT_EQ(1, update_interceptor_->GetCount())
      << update_interceptor_->GetRequestsAsString();
  // Explicitly don't check get_interceptor_count(). Update client's CRX cache
  // data may or may not be persisted from the PRE_ step, depending on whether
  // the test had a graceful shutdown (on Android, the test may just
  // terminate).  So there may or may not be a network request -- either way
  // is fine.

  const std::string update_request =
      std::get<0>(update_interceptor_->GetRequests()[0]);
  const std::optional<base::DictValue> root = GetRequest(0);
  ASSERT_TRUE(root);
  const base::DictValue& app = GetFirstApp(root.value());
  EXPECT_EQ(id_, CHECK_DEREF(app.FindString("appid")));
  EXPECT_EQ("0.0.0.0", CHECK_DEREF(app.FindString("version")));
  EXPECT_EQ("reinstall", CHECK_DEREF(app.FindString("installsource")));
  EXPECT_EQ("policy", CHECK_DEREF(app.FindString("installedby")));
  EXPECT_FALSE(app.FindBool("enabled").value_or(true));
  const base::DictValue& disabled =
      CHECK_DEREF(app.FindList("disabled"))[0].GetDict();
  EXPECT_EQ(disable_reason::DISABLE_CORRUPTED, disabled.FindInt("reason"));

#if BUILDFLAG(IS_ANDROID)
  // Signal any in-flight CrxInstaller instances to clean up.
  // TODO(jamescook): Consider moving this to AndroidBrowserTest shutdown.
  browser_shutdown::NotifyAppTerminating();

  // Ensure cleanup on other threads (e.g. from GetExtensionFileTaskRunner())
  // has a chance to complete (e.g. CrxInstaller).
  base::ThreadPoolInstance::Get()->FlushForTesting();
#endif  // BUILDFLAG(IS_ANDROID)
}

}  // namespace extensions
