// 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 "chrome/browser/web_applications/web_app_registrar.h"

#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "ash/constants/web_app_id_constants.h"
#include "base/containers/flat_set.h"
#include "base/files/file_path.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "chrome/browser/apps/link_capturing/link_capturing_feature_test_support.h"
#include "chrome/browser/web_applications/commands/run_on_os_login_command.h"
#include "chrome/browser/web_applications/commands/web_app_uninstall_command.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
#include "chrome/browser/web_applications/model/display_override.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
#include "chrome/browser/web_applications/proto/web_app.pb.h"
#include "chrome/browser/web_applications/proto/web_app_install_state.pb.h"
#include "chrome/browser/web_applications/scope_extension_info.h"
#include "chrome/browser/web_applications/test/fake_web_app_database_factory.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test.h"
#include "chrome/browser/web_applications/test/web_app_test_utils.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include "chrome/browser/web_applications/web_app_command_scheduler.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_icon_generator.h"
#include "chrome/browser/web_applications/web_app_install_manager.h"
#include "chrome/browser/web_applications/web_app_management_type.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "components/sync/test/mock_data_type_local_change_processor.h"
#include "components/webapps/browser/uninstall_result_code.h"
#include "components/webapps/common/web_app_id.h"
#include "components/webapps/isolated_web_apps/scheme.h"
#include "components/webapps/isolated_web_apps/types/storage_location.h"
#include "content/public/browser/storage_partition_config.h"
#include "content/public/common/content_features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "url/gurl.h"
#include "url/url_constants.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_features.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/user_manager/test_helper.h"
#include "components/user_manager/user_names.h"
#endif

namespace web_app {

namespace {

Registry CreateRegistryForTesting(const std::string& base_url, int num_apps) {
  Registry registry;

  for (int i = 0; i < num_apps; ++i) {
    const auto start_url_str = base_url + base::NumberToString(i);
    GURL start_url = GURL(start_url_str);

    webapps::ManifestId manifest_id =
        GenerateManifestIdFromStartUrlOnly(start_url);
    GURL scope = start_url.GetWithoutFilename();
    auto web_app = std::make_unique<WebApp>(manifest_id, start_url, scope);
    web_app->SetName("Name" + base::NumberToString(i));
    web_app->SetDisplayMode(DisplayMode::kBrowser);
    web_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser);
    web_app->SetInstallState(proto::INSTALLED_WITH_OS_INTEGRATION);
    web_app->AddSource(WebAppManagement::kSync);
    // Set an OS integration state (with shortcuts) to prevent migration to a
    // partially installed status.
    proto::os_state::WebAppOsIntegration os_state;
    os_state.mutable_shortcut();
    web_app->SetCurrentOsIntegrationStates(os_state);

    webapps::AppId app_id = web_app->app_id();
    registry.emplace(app_id, std::move(web_app));
  }

  return registry;
}

int CountApps(const WebAppRegistrar::AppSet& app_set) {
  int count = 0;
  for (const auto& web_app : app_set) {
    EXPECT_FALSE(web_app.is_uninstalling());
    ++count;
  }
  return count;
}

}  // namespace

using ::testing::ElementsAre;
using ::testing::IsEmpty;
using ::testing::Pair;

class WebAppRegistrarTest : public WebAppTest {
 public:
  void SetUp() override {
    WebAppTest::SetUp();
    web_app::FakeWebAppProvider::Get(profile())->SetDatabaseFactory(
        std::make_unique<FakeWebAppDatabaseFactory>());
  }

  void TearDown() override {
    WebAppTest::TearDown();
  }

 protected:
  void StartWebAppProvider() {
    test::AwaitStartWebAppProviderAndSubsystems(profile());
  }

  base::flat_set<webapps::AppId> PopulateRegistry(const Registry& registry) {
    auto app_ids = base::MakeFlatSet<webapps::AppId>(
        registry, /*comp=*/{}, [&](auto& kv) { return kv.second->app_id(); });

    database_factory().WriteRegistry(registry);

    return app_ids;
  }

  void PopulateRegistryWithApp(std::unique_ptr<WebApp> app) {
    Registry registry;
    webapps::AppId app_id = app->app_id();
    registry[app_id] = std::move(app);
    PopulateRegistry(std::move(registry));
  }

  base::flat_set<webapps::AppId> PopulateRegistryWithApps(
      const std::string& base_url,
      int num_apps) {
    return PopulateRegistry(CreateRegistryForTesting(base_url, num_apps));
  }

  FakeWebAppDatabaseFactory& database_factory() const {
    return static_cast<FakeWebAppDatabaseFactory&>(
        fake_provider().GetDatabaseFactory());
  }

  WebAppRegistrar& registrar() const {
    return fake_provider().registrar_unsafe();
  }

  WebAppSyncBridge& sync_bridge() const {
    return fake_provider().sync_bridge_unsafe();
  }

  // Do not copy/paste this, and instead use normal installation commands to
  // install an app.
  void RegisterAppUnsafe(std::unique_ptr<WebApp> app) {
    ScopedRegistryUpdate update =
        fake_provider().sync_bridge_unsafe().BeginUpdate();
    update->CreateApp(std::move(app));
  }

  void Uninstall(const webapps::AppId& app_id) {
    // There is no longer a universal uninstall, so just remove each management.
    WebAppManagementTypes managements =
        registrar().GetAppById(app_id)->GetSources();
    for (WebAppManagement::Type type : managements) {
      base::test::TestFuture<webapps::UninstallResultCode> future;
      fake_provider().scheduler().RemoveInstallManagementMaybeUninstall(
          app_id, type, webapps::WebappUninstallSource::kTestCleanup,
          future.GetCallback());
      EXPECT_TRUE(future.Wait());
      EXPECT_EQ(future.Get<webapps::UninstallResultCode>(),
                webapps::UninstallResultCode::kAppRemoved);
    }
  }

  void UninstallViaRemoveSource(
      const webapps::AppId& app_id,
      web_app::WebAppManagement::Type management_type) {
    base::test::TestFuture<webapps::UninstallResultCode> future;
    fake_provider().scheduler().RemoveInstallManagementMaybeUninstall(
        app_id, management_type, webapps::WebappUninstallSource::kTestCleanup,
        future.GetCallback());
    EXPECT_TRUE(future.Wait());
    EXPECT_EQ(future.Get<webapps::UninstallResultCode>(),
              webapps::UninstallResultCode::kAppRemoved);
  }

 private:
  std::unique_ptr<FakeWebAppDatabaseFactory> database_factory_;
  testing::NiceMock<syncer::MockDataTypeLocalChangeProcessor> mock_processor_;
};

class WebAppRegistrarTest_TabStrip : public WebAppRegistrarTest {
 public:
  WebAppRegistrarTest_TabStrip() = default;

 private:
  base::test::ScopedFeatureList scoped_feature_list_{
      blink::features::kDesktopPWAsTabStrip};
};

TEST_F(WebAppRegistrarTest, EmptyRegistrar) {
  StartWebAppProvider();
  EXPECT_TRUE(registrar().is_empty());
  EXPECT_FALSE(registrar().GetInstallState(webapps::AppId()).has_value());
  EXPECT_EQ(std::nullopt, registrar().GetInstallState(webapps::AppId()));
  EXPECT_FALSE(registrar().GetInstallState(webapps::AppId()).has_value());
  EXPECT_EQ(nullptr, registrar().GetAppById(webapps::AppId()));
  EXPECT_EQ(std::string(), registrar().GetAppShortName(webapps::AppId()));
  EXPECT_EQ(GURL(), registrar().GetAppStartUrl(webapps::AppId()));
}

TEST_F(WebAppRegistrarTest, InitWithApps) {
  const GURL start_url = GURL("https://example.com/scope/path");
  const webapps::AppId app_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, start_url);
  const std::string name = "Name";
  const std::string description = "Description";
  const GURL scope = GURL("https://example.com/scope");
  const std::optional<SkColor> theme_color = 0xAABBCCDD;

  const GURL start_url2 = GURL("https://example.com/scope2/path2");
  const GURL scope2 = GURL("https://example.com/scope2");
  const webapps::AppId app_id2 =
      GenerateAppId(/*manifest_id=*/std::nullopt, start_url2);

  auto web_app =
      std::make_unique<WebApp>(/*manifest_id=*/
                               GenerateManifestIdFromStartUrlOnly(start_url),
                               start_url, scope);
  auto web_app2 = std::make_unique<WebApp>(
      /*manifest_id=*/GenerateManifestIdFromStartUrlOnly(start_url2),
      start_url2, scope2);

  web_app->AddSource(WebAppManagement::kUserInstalled);
  web_app->SetDisplayMode(DisplayMode::kStandalone);
  web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone);
  web_app->SetName(name);
  web_app->SetDescription(description);
  web_app->SetThemeColor(theme_color);

  web_app2->AddSource(WebAppManagement::kDefault);
  web_app2->SetDisplayMode(DisplayMode::kBrowser);
  web_app2->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser);
  web_app2->SetName(name);

  Registry registry;
  registry[app_id] = std::move(web_app);
  registry[app_id2] = std::move(web_app2);
  PopulateRegistry(std::move(registry));

  StartWebAppProvider();

  EXPECT_TRUE(registrar().GetInstallState(app_id).has_value());
  EXPECT_EQ(proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION,
            registrar().GetInstallState(app_id));

  const WebApp* app = registrar().GetAppById(app_id);

  EXPECT_EQ(app_id, app->app_id());
  EXPECT_EQ(name, app->untranslated_name());
  EXPECT_EQ(description, app->untranslated_description());
  EXPECT_EQ(start_url, app->start_url());
  EXPECT_EQ(scope, app->scope());
  EXPECT_EQ(theme_color, app->theme_color());

  EXPECT_FALSE(registrar().is_empty());

  EXPECT_TRUE(registrar().GetInstallState(app_id).has_value());
  EXPECT_EQ(proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION,
            registrar().GetInstallState(app_id2));
  const WebApp* app2 = registrar().GetAppById(app_id2);
  EXPECT_EQ(app_id2, app2->app_id());
  EXPECT_FALSE(registrar().is_empty());
  EXPECT_EQ(CountApps(registrar().GetApps()), 2);

  Uninstall(app_id);
  EXPECT_FALSE(registrar().GetInstallState(app_id).has_value());
  EXPECT_EQ(std::nullopt, registrar().GetInstallState(app_id));
  EXPECT_EQ(nullptr, registrar().GetAppById(app_id));
  EXPECT_FALSE(registrar().is_empty());

  // Check that app2 is still registered.
  app2 = registrar().GetAppById(app_id2);
  EXPECT_TRUE(registrar().GetInstallState(app_id2).has_value());
  EXPECT_EQ(proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION,
            registrar().GetInstallState(app_id2));
  EXPECT_EQ(app_id2, app2->app_id());

  Uninstall(app_id2);
  EXPECT_FALSE(registrar().GetInstallState(app_id2).has_value());
  EXPECT_EQ(std::nullopt, registrar().GetInstallState(app_id2));
  EXPECT_EQ(nullptr, registrar().GetAppById(app_id2));
  EXPECT_TRUE(registrar().is_empty());
  EXPECT_EQ(CountApps(registrar().GetApps()), 0);
}

TEST_F(WebAppRegistrarTest, InitRegistrarAndDoForEachApp) {
  base::flat_set<webapps::AppId> ids = PopulateRegistry(
      CreateRegistryForTesting("https://example.com/path", 20));
  StartWebAppProvider();

  for (const WebApp& web_app : registrar().GetAppsIncludingStubs()) {
    const size_t num_removed = ids.erase(web_app.app_id());
    EXPECT_EQ(1U, num_removed);
  }

  EXPECT_TRUE(ids.empty());
}

TEST_F(WebAppRegistrarTest, DoForEachAndUnregisterAllApps) {
  Registry registry = CreateRegistryForTesting("https://example.com/path", 20);
  auto ids = PopulateRegistry(std::move(registry));
  EXPECT_EQ(20UL, ids.size());

  StartWebAppProvider();

  for (const WebApp& web_app : registrar().GetAppsIncludingStubs()) {
    const size_t num_removed = ids.erase(web_app.app_id());
    EXPECT_EQ(1U, num_removed);
  }
  EXPECT_TRUE(ids.empty());

  EXPECT_FALSE(registrar().is_empty());
  test::UninstallAllWebApps(profile());
  EXPECT_TRUE(registrar().is_empty());
}

TEST_F(WebAppRegistrarTest, AppsInstalledByUserMetric) {
  base::HistogramTester histogram_tester;

  // All of these apps are marked as 'not locally installed'.
  PopulateRegistry(CreateRegistryForTesting("https://example.com/path", 10));
  StartWebAppProvider();

  EXPECT_THAT(histogram_tester.GetAllSamples("WebApp.InstalledCount.ByUser"),
              base::BucketsAre(base::Bucket(/*min=*/10,
                                            /*count=*/1)));
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "WebApp.InstalledCount.ByUserNotLocallyInstalled"),
              base::BucketsAre(base::Bucket(/*min=*/0,
                                            /*count=*/1)));
}

TEST_F(WebAppRegistrarTest, AppsNonUserInstalledMetric) {
  base::HistogramTester histogram_tester;

  auto web_app = web_app::test::CreateWebApp(GURL("https://example.com/path"),
                                             WebAppManagement::kPolicy);
  web_app->SetDisplayMode(DisplayMode::kStandalone);
  web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone);
  web_app->SetName("name");
  PopulateRegistryWithApp(std::move(web_app));
  StartWebAppProvider();

  histogram_tester.ExpectUniqueSample("WebApp.InstalledCount.ByUser",
                                      /*sample=*/0,
                                      /*expected_bucket_count=*/1);
  histogram_tester.ExpectUniqueSample(
      "WebApp.InstalledCount.ByUserNotLocallyInstalled", /*sample=*/0,
      /*expected_bucket_count=*/1);
}

TEST_F(WebAppRegistrarTest, AppsNotLocallyInstalledMetric) {
  base::HistogramTester histogram_tester;

  auto web_app = web_app::test::CreateWebApp(GURL("https://example.com/path"),
                                             WebAppManagement::kSync);
  web_app->SetDisplayMode(DisplayMode::kStandalone);
  web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone);
  web_app->SetName("name");
  web_app->SetInstallState(proto::SUGGESTED_FROM_ANOTHER_DEVICE);
  web_app->AddSource(WebAppManagement::kSync);
  PopulateRegistryWithApp(std::move(web_app));
  StartWebAppProvider();

  histogram_tester.ExpectUniqueSample("WebApp.InstalledCount.ByUser",
                                      /*sample=*/0,
                                      /*expected_bucket_count=*/1);
  histogram_tester.ExpectUniqueSample(
      "WebApp.InstalledCount.ByUserNotLocallyInstalled", /*sample=*/1,
      /*expected_bucket_count=*/1);
}

TEST_F(WebAppRegistrarTest, GetApps) {
  base::flat_set<webapps::AppId> ids =
      PopulateRegistryWithApps("https://example.com/path", 10);
  StartWebAppProvider();

  int not_in_sync_install_count = 0;
  for (const WebApp& web_app : registrar().GetApps()) {
    ++not_in_sync_install_count;
    EXPECT_TRUE(ids.contains(web_app.app_id()));
  }
  EXPECT_EQ(10, not_in_sync_install_count);

  auto web_app_in_sync1 = test::CreateWebApp(GURL("https://example.org/sync1"));
  web_app_in_sync1->SetIsFromSyncAndPendingInstallation(true);
  const webapps::AppId web_app_id_in_sync1 = web_app_in_sync1->app_id();
  RegisterAppUnsafe(std::move(web_app_in_sync1));

  auto web_app_in_sync2 = test::CreateWebApp(GURL("https://example.org/sync2"));
  web_app_in_sync2->SetIsFromSyncAndPendingInstallation(true);
  const webapps::AppId web_app_id_in_sync2 = web_app_in_sync2->app_id();
  RegisterAppUnsafe(std::move(web_app_in_sync2));

  int all_apps_count = 0;
  for ([[maybe_unused]] const WebApp& web_app :
       registrar().GetAppsIncludingStubs()) {
    ++all_apps_count;
  }
  EXPECT_EQ(12, all_apps_count);

  for (const WebApp& web_app : registrar().GetApps()) {
    EXPECT_NE(web_app_id_in_sync1, web_app.app_id());
    EXPECT_NE(web_app_id_in_sync2, web_app.app_id());

    const size_t num_removed = ids.erase(web_app.app_id());
    EXPECT_EQ(1U, num_removed);
  }
  EXPECT_TRUE(ids.empty());

  Uninstall(web_app_id_in_sync1);
  Uninstall(web_app_id_in_sync2);

  not_in_sync_install_count = 0;
  for ([[maybe_unused]] const WebApp& web_app : registrar().GetApps()) {
    ++not_in_sync_install_count;
  }
  EXPECT_EQ(10, not_in_sync_install_count);
}

TEST_F(WebAppRegistrarTest, GetAppDataFields) {
  const GURL start_url = GURL("https://example.com/path");
  const webapps::ManifestId manifest_id =
      GenerateManifestIdFromStartUrlOnly(start_url);
  const GURL scope = start_url.GetWithoutFilename();
  const std::string name = "Name";
  const std::string description = "Description";
  const std::optional<SkColor> theme_color = 0xAABBCCDD;
  const auto display_mode = DisplayMode::kMinimalUi;
  const auto user_display_mode = mojom::UserDisplayMode::kStandalone;

  std::vector<DisplayOverride> display_mode_override = {
      DisplayOverride::Create(DisplayMode::kMinimalUi),
      DisplayOverride::Create(DisplayMode::kStandalone)};

  auto web_app = std::make_unique<WebApp>(manifest_id, start_url, scope);
  web_app->SetName(name);
  web_app->SetDescription(description);
  web_app->SetThemeColor(theme_color);
  web_app->SetDisplayMode(display_mode);
  web_app->SetUserDisplayMode(user_display_mode);
  web_app->SetDisplayModeOverride(std::move(display_mode_override));
  web_app->SetInstallState(proto::SUGGESTED_FROM_ANOTHER_DEVICE);
  web_app->AddSource(WebAppManagement::kSync);
  webapps::AppId app_id = web_app->app_id();

  PopulateRegistryWithApp(std::move(web_app));
  StartWebAppProvider();

  EXPECT_EQ(name, registrar().GetAppShortName(app_id));
  EXPECT_EQ(description, registrar().GetAppDescription(app_id));
  EXPECT_EQ(theme_color, registrar().GetAppThemeColor(app_id));
  EXPECT_EQ(start_url, registrar().GetAppStartUrl(app_id));
  EXPECT_EQ(mojom::UserDisplayMode::kBrowser,
            registrar().GetAppUserDisplayMode(app_id));

  {
    std::vector<DisplayMode> app_display_mode_override =
        registrar().GetAppDisplayModeOverride(app_id);
    ASSERT_EQ(2u, app_display_mode_override.size());
    EXPECT_EQ(DisplayMode::kMinimalUi, app_display_mode_override[0]);
    EXPECT_EQ(DisplayMode::kStandalone, app_display_mode_override[1]);
  }

  {
    EXPECT_TRUE(registrar().GetInstallState(app_id).has_value());
    EXPECT_EQ(proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE,
              registrar().GetInstallState(app_id));

    EXPECT_FALSE(registrar().GetInstallState("unknown").has_value());
    EXPECT_EQ(std::nullopt, registrar().GetInstallState("unknown"));
    base::test::TestFuture<void> future;
    fake_provider().scheduler().InstallAppLocally(app_id, future.GetCallback());
    ASSERT_TRUE(future.Wait());
    EXPECT_EQ(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
              registrar().GetInstallState(app_id));
    EXPECT_EQ(mojom::UserDisplayMode::kStandalone,
              registrar().GetAppUserDisplayMode(app_id));
  }

  {
    EXPECT_FALSE(registrar().GetAppUserDisplayMode("unknown").has_value());
    {
      ScopedRegistryUpdate update = sync_bridge().BeginUpdate();
      update->UpdateApp(app_id)->SetUserDisplayMode(
          mojom::UserDisplayMode::kBrowser);
    }
    EXPECT_EQ(mojom::UserDisplayMode::kBrowser,
              registrar().GetAppUserDisplayMode(app_id));

    fake_provider().sync_bridge_unsafe().SetAppUserDisplayModeForTesting(
        app_id, mojom::UserDisplayMode::kStandalone);
    EXPECT_EQ(mojom::UserDisplayMode::kStandalone,
              registrar().GetAppUserDisplayMode(app_id));
    EXPECT_EQ(DisplayMode::kMinimalUi, registrar().GetAppDisplayMode(app_id));

    ASSERT_EQ(2u, registrar().GetAppDisplayModeOverride(app_id).size());
    EXPECT_EQ(DisplayMode::kMinimalUi,
              registrar().GetAppDisplayModeOverride(app_id)[0]);
    EXPECT_EQ(DisplayMode::kStandalone,
              registrar().GetAppDisplayModeOverride(app_id)[1]);
  }
}

TEST_F(WebAppRegistrarTest, CanFindAppsInScope) {
  StartWebAppProvider();

  const GURL origin_scope("https://example.com/");

  const GURL app1_scope("https://example.com/app/");
  const GURL app2_scope("https://example.com/app/two/");
  const GURL app3_scope("https://not-example.com/app/");

  const webapps::AppId app1_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, app1_scope);
  const webapps::AppId app2_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, app2_scope);
  const webapps::AppId app3_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, app3_scope);

  std::vector<webapps::AppId> in_scope = registrar().FindAllAppsNestedInUrl(
      origin_scope,
      web_app::WebAppFilter::InstalledInOperatingSystemForTesting());
  EXPECT_EQ(0u, in_scope.size());
  EXPECT_FALSE(registrar().DoesScopeContainAnyApp(
      origin_scope, web_app::WebAppFilter::InstalledInChrome()));
  EXPECT_FALSE(registrar().DoesScopeContainAnyApp(
      app3_scope, web_app::WebAppFilter::InstalledInChrome()));

  auto app1 = test::CreateWebApp(app1_scope);
  RegisterAppUnsafe(std::move(app1));

  in_scope = registrar().FindAllAppsNestedInUrl(
      origin_scope, web_app::WebAppFilter::InstalledInChrome());
  EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app1_id));
  EXPECT_TRUE(registrar().DoesScopeContainAnyApp(
      origin_scope, web_app::WebAppFilter::InstalledInChrome()));
  EXPECT_FALSE(registrar().DoesScopeContainAnyApp(
      app3_scope, web_app::WebAppFilter::InstalledInChrome()));

  in_scope = registrar().FindAllAppsNestedInUrl(
      app1_scope, web_app::WebAppFilter::InstalledInChrome());
  EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app1_id));
  EXPECT_TRUE(registrar().DoesScopeContainAnyApp(
      app1_scope, web_app::WebAppFilter::InstalledInChrome()));

  auto app2 = test::CreateWebApp(app2_scope);
  RegisterAppUnsafe(std::move(app2));

  in_scope = registrar().FindAllAppsNestedInUrl(
      origin_scope, web_app::WebAppFilter::InstalledInChrome());
  EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app1_id, app2_id));
  EXPECT_TRUE(registrar().DoesScopeContainAnyApp(
      origin_scope, web_app::WebAppFilter::InstalledInChrome()));
  EXPECT_FALSE(registrar().DoesScopeContainAnyApp(
      app3_scope, web_app::WebAppFilter::InstalledInChrome()));

  in_scope = registrar().FindAllAppsNestedInUrl(
      app1_scope, web_app::WebAppFilter::InstalledInChrome());
  EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app1_id, app2_id));
  EXPECT_TRUE(registrar().DoesScopeContainAnyApp(
      app1_scope, web_app::WebAppFilter::InstalledInChrome()));

  in_scope = registrar().FindAllAppsNestedInUrl(
      app2_scope, web_app::WebAppFilter::InstalledInChrome());
  EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app2_id));
  EXPECT_TRUE(registrar().DoesScopeContainAnyApp(
      app2_scope, web_app::WebAppFilter::InstalledInChrome()));

  auto app3 = test::CreateWebApp(app3_scope);
  RegisterAppUnsafe(std::move(app3));

  in_scope = registrar().FindAllAppsNestedInUrl(
      origin_scope, web_app::WebAppFilter::InstalledInChrome());
  EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app1_id, app2_id));
  EXPECT_TRUE(registrar().DoesScopeContainAnyApp(
      origin_scope, web_app::WebAppFilter::InstalledInChrome()));

  in_scope = registrar().FindAllAppsNestedInUrl(
      app3_scope, web_app::WebAppFilter::InstalledInChrome());
  EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app3_id));
  EXPECT_TRUE(registrar().DoesScopeContainAnyApp(
      app3_scope, web_app::WebAppFilter::InstalledInChrome()));
}

TEST_F(WebAppRegistrarTest, CanFindAppWithUrlInScope) {
  StartWebAppProvider();

  const GURL origin_scope("https://example.com/");

  const GURL app1_scope("https://example.com/app/");
  const GURL app2_scope("https://example.com/app/two/");
  const GURL app3_scope("https://not-example.com/app/");
  const GURL app4_scope("https://app-four.com/");

  const webapps::AppId app1_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, app1_scope);
  const webapps::AppId app2_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, app2_scope);
  const webapps::AppId app3_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, app3_scope);
  const webapps::AppId app4_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, app3_scope);

  auto app1 = test::CreateWebApp(app1_scope);
  RegisterAppUnsafe(std::move(app1));

  std::optional<webapps::AppId> app2_match =
      registrar().FindBestAppWithUrlInScope(
          app2_scope, web_app::WebAppFilter::InstalledInChrome());
  DCHECK(app2_match);
  EXPECT_EQ(*app2_match, app1_id);

  std::optional<webapps::AppId> app3_match =
      registrar().FindBestAppWithUrlInScope(
          app3_scope,
          web_app::WebAppFilter::InstalledInOperatingSystemForTesting());
  EXPECT_FALSE(app3_match);

  std::optional<webapps::AppId> app4_match =
      registrar().FindBestAppWithUrlInScope(
          app4_scope,
          web_app::WebAppFilter::InstalledInOperatingSystemForTesting());
  EXPECT_FALSE(app4_match);

  auto app2 = test::CreateWebApp(app2_scope);
  RegisterAppUnsafe(std::move(app2));

  auto app3 = test::CreateWebApp(app3_scope);
  RegisterAppUnsafe(std::move(app3));

  auto app4 = test::CreateWebApp(app4_scope);
  app4->SetIsUninstalling(true);
  RegisterAppUnsafe(std::move(app4));

  std::optional<webapps::AppId> origin_match =
      registrar().FindBestAppWithUrlInScope(
          origin_scope,
          web_app::WebAppFilter::InstalledInOperatingSystemForTesting());
  EXPECT_FALSE(origin_match);

  std::optional<webapps::AppId> app1_match =
      registrar().FindBestAppWithUrlInScope(
          app1_scope, web_app::WebAppFilter::InstalledInChrome());
  DCHECK(app1_match);
  EXPECT_EQ(*app1_match, app1_id);

  app2_match = registrar().FindBestAppWithUrlInScope(
      app2_scope, web_app::WebAppFilter::InstalledInChrome());
  DCHECK(app2_match);
  EXPECT_EQ(*app2_match, app2_id);

  app3_match = registrar().FindBestAppWithUrlInScope(
      app3_scope, web_app::WebAppFilter::InstalledInChrome());
  DCHECK(app3_match);
  EXPECT_EQ(*app3_match, app3_id);

  // Apps in the process of uninstalling are ignored.
  app4_match = registrar().FindBestAppWithUrlInScope(
      app4_scope,
      web_app::WebAppFilter::InstalledInOperatingSystemForTesting());
  EXPECT_FALSE(app4_match);
}

TEST_F(WebAppRegistrarTest, FindPwaBasedOnStartUrlIfScopeIsEmpty) {
  StartWebAppProvider();

  const GURL app1_launch("https://example.com/app/specific/launch1");

  const GURL app2_scope("https://example.com/app");
  const GURL app2_page("https://example.com/app/specific/page2");

  const GURL app3_launch("https://example.com/app/specific/launch3");
  const webapps::AppId app3_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, app3_launch);

  auto app1 = test::CreateWebApp(app1_launch);
  RegisterAppUnsafe(std::move(app1));

  auto app2 = test::CreateWebApp(app2_scope);
  RegisterAppUnsafe(std::move(app2));

  auto app3 = test::CreateWebApp(app3_launch);
  RegisterAppUnsafe(std::move(app3));

  std::optional<webapps::AppId> app2_match =
      registrar().FindBestAppWithUrlInScope(
          app2_page, web_app::WebAppFilter::InstalledInChrome());
  ASSERT_TRUE(app2_match);
  EXPECT_EQ(app2_match, std::optional<webapps::AppId>(app3_id));
}

TEST_F(WebAppRegistrarTest, BeginAndCommitUpdate) {
  base::flat_set<webapps::AppId> ids =
      PopulateRegistryWithApps("https://example.com/path", 10);
  StartWebAppProvider();

  base::test::TestFuture<bool> future;
  {
    ScopedRegistryUpdate update =
        fake_provider().sync_bridge_unsafe().BeginUpdate(future.GetCallback());

    for (auto& app_id : ids) {
      WebApp* app = update->UpdateApp(app_id);
      EXPECT_TRUE(app);
      app->SetName("New Name");
    }

    // Acquire each app second time to make sure update requests get merged.
    for (auto& app_id : ids) {
      WebApp* app = update->UpdateApp(app_id);
      EXPECT_TRUE(app);
      app->SetDisplayMode(DisplayMode::kStandalone);
    }
  }
  EXPECT_TRUE(future.Take());

  // Make sure that all app ids were written to the database.
  auto registry_written = database_factory().ReadRegistry();
  EXPECT_EQ(ids.size(), registry_written.size());

  for (auto& kv : registry_written) {
    EXPECT_EQ("New Name", kv.second->untranslated_name());
    ids.erase(kv.second->app_id());
  }

  EXPECT_TRUE(ids.empty());
}

TEST_F(WebAppRegistrarTest, CommitEmptyUpdate) {
  base::flat_set<webapps::AppId> ids =
      PopulateRegistryWithApps("https://example.com/path", 10);
  StartWebAppProvider();
  const auto initial_registry = database_factory().ReadRegistry();

  {
    base::test::TestFuture<bool> future;
    {
      ScopedRegistryUpdate update =
          sync_bridge().BeginUpdate(future.GetCallback());
    }
    EXPECT_TRUE(future.Take());

    auto registry = database_factory().ReadRegistry();
    EXPECT_TRUE(IsRegistryEqual(initial_registry, registry));
  }

  {
    base::test::TestFuture<bool> future;
    {
      ScopedRegistryUpdate update =
          sync_bridge().BeginUpdate(future.GetCallback());

      WebApp* app = update->UpdateApp("unknown");
      EXPECT_FALSE(app);
    }
    EXPECT_TRUE(future.Take());

    auto registry = database_factory().ReadRegistry();
    EXPECT_TRUE(IsRegistryEqual(initial_registry, registry));
  }
}

TEST_F(WebAppRegistrarTest, ScopedRegistryUpdate) {
  base::flat_set<webapps::AppId> ids =
      PopulateRegistryWithApps("https://example.com/path", 10);
  StartWebAppProvider();
  const auto initial_registry = database_factory().ReadRegistry();

  // Test empty update first.
  { ScopedRegistryUpdate update = sync_bridge().BeginUpdate(); }
  EXPECT_TRUE(
      IsRegistryEqual(initial_registry, database_factory().ReadRegistry()));

  {
    ScopedRegistryUpdate update = sync_bridge().BeginUpdate();

    for (auto& app_id : ids) {
      WebApp* app = update->UpdateApp(app_id);
      EXPECT_TRUE(app);
      app->SetDescription("New Description");
    }
  }

  // Make sure that all app ids were written to the database.
  auto updated_registry = database_factory().ReadRegistry();
  EXPECT_EQ(ids.size(), updated_registry.size());

  for (auto& kv : updated_registry) {
    EXPECT_EQ(kv.second->untranslated_description(), "New Description");
    ids.erase(kv.second->app_id());
  }

  EXPECT_TRUE(ids.empty());
}

TEST_F(WebAppRegistrarTest, CopyOnWrite) {
  StartWebAppProvider();

  const GURL start_url("https://example.com");
  const webapps::AppId app_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, start_url);
  const WebApp* app = nullptr;
  {
    auto new_app = test::CreateWebApp(start_url);
    app = new_app.get();
    RegisterAppUnsafe(std::move(new_app));
  }

  base::test::TestFuture<bool> future;
  {
    ScopedRegistryUpdate update =
        sync_bridge().BeginUpdate(future.GetCallback());

    WebApp* app_copy = update->UpdateApp(app_id);
    EXPECT_TRUE(app_copy);
    EXPECT_NE(app_copy, app);

    app_copy->SetName("New Name");
    EXPECT_EQ(app_copy->untranslated_name(), "New Name");
    EXPECT_EQ(app->untranslated_name(), "Name");

    app_copy->AddSource(WebAppManagement::kPolicy);
    app_copy->RemoveSource(WebAppManagement::kSync);

    EXPECT_FALSE(app_copy->IsSynced());
    EXPECT_TRUE(app_copy->HasAnySources());

    EXPECT_TRUE(app->IsSynced());
    EXPECT_TRUE(app->HasAnySources());
  }
  EXPECT_TRUE(future.Take());

  // Pointer value stays the same.
  EXPECT_EQ(app, registrar().GetAppById(app_id));

  EXPECT_EQ(app->untranslated_name(), "New Name");
  EXPECT_FALSE(app->IsSynced());
  EXPECT_TRUE(app->HasAnySources());
}

TEST_F(WebAppRegistrarTest, CountUserInstalledApps) {
  StartWebAppProvider();

  const std::string base_url{"https://example.com/path"};

  for (WebAppManagement::Type type : WebAppManagementTypes::All()) {
    int i = static_cast<int>(type);
    auto web_app =
        test::CreateWebApp(GURL(base_url + base::NumberToString(i)), type);
    RegisterAppUnsafe(std::move(web_app));
  }

  // User-installed apps have one of the following types:
  // - `WebAppManagement::kSync`
  // - `WebAppManagement::kUserInstalled`
  // - `WebAppManagement::kWebAppStore`
  // - `WebAppManagement::kOneDriveIntegration`
  // - `WebAppManagement::kIwaUserInstalled`
  EXPECT_EQ(5, registrar().CountUserInstalledApps());
}

TEST_F(WebAppRegistrarTest, CountUserInstalledAppsDiy) {
  StartWebAppProvider();

  int i = 1;
  const std::string base_url{"https://example.com/path"};

  // Sync installed non-DIY.
  auto web_app1 = test::CreateWebApp(GURL(base_url + base::NumberToString(i)),
                                     WebAppManagement::kSync);
  web_app1->SetIsDiyApp(/*is_diy_app=*/false);
  RegisterAppUnsafe(std::move(web_app1));
  ++i;

  // Sync installed DIY.
  auto web_app2 = test::CreateWebApp(GURL(base_url + base::NumberToString(i)),
                                     WebAppManagement::kSync);
  web_app2->SetIsDiyApp(/*is_diy_app=*/true);
  RegisterAppUnsafe(std::move(web_app2));
  ++i;

  // Policy installed DIY (not counted as part of user installed)
  auto web_app3 = test::CreateWebApp(GURL(base_url + base::NumberToString(i)),
                                     WebAppManagement::kPolicy);
  web_app3->SetIsDiyApp(/*is_diy_app=*/true);
  RegisterAppUnsafe(std::move(web_app3));
  ++i;

  EXPECT_EQ(2, registrar().CountUserInstalledApps());
  EXPECT_EQ(1, registrar().CountUserInstalledDiyApps());
}

TEST_F(WebAppRegistrarTest, GetAllIsolatedWebAppStoragePartitionConfigs) {
  base::test::ScopedFeatureList scoped_feature_list(features::kIsolatedWebApps);
  StartWebAppProvider();

  constexpr char kIwaHostname[] =
      "berugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic";
  constexpr char kExpectedIwaStoragePartitionDomain[] =
      "i1kr80qqyjuuVC4UFPN7ovBngVoA2HbXGtTXtmQn6/H4=";
  GURL start_url(base::StrCat({webapps::kIsolatedAppScheme,
                               url::kStandardSchemeSeparator, kIwaHostname}));
  auto isolated_web_app = test::CreateWebApp(start_url);
  const webapps::AppId app_id = isolated_web_app->app_id();
  isolated_web_app->SetIsolationData(
      IsolationData::Builder(
          IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
          *IwaVersion::Create("1.0.0"))
          .Build());
  RegisterAppUnsafe(std::move(isolated_web_app));

  std::vector<content::StoragePartitionConfig> storage_partition_configs =
      registrar().GetIsolatedWebAppStoragePartitionConfigs(app_id);

  auto expected_config = content::StoragePartitionConfig::Create(
      profile(), kExpectedIwaStoragePartitionDomain,
      /*partition_name=*/"", /*in_memory=*/false);
  ASSERT_EQ(1UL, storage_partition_configs.size());
  EXPECT_EQ(expected_config, storage_partition_configs[0]);
}

TEST_F(WebAppRegistrarTest, SaveAndGetInMemoryControlledFramePartitionConfig) {
  base::test::ScopedFeatureList scoped_feature_list(features::kIsolatedWebApps);
  StartWebAppProvider();

  constexpr char kIwaHostname[] =
      "berugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic";
  constexpr char kExpectedIwaStoragePartitionDomain[] =
      "i1kr80qqyjuuVC4UFPN7ovBngVoA2HbXGtTXtmQn6/H4=";
  GURL start_url(base::StrCat({webapps::kIsolatedAppScheme,
                               url::kStandardSchemeSeparator, kIwaHostname}));
  auto isolated_web_app = test::CreateWebApp(start_url);
  const webapps::AppId app_id = isolated_web_app->app_id();
  auto url_info = IsolatedWebAppUrlInfo::Create(start_url);
  ASSERT_TRUE(url_info.has_value());

  isolated_web_app->SetIsolationData(
      IsolationData::Builder(
          IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
          *IwaVersion::Create("1.0.0"))
          .Build());
  RegisterAppUnsafe(std::move(isolated_web_app));

  auto output_config =
      registrar().SaveAndGetInMemoryControlledFramePartitionConfig(
          url_info.value(), "partition_1");

  auto expected_config_cf_1 = content::StoragePartitionConfig::Create(
      profile(), kExpectedIwaStoragePartitionDomain,
      /*partition_name=*/"partition_1", /*in_memory=*/true);

  EXPECT_EQ(expected_config_cf_1, output_config);
}

TEST_F(WebAppRegistrarTest,
       AppsFromSyncAndPendingInstallationExcludedFromGetAppIds) {
  PopulateRegistryWithApps("https://example.com/path/", 20);
  StartWebAppProvider();

  EXPECT_EQ(20u, registrar().GetAppIds().size());

  std::unique_ptr<WebApp> web_app_in_sync_install =
      test::CreateWebApp(GURL("https://example.org/"));
  web_app_in_sync_install->SetIsFromSyncAndPendingInstallation(true);

  const webapps::AppId web_app_in_sync_install_id =
      web_app_in_sync_install->app_id();
  RegisterAppUnsafe(std::move(web_app_in_sync_install));

  // Tests that GetAppIds() excludes web app in sync install:
  std::vector<webapps::AppId> ids = registrar().GetAppIds();
  EXPECT_EQ(20u, ids.size());
  for (const webapps::AppId& app_id : ids) {
    EXPECT_NE(app_id, web_app_in_sync_install_id);
  }

  // Tests that GetAppsIncludingStubs() returns a web app which is either in
  // GetAppIds() set or it is the web app in sync install:
  bool web_app_in_sync_install_found = false;
  for (const WebApp& web_app : registrar().GetAppsIncludingStubs()) {
    if (web_app.app_id() == web_app_in_sync_install_id) {
      web_app_in_sync_install_found = true;
    } else {
      EXPECT_TRUE(std::ranges::contains(ids, web_app.app_id()));
    }
  }
  EXPECT_TRUE(web_app_in_sync_install_found);
}

TEST_F(WebAppRegistrarTest, NotLocallyInstalledAppGetsDisplayModeBrowser) {
  StartWebAppProvider();

  auto web_app = test::CreateWebApp();
  const webapps::AppId app_id = web_app->app_id();
  web_app->SetDisplayMode(DisplayMode::kStandalone);
  web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone);
  web_app->SetInstallState(proto::SUGGESTED_FROM_ANOTHER_DEVICE);
  RegisterAppUnsafe(std::move(web_app));

  EXPECT_EQ(DisplayMode::kBrowser,
            registrar().GetAppEffectiveDisplayMode(app_id));

  base::test::TestFuture<void> future;
  fake_provider().scheduler().InstallAppLocally(app_id, future.GetCallback());
  ASSERT_TRUE(future.Wait());

  EXPECT_EQ(DisplayMode::kStandalone,
            registrar().GetAppEffectiveDisplayMode(app_id));
}

TEST_F(WebAppRegistrarTest,
       NotLocallyInstalledAppGetsDisplayModeBrowserEvenForIsolatedWebApps) {
  base::test::ScopedFeatureList scoped_feature_list(features::kIsolatedWebApps);
  StartWebAppProvider();

  std::unique_ptr<WebApp> web_app =
      test::CreateWebApp(GURL("isolated-app://random_name"));
  const webapps::AppId app_id = web_app->app_id();
  web_app->SetDisplayMode(DisplayMode::kStandalone);
  web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone);
  web_app->SetInstallState(proto::SUGGESTED_FROM_ANOTHER_DEVICE);
  web_app->SetIsolationData(
      IsolationData::Builder(
          IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
          *IwaVersion::Create("1.0.0"))
          .Build());

  RegisterAppUnsafe(std::move(web_app));

  EXPECT_EQ(DisplayMode::kBrowser,
            registrar().GetAppEffectiveDisplayMode(app_id));
}

TEST_F(WebAppRegistrarTest,
       IsolatedWebAppsGetDisplayModeStandaloneRegardlessOfUserSettings) {
  base::test::ScopedFeatureList scoped_feature_list(features::kIsolatedWebApps);
  StartWebAppProvider();

  std::unique_ptr<WebApp> web_app =
      test::CreateWebApp(GURL("isolated-app://random_name"));
  const webapps::AppId app_id = web_app->app_id();

  // Valid manifest must have standalone display mode
  web_app->SetDisplayMode(DisplayMode::kStandalone);
  web_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser);
  web_app->SetInstallState(proto::INSTALLED_WITH_OS_INTEGRATION);
  web_app->SetIsolationData(
      IsolationData::Builder(
          IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
          *IwaVersion::Create("1.0.0"))
          .Build());

  RegisterAppUnsafe(std::move(web_app));

  EXPECT_EQ(DisplayMode::kStandalone,
            registrar().GetAppEffectiveDisplayMode(app_id));
}

TEST_F(WebAppRegistrarTest,
       IsolatedWebAppsGetDisplayModeUnframedRegardlessOfUserSettings) {
  base::test::ScopedFeatureList scoped_feature_list(features::kIsolatedWebApps);
  StartWebAppProvider();

  std::unique_ptr<WebApp> web_app =
      test::CreateWebApp(GURL("isolated-app://random_name"));
  const webapps::AppId app_id = web_app->app_id();

  web_app->SetDisplayMode(DisplayMode::kUnframed);
  web_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser);
  web_app->SetInstallState(proto::INSTALLED_WITH_OS_INTEGRATION);
  web_app->SetIsolationData(
      IsolationData::Builder(
          IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
          *IwaVersion::Create("1.0.0"))
          .Build());

  RegisterAppUnsafe(std::move(web_app));

  EXPECT_EQ(DisplayMode::kUnframed,
            registrar().GetAppEffectiveDisplayMode(app_id));
}

TEST_F(WebAppRegistrarTest, NotLocallyInstalledAppGetsDisplayModeOverride) {
  StartWebAppProvider();

  auto web_app = test::CreateWebApp();
  const webapps::AppId app_id = web_app->app_id();
  std::vector<DisplayOverride> display_mode_overrides = {
      DisplayOverride::Create(DisplayMode::kFullscreen),
      DisplayOverride::Create(DisplayMode::kMinimalUi)};

  web_app->SetDisplayMode(DisplayMode::kStandalone);
  web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone);
  web_app->SetDisplayModeOverride(display_mode_overrides);
  web_app->SetInstallState(proto::SUGGESTED_FROM_ANOTHER_DEVICE);
  RegisterAppUnsafe(std::move(web_app));

  EXPECT_EQ(DisplayMode::kBrowser,
            registrar().GetAppEffectiveDisplayMode(app_id));

  base::test::TestFuture<void> future;
  fake_provider().scheduler().InstallAppLocally(app_id, future.GetCallback());
  ASSERT_TRUE(future.Wait());

  EXPECT_EQ(DisplayMode::kMinimalUi,
            registrar().GetAppEffectiveDisplayMode(app_id));
}

TEST_F(WebAppRegistrarTest,
       CheckDisplayOverrideFromGetEffectiveDisplayModeFromManifest) {
  StartWebAppProvider();

  auto web_app = test::CreateWebApp();
  const webapps::AppId app_id = web_app->app_id();
  std::vector<DisplayOverride> display_mode_overrides = {
      DisplayOverride::Create(DisplayMode::kFullscreen),
      DisplayOverride::Create(DisplayMode::kMinimalUi)};

  web_app->SetDisplayMode(DisplayMode::kStandalone);
  web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone);
  web_app->SetDisplayModeOverride(display_mode_overrides);
  web_app->SetInstallState(proto::SUGGESTED_FROM_ANOTHER_DEVICE);
  RegisterAppUnsafe(std::move(web_app));

  EXPECT_EQ(DisplayMode::kFullscreen,
            registrar().GetEffectiveDisplayModeFromManifest(app_id));

  base::test::TestFuture<void> future;
  fake_provider().scheduler().InstallAppLocally(app_id, future.GetCallback());
  ASSERT_TRUE(future.Wait());

  EXPECT_EQ(DisplayMode::kFullscreen,
            registrar().GetEffectiveDisplayModeFromManifest(app_id));
}

TEST_F(WebAppRegistrarTest, WindowControlsOverlay) {
  StartWebAppProvider();

  auto web_app = test::CreateWebApp();
  const webapps::AppId app_id = web_app->app_id();
  RegisterAppUnsafe(std::move(web_app));

  EXPECT_EQ(false, registrar().GetWindowControlsOverlayEnabled(app_id));

  sync_bridge().SetAppWindowControlsOverlayEnabled(app_id, true);
  EXPECT_EQ(true, registrar().GetWindowControlsOverlayEnabled(app_id));

  sync_bridge().SetAppWindowControlsOverlayEnabled(app_id, false);
  EXPECT_EQ(false, registrar().GetWindowControlsOverlayEnabled(app_id));
}

TEST_F(WebAppRegistrarTest, IsRegisteredLaunchProtocol) {
  StartWebAppProvider();

  apps::ProtocolHandlerInfo protocol_handler_info1;
  protocol_handler_info1.protocol = "web+test";
  protocol_handler_info1.url = GURL("http://example.com/test=%s");

  apps::ProtocolHandlerInfo protocol_handler_info2;
  protocol_handler_info2.protocol = "web+test2";
  protocol_handler_info2.url = GURL("http://example.com/test2=%s");

  auto web_app = test::CreateWebApp(GURL("https://example.com/path"));
  const webapps::AppId app_id = web_app->app_id();
  web_app->SetProtocolHandlers(
      {protocol_handler_info1, protocol_handler_info2});
  RegisterAppUnsafe(std::move(web_app));

  EXPECT_TRUE(registrar().IsRegisteredLaunchProtocol(app_id, "web+test"));
  EXPECT_TRUE(registrar().IsRegisteredLaunchProtocol(app_id, "web+test2"));
  EXPECT_FALSE(registrar().IsRegisteredLaunchProtocol(app_id, "web+test3"));
  EXPECT_FALSE(registrar().IsRegisteredLaunchProtocol(app_id, "mailto"));
}

TEST_F(WebAppRegistrarTest, TestIsDefaultManagementInstalled) {
  StartWebAppProvider();

  auto web_app1 =
      test::CreateWebApp(GURL("https://start.com"), WebAppManagement::kDefault);
  auto web_app2 = test::CreateWebApp(GURL("https://starter.com"),
                                     WebAppManagement::kPolicy);
  const webapps::AppId app_id1 = web_app1->app_id();
  const webapps::AppId app_id2 = web_app2->app_id();
  RegisterAppUnsafe(std::move(web_app1));
  RegisterAppUnsafe(std::move(web_app2));

  // Currently default installed.
  EXPECT_TRUE(registrar().AppMatches(
      app_id1, WebAppFilter::InstalledByDefaultManagement()));
  // Currently installed by source other than installed.
  EXPECT_FALSE(registrar().AppMatches(
      app_id2, WebAppFilter::InstalledByDefaultManagement()));

  // Uninstalling the previously default installed app.
  Uninstall(app_id1);
  EXPECT_FALSE(registrar().AppMatches(
      app_id1, WebAppFilter::InstalledByDefaultManagement()));
}

// This test uses SetLinkCapturingUserPreference, which is not appropriate for
// ChromeOS because link capturing preferences & overlapping scopes have custom
// behavior on CrOS.
#if !BUILDFLAG(IS_CHROMEOS)
TEST_F(WebAppRegistrarTest, AppsDoNotOverlapIfNestedScope) {
  StartWebAppProvider();

  // Initialize 2 apps, with app2 having a scope with the same origin as app1
  // but is nested. If app1 is passed as an input, then app2 is not returned as
  // an overlapping app since nested scopes are not considered overlapping.
  auto web_app1 =
      test::CreateWebApp(GURL("https://example.com"), WebAppManagement::kSync);

  auto web_app2 = test::CreateWebApp(GURL("https://example.com/nested/"),
                                     WebAppManagement::kDefault);
  web_app2->SetLinkCapturingUserPreference(
      proto::NAVIGATION_CAPTURING_PREFERENCE_CAPTURE);

  const webapps::AppId app_id1 = web_app1->app_id();
  const webapps::AppId app_id2 = web_app2->app_id();
  RegisterAppUnsafe(std::move(web_app1));
  RegisterAppUnsafe(std::move(web_app2));

  EXPECT_TRUE(registrar().GetOverlappingAppsMatchingScope(app_id1).empty());
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

using WebAppRegistrarTest_ScopeExtensions = WebAppRegistrarTest;

TEST_F(WebAppRegistrarTest_ScopeExtensions, IsUrlInAppExtendedScope) {
  StartWebAppProvider();

  auto web_app = test::CreateWebApp(GURL("https://example.com/start"));
  webapps::AppId app_id = web_app->app_id();

  // Limit scope to https://example.app/extended_scope
  auto associate_url = GURL("https://example.app");
  auto associate_extended_scope = GURL(associate_url.spec() + "extended_scope");

  // Manifest entry {"origin": "https://*.example.co"}.
  auto associate_url2 = GURL("https://example.co");

  // Full scope
  auto associate_url3 = GURL("https://example.co.uk");

  // Scope with query and fragment
  auto associate_url4 =
      GURL("https://example.com.jp/search?q=asdf+text#fragment");
  auto associate_extended_scope4 = GURL(associate_url4.spec());

  auto associate_url5 = GURL("https://example.com/index.html/#fragment");
  auto associate_extended_scope5 = GURL(associate_url5.spec());

  web_app->SetValidatedScopeExtensions(
      {ScopeExtensionInfo::CreateForScope(associate_extended_scope),
       ScopeExtensionInfo::CreateForScope(associate_url2,
                                          /*has_origin_wildcard=*/true),
       ScopeExtensionInfo::CreateForScope(associate_url3),
       ScopeExtensionInfo::CreateForScope(associate_extended_scope4),
       ScopeExtensionInfo::CreateForScope(associate_extended_scope5)});
  RegisterAppUnsafe(std::move(web_app));

  EXPECT_EQ(
      registrar().GetAppExtendedScopeScore(GURL("https://test.com"), app_id),
      0);

  EXPECT_GT(registrar().GetAppExtendedScopeScore(
                GURL("https://example.com/path"), app_id),
            0);

  // Scope is extended to all sub-domains of example.co with the wildcard
  // prefix.
  EXPECT_GT(registrar().GetAppExtendedScopeScore(GURL("https://app.example.co"),
                                                 app_id),
            0);
  EXPECT_GT(registrar().GetAppExtendedScopeScore(
                GURL("https://test.app.example.co"), app_id),
            0);
  EXPECT_GT(registrar().GetAppExtendedScopeScore(
                GURL("https://example.co/path"), app_id),
            0);

  EXPECT_GT(registrar().GetAppExtendedScopeScore(
                GURL("https://example.app/extended_scope"), app_id),
            0);
  EXPECT_GT(registrar().GetAppExtendedScopeScore(GURL("https://example.co.uk"),
                                                 app_id),
            0);
  EXPECT_GT(registrar().GetAppExtendedScopeScore(
                GURL("https://example.com.jp/search"), app_id),
            0);
  EXPECT_GT(registrar().GetAppExtendedScopeScore(
                GURL("https://example.com.jp/search?q=something"), app_id),
            0);
  EXPECT_GT(registrar().GetAppExtendedScopeScore(
                GURL("https://example.com/index.html"), app_id),
            0);
  EXPECT_GT(registrar().GetAppExtendedScopeScore(
                GURL("https://example.com/index.html#asdfragment"), app_id),
            0);

  // Scope is extended to the example.app domain but not to the sub-domain
  // test.example.app as there was no wildcard prefix.
  EXPECT_EQ(registrar().GetAppExtendedScopeScore(
                GURL("https://test.example.app"), app_id),
            0);

  EXPECT_EQ(registrar().GetAppExtendedScopeScore(
                GURL("https://other.origin.com"), app_id),
            0);
  EXPECT_EQ(registrar().GetAppExtendedScopeScore(GURL("https://testexample.co"),
                                                 app_id),
            0);
  EXPECT_EQ(registrar().GetAppExtendedScopeScore(
                GURL("https://app.example.com"), app_id),
            0);
}

TEST_F(WebAppRegistrarTest_TabStrip, TabbedAppNewTabUrl) {
  StartWebAppProvider();

  auto web_app = test::CreateWebApp(GURL("https://example.com/path"));
  webapps::AppId app_id = web_app->app_id();
  GURL new_tab_url = GURL("https://example.com/path/newtab");

  blink::Manifest::NewTabButtonParams new_tab_button_params;
  new_tab_button_params.url = new_tab_url;
  TabStrip tab_strip;
  tab_strip.new_tab_button = new_tab_button_params;

  web_app->SetDisplayMode(DisplayMode::kTabbed);
  web_app->SetTabStrip(tab_strip);
  web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
  RegisterAppUnsafe(std::move(web_app));

  EXPECT_EQ(registrar().GetAppNewTabUrl(app_id), new_tab_url);
}

TEST_F(WebAppRegistrarTest_TabStrip, TabbedAppAutoNewTabUrl) {
  StartWebAppProvider();

  auto web_app = test::CreateWebApp(GURL("https://example.com/path"));
  webapps::AppId app_id = web_app->app_id();

  web_app->SetDisplayMode(DisplayMode::kTabbed);
  RegisterAppUnsafe(std::move(web_app));

  EXPECT_EQ(registrar().GetAppNewTabUrl(app_id),
            registrar().GetAppStartUrl(app_id));
}

TEST_F(WebAppRegistrarTest, VerifyPlaceholderFinderBehavior) {
  // Please note, this is a bad state done to test crbug.com/40261748.
  // This should not occur once crbug.com/40264854 is implemented.
  StartWebAppProvider();

  // Add first app with install_url in the registry as a non-placeholder app,
  // verify that the app is not a placeholder.
  GURL install_url("https://start_install.com/");
  auto web_app1 =
      test::CreateWebApp(GURL("https://start1.com"), WebAppManagement::kPolicy);
  const webapps::AppId app_id1 = web_app1->app_id();
  RegisterAppUnsafe(std::move(web_app1));
  test::AddInstallUrlAndPlaceholderData(
      profile()->GetPrefs(), &sync_bridge(), app_id1, install_url,
      ExternalInstallSource::kExternalPolicy, /*is_placeholder=*/false);
  EXPECT_FALSE(
      registrar()
          .LookupPlaceholderAppId(install_url, WebAppManagement::kPolicy)
          .has_value());

  // Add second app with same install_url in the registrar as a placeholder,
  // verify that app shows up as a placeholder.
  auto web_app2 =
      test::CreateWebApp(GURL("https://start2.com"), WebAppManagement::kPolicy);
  const webapps::AppId app_id2 = web_app2->app_id();
  RegisterAppUnsafe(std::move(web_app2));
  test::AddInstallUrlAndPlaceholderData(
      profile()->GetPrefs(), &sync_bridge(), app_id2, install_url,
      ExternalInstallSource::kExternalPolicy, /*is_placeholder=*/true);
  auto placeholder_id = registrar().LookupPlaceholderAppId(
      install_url, WebAppManagement::kPolicy);

  // This will fail if the fix for crbug.com/40261748 is reverted.
  EXPECT_TRUE(placeholder_id.has_value());
  EXPECT_EQ(placeholder_id.value(), app_id2);
}

TEST_F(WebAppRegistrarTest, InnerAndOuterScopeIntentPicker) {
  StartWebAppProvider();
  const GURL document_url("https://abc.com/inner/abc.html");

  auto outer_web_app =
      test::CreateWebApp(GURL("https://abc.com"), WebAppManagement::kPolicy);
  outer_web_app->SetName("ABC_Outer");
  outer_web_app->SetInstallState(
      proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
  const webapps::AppId outer_app_id = outer_web_app->app_id();
  RegisterAppUnsafe(std::move(outer_web_app));

  auto inner_web_app = test::CreateWebApp(GURL("https://abc.com/inner"),
                                          WebAppManagement::kDefault);
  inner_web_app->SetName("ABC_Inner");
  inner_web_app->SetInstallState(
      proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
  const webapps::AppId inner_app_id = inner_web_app->app_id();
  RegisterAppUnsafe(std::move(inner_web_app));

  // This should not be considered since the scopes do not match the visited
  // URL.
  auto no_match_scope_app =
      test::CreateWebApp(GURL("https://def.com/"), WebAppManagement::kSync);
  no_match_scope_app->SetName("App_No_Match");
  const webapps::AppId no_match_scope_app_id = no_match_scope_app->app_id();
  RegisterAppUnsafe(std::move(no_match_scope_app));

  // This should not be considered since this app is not set to open in a new
  // window.
  auto browser_mode_app = test::CreateWebApp(
      GURL("https://abc.com/inner/outer"), WebAppManagement::kSync,
      GURL("https://abc.com/inner/"));
  browser_mode_app->SetName("App_Browser");
  browser_mode_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser);
  const webapps::AppId browser_mode_app_id = browser_mode_app->app_id();
  RegisterAppUnsafe(std::move(browser_mode_app));

  EXPECT_THAT(registrar().GetAllAppsControllingUrl(document_url),
              ElementsAre(Pair(inner_app_id, "ABC_Inner"),
                          Pair(outer_app_id, "ABC_Outer")));
}

TEST_F(WebAppRegistrarTest, GetAllAppsControllingUrl_ScopeExtensions) {
  StartWebAppProvider();

  auto web_app_info = WebAppInstallInfo::CreateWithStartUrlForTesting(
      GURL("https://example.com/app/start"));
  web_app_info->user_display_mode = mojom::UserDisplayMode::kStandalone;
  web_app_info->scope = GURL("https://example.com/app/");
  web_app_info->validated_scope_extensions = {
      ScopeExtensionInfo::CreateForOrigin(
          url::Origin::Create(GURL("https://example.org")))};
  webapps::AppId app_id =
      test::InstallWebApp(profile(), std::move(web_app_info));

  const GURL url_in_scope("https://example.com/app/page.html");
  const GURL url_in_extension("https://example.org/page.html");
  const GURL url_outside("https://example.net/page.html");

  auto controlling_apps_in_scope =
      registrar().GetAllAppsControllingUrl(url_in_scope);
  EXPECT_EQ(1u, controlling_apps_in_scope.size());
  EXPECT_EQ(app_id, controlling_apps_in_scope.begin()->first);

  auto controlling_apps_in_extension =
      registrar().GetAllAppsControllingUrl(url_in_extension);
  EXPECT_EQ(1u, controlling_apps_in_extension.size());
  EXPECT_EQ(app_id, controlling_apps_in_extension.begin()->first);

  auto controlling_apps_outside =
      registrar().GetAllAppsControllingUrl(url_outside);
  EXPECT_TRUE(controlling_apps_outside.empty());
}

TEST_F(WebAppRegistrarTest, GetTrustedIconsIfPopulatedSingleNoSize) {
  StartWebAppProvider();
  auto web_app = test::CreateWebApp(GURL("https://abc.com"),
                                    WebAppManagement::kUserInstalled);
  web_app->SetName("ABC");
  web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);

  apps::IconInfo trusted_icon;
  trusted_icon.purpose = apps::IconInfo::Purpose::kAny;
  trusted_icon.square_size_px = 128;
  trusted_icon.url = GURL("https://abc.com/icon.jpg");
  web_app->SetTrustedIcons({trusted_icon});
  const webapps::AppId app_id = web_app->app_id();
  RegisterAppUnsafe(std::move(web_app));

  EXPECT_THAT(registrar().GetTrustedAppIconsMetadata(app_id),
              ElementsAre(trusted_icon));
  EXPECT_EQ(trusted_icon,
            registrar().GetSingleTrustedAppIconForSecuritySurfaces(
                app_id, /*input_size=*/512));
}

TEST_F(WebAppRegistrarTest, EmptyTrustedOrManifestIcons) {
  StartWebAppProvider();
  auto web_app = test::CreateWebApp(GURL("https://abc.com"),
                                    WebAppManagement::kUserInstalled);
  web_app->SetName("ABC");
  web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);

  // Explicitly ensure that there are no manifest or trusted icons.
  web_app->SetManifestIcons({});
  web_app->SetTrustedIcons({});
  const webapps::AppId app_id = web_app->app_id();
  RegisterAppUnsafe(std::move(web_app));

  EXPECT_THAT(registrar().GetTrustedAppIconsMetadata(app_id), IsEmpty());
  EXPECT_EQ(std::nullopt,
            registrar().GetSingleTrustedAppIconForSecuritySurfaces(
                app_id, /*input_size=*/128));
}

TEST_F(WebAppRegistrarTest, NoTrustedIconsFallbackToManifest) {
  StartWebAppProvider();
  auto web_app = test::CreateWebApp(GURL("https://abc.com"),
                                    WebAppManagement::kUserInstalled);
  web_app->SetName("ABC");
  web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);

  // Explicitly ensure that there are no trusted icons, but manifest icons are
  // populated.
  web_app->SetTrustedIcons({});

  apps::IconInfo manifest_icon;
  manifest_icon.purpose = apps::IconInfo::Purpose::kAny;
  manifest_icon.square_size_px = 128;
  manifest_icon.url = GURL("https://abc.com/icon.jpg");
  web_app->SetManifestIcons({manifest_icon});

  const webapps::AppId app_id = web_app->app_id();
  RegisterAppUnsafe(std::move(web_app));

  // There are no trusted app icons, but `manifest_icon` is used as the
  // fallback.
  EXPECT_THAT(registrar().GetTrustedAppIconsMetadata(app_id),
              ElementsAre(manifest_icon));
  EXPECT_EQ(manifest_icon,
            registrar().GetSingleTrustedAppIconForSecuritySurfaces(
                app_id, /*input_size=*/64));
}

TEST_F(WebAppRegistrarTest, NoTrustedIconsFallbackToManifestMultipleIcons) {
  StartWebAppProvider();
  auto web_app = test::CreateWebApp(GURL("https://abc.com"),
                                    WebAppManagement::kUserInstalled);
  web_app->SetName("ABC");
  web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);

  // Explicitly ensure that there are no trusted icons, but manifest icons are
  // populated.
  web_app->SetTrustedIcons({});

  apps::IconInfo manifest_icon1;
  manifest_icon1.purpose = apps::IconInfo::Purpose::kAny;
  manifest_icon1.square_size_px = 128;
  manifest_icon1.url = GURL("https://abc.com/icon.jpg");
  apps::IconInfo manifest_icon2;
  manifest_icon2.purpose = apps::IconInfo::Purpose::kMaskable;
  manifest_icon2.square_size_px = 256;
  manifest_icon2.url = GURL("https://abc.com/icon2.jpg");

  web_app->SetManifestIcons({manifest_icon1, manifest_icon2});

  const webapps::AppId app_id = web_app->app_id();
  RegisterAppUnsafe(std::move(web_app));

  // There are no trusted app icons, but `manifest_icon2` is used as the
  // fallback, since that is closest to the input_size.
  EXPECT_THAT(registrar().GetTrustedAppIconsMetadata(app_id),
              ElementsAre(manifest_icon1, manifest_icon2));
  EXPECT_EQ(manifest_icon2,
            registrar().GetSingleTrustedAppIconForSecuritySurfaces(
                app_id, /*input_size=*/512));
}

TEST_F(WebAppRegistrarTest, MultipleTrustedIconsUseBiggestClosestToSize) {
  StartWebAppProvider();
  auto web_app = test::CreateWebApp(GURL("https://abc.com"),
                                    WebAppManagement::kUserInstalled);
  web_app->SetName("ABC");
  web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
  web_app->SetManifestIcons({});

  apps::IconInfo trusted_icon1;
  trusted_icon1.purpose = apps::IconInfo::Purpose::kAny;
  trusted_icon1.square_size_px = 256;
  trusted_icon1.url = GURL("https://abc.com/icon.jpg");
  apps::IconInfo trusted_icon2;
  trusted_icon2.purpose = apps::IconInfo::Purpose::kMaskable;
  trusted_icon2.square_size_px = 512;
  trusted_icon2.url = GURL("https://abc.com/icon2.jpg");
  apps::IconInfo trusted_icon3;
  trusted_icon3.purpose = apps::IconInfo::Purpose::kAny;
  trusted_icon3.square_size_px = 96;
  trusted_icon3.url = GURL("https://abc.com/icon3.jpg");
  web_app->SetTrustedIcons({trusted_icon1, trusted_icon2, trusted_icon3});

  const webapps::AppId app_id = web_app->app_id();
  RegisterAppUnsafe(std::move(web_app));

  EXPECT_THAT(registrar().GetTrustedAppIconsMetadata(app_id),
              ElementsAre(trusted_icon1, trusted_icon2, trusted_icon3));
  // `trusted_icon1` is used, since it is the biggest icon with size closer to
  // the input size.
  EXPECT_EQ(trusted_icon1,
            registrar().GetSingleTrustedAppIconForSecuritySurfaces(
                app_id, /*input_size=*/128));
}

TEST_F(WebAppRegistrarTest, MultipleTrustedIconsUseSmallerCloserToSize) {
  StartWebAppProvider();
  auto web_app = test::CreateWebApp(GURL("https://abc.com"),
                                    WebAppManagement::kUserInstalled);
  web_app->SetName("ABC");
  web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
  web_app->SetManifestIcons({});

  apps::IconInfo trusted_icon1;
  trusted_icon1.purpose = apps::IconInfo::Purpose::kAny;
  trusted_icon1.square_size_px = 128;
  trusted_icon1.url = GURL("https://abc.com/icon.jpg");
  apps::IconInfo trusted_icon2;
  trusted_icon2.purpose = apps::IconInfo::Purpose::kMaskable;
  trusted_icon2.square_size_px = 256;
  trusted_icon2.url = GURL("https://abc.com/icon2.jpg");
  apps::IconInfo trusted_icon3;
  trusted_icon3.purpose = apps::IconInfo::Purpose::kAny;
  trusted_icon3.square_size_px = 96;
  trusted_icon3.url = GURL("https://abc.com/icon3.jpg");
  web_app->SetTrustedIcons({trusted_icon1, trusted_icon2, trusted_icon3});

  const webapps::AppId app_id = web_app->app_id();
  RegisterAppUnsafe(std::move(web_app));

  EXPECT_THAT(registrar().GetTrustedAppIconsMetadata(app_id),
              ElementsAre(trusted_icon1, trusted_icon2, trusted_icon3));
  // `trusted_icon2` is used, since it is the largest icon with size closer to
  // the input size but smaller than the input size.
  EXPECT_EQ(trusted_icon2,
            registrar().GetSingleTrustedAppIconForSecuritySurfaces(
                app_id, /*input_size=*/512));
}

TEST_F(WebAppRegistrarTest, AllIconSizesHigherThanInputSize) {
  StartWebAppProvider();
  auto web_app = test::CreateWebApp(GURL("https://abc.com"),
                                    WebAppManagement::kUserInstalled);
  web_app->SetName("ABC");
  web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
  web_app->SetManifestIcons({});

  apps::IconInfo trusted_icon1;
  trusted_icon1.purpose = apps::IconInfo::Purpose::kAny;
  trusted_icon1.square_size_px = 128;
  trusted_icon1.url = GURL("https://abc.com/icon.jpg");
  apps::IconInfo trusted_icon2;
  trusted_icon2.purpose = apps::IconInfo::Purpose::kMaskable;
  trusted_icon2.square_size_px = 256;
  trusted_icon2.url = GURL("https://abc.com/icon2.jpg");
  apps::IconInfo trusted_icon3;
  trusted_icon3.purpose = apps::IconInfo::Purpose::kAny;
  trusted_icon3.square_size_px = 96;
  trusted_icon3.url = GURL("https://abc.com/icon3.jpg");
  web_app->SetTrustedIcons({trusted_icon1, trusted_icon2, trusted_icon3});

  const webapps::AppId app_id = web_app->app_id();
  RegisterAppUnsafe(std::move(web_app));

  EXPECT_THAT(registrar().GetTrustedAppIconsMetadata(app_id),
              ElementsAre(trusted_icon1, trusted_icon2, trusted_icon3));
  // `trusted_icon3` is used, since it is the smallest icon with size closer to
  // the input size but larger than the input size.
  EXPECT_EQ(trusted_icon3,
            registrar().GetSingleTrustedAppIconForSecuritySurfaces(
                app_id, /*input_size=*/64));
}

TEST_F(WebAppRegistrarTest, NoSizesProvidedNoMetadata) {
  // Crash fix for
  StartWebAppProvider();
  auto web_app = test::CreateWebApp(GURL("https://abc.com"),
                                    WebAppManagement::kUserInstalled);
  web_app->SetName("ABC");
  web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
  web_app->SetManifestIcons({});

  apps::IconInfo trusted_icon1;
  trusted_icon1.purpose = apps::IconInfo::Purpose::kAny;
  trusted_icon1.url = GURL("https://abc.com/icon.jpg");
  apps::IconInfo trusted_icon2;
  trusted_icon2.purpose = apps::IconInfo::Purpose::kAny;
  trusted_icon2.url = GURL("https://abc.com/icon2.jpg");
  web_app->SetTrustedIcons({trusted_icon1, trusted_icon2});

  const webapps::AppId app_id = web_app->app_id();
  RegisterAppUnsafe(std::move(web_app));

  EXPECT_THAT(registrar().GetTrustedAppIconsMetadata(app_id),
              ElementsAre(trusted_icon1, trusted_icon2));
  EXPECT_EQ(std::nullopt,
            registrar().GetSingleTrustedAppIconForSecuritySurfaces(
                app_id, /*input_size=*/128));
}

TEST_F(WebAppRegistrarTest, TrustedIconMetrics) {
  base::HistogramTester histogram_tester;

  // Set up the registry with 10 apps, and set trusted icons on 5 of them.
  Registry test_registry =
      CreateRegistryForTesting("https://example.com/path", 10);
  int i = 0;
  apps::IconInfo icon_info(GURL("https://www.example.com/icon.png"),
                           icon_size::k48);
  for (auto& apps : test_registry) {
    if (i % 2 == 0) {
      apps.second->SetTrustedIcons({icon_info});
    }
    i++;
  }

  PopulateRegistry(std::move(test_registry));
  StartWebAppProvider();

  EXPECT_THAT(
      histogram_tester.GetAllSamples("WebApp.InstalledCount.HasTrustedIcons"),
      base::BucketsAre(base::Bucket(/*min=*/5,
                                    /*count=*/1)));
  EXPECT_THAT(
      histogram_tester.GetAllSamples("WebApp.InstalledCount.HasNoTrustedIcons"),
      base::BucketsAre(base::Bucket(/*min=*/5,
                                    /*count=*/1)));
}

#if BUILDFLAG(IS_CHROMEOS)

class WebAppRegistrarAshTest : public WebAppTest {
 public:
  void SetUp() override {
    // TODO(crbug.com/40275387): Consider setting up a fake user in all Ash web
    // app tests.
    auto user_manager = std::make_unique<ash::FakeChromeUserManager>();
    auto* fake_user_manager = user_manager.get();
    scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
        std::move(user_manager));
    fake_user_manager->AddUser(user_manager::StubAccountId());
    fake_user_manager->UserLoggedIn(
        user_manager::StubAccountId(),
        user_manager::TestHelper::GetFakeUsernameHash(
            user_manager::StubAccountId()));
    // Need to run the WebAppTest::SetUp() after the fake user manager set up
    // so that the scoped_user_manager can be destructed in the correct order.
    WebAppTest::SetUp();
  }
  WebAppRegistrarAshTest() = default;
  ~WebAppRegistrarAshTest() override = default;

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
};

TEST_F(WebAppRegistrarAshTest, SourceSupported) {
  const GURL example_url("https://example.com/my-app/start");
  const GURL swa_url("chrome://swa/start");
  const GURL uninstalling_url("https://example.com/uninstalling/start");

  webapps::AppId example_id;
  webapps::AppId swa_id;
  webapps::AppId uninstalling_id;
  WebAppRegistrarMutable registrar(profile());
  {
    Registry registry;

    auto example_app = test::CreateWebApp(example_url);
    example_id = example_app->app_id();
    registry.emplace(example_id, std::move(example_app));

    auto swa_app = test::CreateWebApp(swa_url, WebAppManagement::Type::kSystem);
    swa_id = swa_app->app_id();
    registry.emplace(swa_id, std::move(swa_app));

    auto uninstalling_app =
        test::CreateWebApp(uninstalling_url, WebAppManagement::Type::kSystem);
    uninstalling_app->SetIsUninstalling(true);
    uninstalling_id = uninstalling_app->app_id();
    registry.emplace(uninstalling_id, std::move(uninstalling_app));

    registrar.InitRegistry(std::move(registry));
  }

  EXPECT_EQ(registrar.CountUserInstalledApps(), 1);
  EXPECT_EQ(CountApps(registrar.GetApps()), 2);

  EXPECT_EQ(registrar.FindBestAppWithUrlInScope(
                example_url, web_app::WebAppFilter::InstalledInChrome()),
            example_id);
  EXPECT_EQ(registrar.GetAppScope(example_id),
            GURL("https://example.com/my-app/"));
  EXPECT_TRUE(registrar.GetAppUserDisplayMode(example_id).has_value());

  EXPECT_EQ(registrar.FindBestAppWithUrlInScope(
                swa_url, web_app::WebAppFilter::InstalledInChrome()),
            swa_id);
  EXPECT_EQ(registrar.GetAppScope(swa_id), GURL("chrome://swa/"));
  EXPECT_TRUE(registrar.GetAppUserDisplayMode(swa_id).has_value());

  EXPECT_FALSE(
      registrar
          .FindBestAppWithUrlInScope(uninstalling_url,
                                     web_app::WebAppFilter::InstalledInChrome())
          .has_value());
  EXPECT_EQ(registrar.GetAppScope(uninstalling_id),
            GURL("https://example.com/uninstalling/"));
  EXPECT_TRUE(registrar.GetAppUserDisplayMode(uninstalling_id).has_value());
  EXPECT_FALSE(std::ranges::contains(registrar.GetAppIds(), uninstalling_id));
}

#endif  // BUILDFLAG(IS_CHROMEOS)

class WebAppRegistrarDisplayModeTest
    : public WebAppRegistrarTest,
      public testing::WithParamInterface<DisplayMode> {
 public:
  const webapps::AppId CreateAppInRegistryWithUserDisplayModeAndOverrides(
      mojom::UserDisplayMode user_display_mode,
      std::vector<DisplayMode> display_mode_overrides,
      bool is_isolated = false) {
    GURL start_url = GURL("https://example.com/start");
    if (is_isolated) {
      constexpr char kIwaHostname[] =
          "berugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic";
      start_url =
          GURL(base::StrCat({webapps::kIsolatedAppScheme,
                             url::kStandardSchemeSeparator, kIwaHostname}));
    }
    auto web_app = test::CreateWebApp(start_url);
    const webapps::AppId app_id = web_app->app_id();

    // Get the display mode from the parameterized inputs.
    web_app->SetDisplayMode(GetParam());
    web_app->SetUserDisplayMode(user_display_mode);
    web_app->SetInstallState(proto::INSTALLED_WITH_OS_INTEGRATION);
    std::vector<DisplayOverride> override_items;
    for (const auto& mode : display_mode_overrides) {
      override_items.push_back(DisplayOverride::Create(mode));
    }
    web_app->SetDisplayModeOverride(std::move(override_items));

    if (is_isolated) {
      web_app->SetIsolationData(
          IsolationData::Builder(
              IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
              *IwaVersion::Create("1.0.0"))
              .Build());
    }

    RegisterAppUnsafe(std::move(web_app));
    return app_id;
  }

  // When user_display_mode indicates a user preference for opening in
  // a standalone window, we open in a minimal-ui window (for app_display_mode
  // 'browser' or 'minimal-ui') or a standalone window (for app_display_mode
  // 'standalone' or 'fullscreen'). For all other display modes, keep the
  // display modes as they're specified.
  DisplayMode GetResolvedDisplayModeForStandaloneUDM() {
    switch (GetParam()) {
      case DisplayMode::kBrowser:
      case DisplayMode::kMinimalUi:
        return DisplayMode::kMinimalUi;
      case DisplayMode::kStandalone:
      case DisplayMode::kFullscreen:
        return DisplayMode::kStandalone;
      case DisplayMode::kUnframed:
        return DisplayMode::kUnframed;
      case DisplayMode::kWindowControlsOverlay:
        return DisplayMode::kWindowControlsOverlay;
      case DisplayMode::kTabbed:
        return DisplayMode::kTabbed;
      case DisplayMode::kUndefined:
      case DisplayMode::kPictureInPicture:
        NOTREACHED();
    }
  }

  // Same as `GetResolvedDisplayModeForStandaloneUDM()`, except minimal-ui does
  // not exist for IWAs.
  DisplayMode GetResolvedDisplayModeForStandaloneUDMIsolated() {
    switch (GetParam()) {
      case DisplayMode::kBrowser:
      case DisplayMode::kMinimalUi:
      case DisplayMode::kStandalone:
      case DisplayMode::kFullscreen:
      case DisplayMode::kTabbed:
        return DisplayMode::kStandalone;
      case DisplayMode::kUnframed:
        return DisplayMode::kUnframed;
      case DisplayMode::kWindowControlsOverlay:
        return DisplayMode::kWindowControlsOverlay;
      case DisplayMode::kUndefined:
      case DisplayMode::kPictureInPicture:
        NOTREACHED();
    }
  }

 private:
  base::test::ScopedFeatureList feature_list_{features::kIsolatedWebApps};
};

// When user_display_mode indicates a user preference for opening in a browser
// tab, we open in a browser tab.
TEST_P(WebAppRegistrarDisplayModeTest, UserWantsBrowser) {
  StartWebAppProvider();
  const webapps::AppId app_id =
      CreateAppInRegistryWithUserDisplayModeAndOverrides(
          mojom::UserDisplayMode::kBrowser, {});
  EXPECT_EQ(DisplayMode::kBrowser,
            registrar().GetAppEffectiveDisplayMode(app_id));
}

TEST_P(WebAppRegistrarDisplayModeTest, UserWantsStandalone) {
  if (GetParam() == DisplayMode::kPictureInPicture) {
    GTEST_SKIP()
        << "PictureInPicture not supported for standalone display modes";
  }
  StartWebAppProvider();
  const webapps::AppId app_id =
      CreateAppInRegistryWithUserDisplayModeAndOverrides(
          mojom::UserDisplayMode::kStandalone, {});
  EXPECT_EQ(GetResolvedDisplayModeForStandaloneUDM(),
            registrar().GetAppEffectiveDisplayMode(app_id));
}

// When user_display_mode indicates a user preference for opening in a browser
// tab, we open in a browser tab, even if display_overrides are specified.
TEST_P(WebAppRegistrarDisplayModeTest, UserWantsBrowserStandaloneOverride) {
  StartWebAppProvider();
  const webapps::AppId app_id =
      CreateAppInRegistryWithUserDisplayModeAndOverrides(
          mojom::UserDisplayMode::kBrowser, {DisplayMode::kStandalone});
  EXPECT_EQ(DisplayMode::kBrowser,
            registrar().GetAppEffectiveDisplayMode(app_id));
}

// When user_display_mode indicates a user preference for opening in
// a standalone window, and the only display modes provided for
// display_overrides contain only 'fullscreen' or 'browser',  open in a
// minimal-ui window (for app_display_mode 'browser' or 'minimal-ui') or a
// standalone window (for app_display_mode 'standalone' or 'fullscreen').
TEST_P(WebAppRegistrarDisplayModeTest, UserWantsStandaloneFullScreenOverride) {
  if (GetParam() == DisplayMode::kPictureInPicture) {
    GTEST_SKIP()
        << "PictureInPicture not supported for standalone display modes";
  }
  StartWebAppProvider();
  const webapps::AppId app_id =
      CreateAppInRegistryWithUserDisplayModeAndOverrides(
          mojom::UserDisplayMode::kStandalone, {DisplayMode::kFullscreen});
  EXPECT_EQ(GetResolvedDisplayModeForStandaloneUDM(),
            registrar().GetAppEffectiveDisplayMode(app_id));
}

// When user_display_mode indicates a user preference for opening in
// a standalone window, and return the first entry that is either
// 'standalone' or 'minimal-ui' in display_override.
TEST_P(WebAppRegistrarDisplayModeTest, UserWantsStandaloneMultipleOverrides) {
  if (GetParam() == DisplayMode::kPictureInPicture) {
    GTEST_SKIP()
        << "PictureInPicture not supported for standalone display modes";
  }
  StartWebAppProvider();
  const webapps::AppId app_id =
      CreateAppInRegistryWithUserDisplayModeAndOverrides(
          mojom::UserDisplayMode::kStandalone,
          {DisplayMode::kFullscreen, DisplayMode::kBrowser,
           DisplayMode::kStandalone});
  EXPECT_EQ(DisplayMode::kStandalone,
            registrar().GetAppEffectiveDisplayMode(app_id));
}

TEST_P(WebAppRegistrarDisplayModeTest, UserStandaloneNoOverrideIsolated) {
  if (GetParam() == DisplayMode::kPictureInPicture) {
    GTEST_SKIP()
        << "PictureInPicture not supported for standalone display modes";
  }
  StartWebAppProvider();
  const webapps::AppId app_id =
      CreateAppInRegistryWithUserDisplayModeAndOverrides(
          mojom::UserDisplayMode::kStandalone, {}, /*is_isolated=*/true);
  EXPECT_EQ(GetResolvedDisplayModeForStandaloneUDMIsolated(),
            registrar().GetAppEffectiveDisplayMode(app_id));
}

INSTANTIATE_TEST_SUITE_P(All,
                         WebAppRegistrarDisplayModeTest,
                         testing::Values(DisplayMode::kBrowser,
                                         DisplayMode::kMinimalUi,
                                         DisplayMode::kStandalone,
                                         DisplayMode::kFullscreen,
                                         DisplayMode::kUnframed,
                                         DisplayMode::kPictureInPicture,
                                         DisplayMode::kWindowControlsOverlay,
                                         DisplayMode::kTabbed),
                         [](const testing::TestParamInfo<DisplayMode>& info) {
                           switch (info.param) {
                             case DisplayMode::kBrowser:
                               return "Browser";
                             case DisplayMode::kMinimalUi:
                               return "MinimalUi";
                             case DisplayMode::kStandalone:
                               return "Standalone";
                             case DisplayMode::kFullscreen:
                               return "Fullscreen";
                             case DisplayMode::kUnframed:
                               return "Unframed";
                             case DisplayMode::kPictureInPicture:
                               return "PictureInPicture";
                             case DisplayMode::kWindowControlsOverlay:
                               return "WindowControlsOverlay";
                             case DisplayMode::kTabbed:
                               return "Tabbed";
                             case DisplayMode::kUndefined:
                               NOTREACHED();
                           }
                         });

class WebAppRegistrarParameterizedTest
    : public WebAppRegistrarTest,
      public testing::WithParamInterface<
          apps::test::LinkCapturingFeatureVersion> {
 public:
  WebAppRegistrarParameterizedTest() {
    link_capturing_feature_list_.InitWithFeaturesAndParameters(
        apps::test::GetFeaturesToEnableLinkCapturingUX(GetParam()), {});
  }

 private:
  base::test::ScopedFeatureList link_capturing_feature_list_;
};

// These tests use SetLinkCapturingUserPreference, which is not appropriate for
// ChromeOS because link capturing preferences & overlapping scopes have custom
// behavior on CrOS.
#if !BUILDFLAG(IS_CHROMEOS)
TEST_P(WebAppRegistrarParameterizedTest, AppsOverlapIfSharesScope) {
  StartWebAppProvider();

  // Initialize 2 apps, both having the same scope, and set the second
  // app to capture links. If app1 is passed as an input, then
  // app2 is returned as an overlapping app that matches the scope and
  // is set by the user to handle links.
  auto web_app1 =
      test::CreateWebApp(GURL("https://example.com"), WebAppManagement::kSync);
  auto web_app2 = test::CreateWebApp(GURL("https://example.com/def"),
                                     WebAppManagement::kDefault,
                                     GURL("https://example.com"));
  web_app2->SetLinkCapturingUserPreference(
      proto::NAVIGATION_CAPTURING_PREFERENCE_CAPTURE);

  const webapps::AppId app_id1 = web_app1->app_id();
  const webapps::AppId app_id2 = web_app2->app_id();
  RegisterAppUnsafe(std::move(web_app1));
  RegisterAppUnsafe(std::move(web_app2));

  EXPECT_THAT(registrar().GetOverlappingAppsMatchingScope(app_id1),
              ElementsAre(app_id2));
}

TEST_P(WebAppRegistrarParameterizedTest, Filter_OpensInBrowserTab) {
  StartWebAppProvider();

  GURL app_url_1 = GURL("https://example.com/1/");
  GURL app_url_2 = GURL("https://example.com/2/");
  auto web_app_1 =
      test::CreateWebApp(app_url_1, WebAppManagement::kUserInstalled);
  auto web_app_2 =
      test::CreateWebApp(app_url_2, WebAppManagement::kUserInstalled);
  const webapps::AppId app_id_browser_tab = web_app_1->app_id();
  const webapps::AppId app_id_standalone = web_app_2->app_id();

  web_app_1->SetDisplayMode(DisplayMode::kBrowser);
  web_app_1->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser);
  web_app_1->SetInstallState(proto::INSTALLED_WITH_OS_INTEGRATION);
  web_app_1->SetLinkCapturingUserPreference(
      proto::NAVIGATION_CAPTURING_PREFERENCE_CAPTURE);

  web_app_2->SetDisplayMode(DisplayMode::kStandalone);
  web_app_2->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone);
  web_app_2->SetInstallState(proto::INSTALLED_WITH_OS_INTEGRATION);
  web_app_2->SetLinkCapturingUserPreference(
      proto::NAVIGATION_CAPTURING_PREFERENCE_CAPTURE);

  RegisterAppUnsafe(std::move(web_app_1));
  RegisterAppUnsafe(std::move(web_app_2));

  EXPECT_TRUE(registrar().AppMatches(app_id_browser_tab,
                                     WebAppFilter::OpensInBrowserTab()));
  EXPECT_FALSE(registrar().AppMatches(app_id_browser_tab,
                                      WebAppFilter::OpensInDedicatedWindow()));
  EXPECT_EQ(app_id_browser_tab,
            registrar().FindBestAppWithUrlInScope(
                app_url_1, WebAppFilter::OpensInBrowserTab()));
  EXPECT_EQ(std::nullopt,
            registrar().FindBestAppWithUrlInScope(
                app_url_1, WebAppFilter::OpensInDedicatedWindow()));

  EXPECT_TRUE(registrar().AppMatches(app_id_standalone,
                                     WebAppFilter::OpensInDedicatedWindow()));
  EXPECT_FALSE(registrar().AppMatches(app_id_standalone,
                                      WebAppFilter::OpensInBrowserTab()));
  EXPECT_EQ(app_id_standalone,
            registrar().FindBestAppWithUrlInScope(
                app_url_2, WebAppFilter::OpensInDedicatedWindow()));
  EXPECT_EQ(std::nullopt, registrar().FindBestAppWithUrlInScope(
                              app_url_2, WebAppFilter::OpensInBrowserTab()));
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

TEST_P(WebAppRegistrarParameterizedTest, Filter_IsIsolatedApp) {
  base::test::ScopedFeatureList scoped_feature_list(features::kIsolatedWebApps);
  StartWebAppProvider();

  constexpr char kIwaHostname[] =
      "berugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic";
  GURL app_url(base::StrCat({webapps::kIsolatedAppScheme,
                             url::kStandardSchemeSeparator, kIwaHostname}));
  auto isolated_web_app = test::CreateWebApp(app_url);
  const webapps::AppId app_id = isolated_web_app->app_id();

  isolated_web_app->SetIsolationData(
      IsolationData::Builder(
          IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
          *IwaVersion::Create("1.0.0"))
          .Build());
  isolated_web_app->SetDisplayMode(DisplayMode::kBrowser);
  isolated_web_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser);
  RegisterAppUnsafe(std::move(isolated_web_app));

  EXPECT_TRUE(registrar().AppMatches(app_id, WebAppFilter::IsIsolatedApp()));
  EXPECT_EQ(app_id, registrar().FindBestAppWithUrlInScope(
                        app_url, WebAppFilter::IsIsolatedApp()));
}

TEST_P(WebAppRegistrarParameterizedTest, Filter_IsIsolatedSubApp) {
  base::test::ScopedFeatureList scoped_feature_list(features::kIsolatedWebApps);
  StartWebAppProvider();

  constexpr char kIwaHostname[] =
      "berugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic";
  GURL app_url(base::StrCat({webapps::kIsolatedAppScheme,
                             url::kStandardSchemeSeparator, kIwaHostname}));
  auto isolated_web_app = test::CreateWebApp(app_url);
  const webapps::AppId app_id = isolated_web_app->app_id();

  isolated_web_app->SetIsolationData(
      IsolationData::Builder(
          IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
          *IwaVersion::Create("1.0.0"))
          .Build());
  isolated_web_app->SetDisplayMode(DisplayMode::kBrowser);
  isolated_web_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser);
  RegisterAppUnsafe(std::move(isolated_web_app));

  constexpr char kSubIwaHostname[] =
      "berugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic";
  GURL sub_app_url(
      base::StrCat({webapps::kIsolatedAppScheme, url::kStandardSchemeSeparator,
                    kSubIwaHostname, "/sub_app/"}));
  auto isolated_sub_app = test::CreateWebApp(sub_app_url);
  isolated_sub_app->SetParentAppId(app_id);
  const webapps::AppId sub_app_id = isolated_sub_app->app_id();
  RegisterAppUnsafe(std::move(isolated_sub_app));

  EXPECT_TRUE(
      registrar().AppMatches(sub_app_id, WebAppFilter::IsIsolatedSubApp()));
  EXPECT_EQ(sub_app_id, registrar().FindBestAppWithUrlInScope(
                            sub_app_url, WebAppFilter::IsIsolatedSubApp()));
}

INSTANTIATE_TEST_SUITE_P(
    ,
    WebAppRegistrarParameterizedTest,
    testing::Values(apps::test::LinkCapturingFeatureVersion::kV2DefaultOff,
                    apps::test::LinkCapturingFeatureVersion::kV2DefaultOn),
    apps::test::LinkCapturingVersionToString);

}  // namespace web_app
