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

#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/web_applications/model/display_override.h"
#include "chrome/browser/web_applications/proto/web_app.pb.h"
#include "chrome/browser/web_applications/proto/web_app_database_metadata.pb.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/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_proto_utils.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "components/webapps/common/web_app_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"

namespace web_app {
namespace {

// Creates a protobuf web app that passes the parsing checks.
proto::WebApp CreateWebAppProtoForTesting(const std::string& name,
                                          const GURL& start_url) {
  proto::WebApp web_app;
  CHECK(start_url.is_valid());
  web_app.set_name(name);
  web_app.mutable_sync_data()->set_name(name);
  web_app.mutable_sync_data()->set_start_url(start_url.spec());
  webapps::ManifestId manifest_id =
      GenerateManifestIdFromStartUrlOnly(start_url);
  web_app.mutable_sync_data()->set_relative_manifest_id(
      RelativeManifestIdPath(manifest_id));
  web_app.mutable_sync_data()->set_user_display_mode_default(
      sync_pb::WebAppSpecifics_UserDisplayMode::
          WebAppSpecifics_UserDisplayMode_STANDALONE);
  web_app.set_scope(start_url.GetWithoutFilename().spec());
  web_app.mutable_sources()->set_user_installed(true);
#if BUILDFLAG(IS_CHROMEOS)
  web_app.mutable_chromeos_data();
  web_app.mutable_sync_data()->set_user_display_mode_cros(
      sync_pb::WebAppSpecifics_UserDisplayMode::
          WebAppSpecifics_UserDisplayMode_STANDALONE);
#endif
  web_app.set_install_state(
      proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION);
  return web_app;
}

webapps::AppId GetAppIdFromWebAppProto(const proto::WebApp& web_app) {
  CHECK(web_app.has_sync_data());
  CHECK(web_app.sync_data().has_relative_manifest_id());
  CHECK(web_app.sync_data().has_start_url());
  GURL start_url = GURL(web_app.sync_data().start_url());
  CHECK(start_url.is_valid());
  return GenerateAppId(web_app.sync_data().relative_manifest_id(), start_url);
}

// Helper function to verify database state matches registrar state.
void VerifyDatabaseRegistryEqualToRegistrar(FakeWebAppProvider* provider) {
  FakeWebAppDatabaseFactory* database_factory =
      provider->GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_NE(database_factory, nullptr);

  Registry db_registry =
      database_factory->ReadRegistry(/*allow_invalid_protos=*/true);
  const Registry& registrar_registry =
      provider->registrar_unsafe().registry_for_testing();

  EXPECT_TRUE(IsRegistryEqual(registrar_registry, db_registry));
}

using WebAppDatabaseMigrationTest = WebAppTest;

// Installs a shortcut app in the profile by emptying its scope.
TEST_F(WebAppDatabaseMigrationTest, MigrateShortcutToDiyApp) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // Create a webapp protobuf that should be 'migrated'
  proto::WebApp web_app_no_scope =
      CreateWebAppProtoForTesting("Test App", GURL("https://www.example.com/"));
  web_app_no_scope.clear_scope();

  proto::WebApp web_app_last_installed_from_shortcut =
      CreateWebAppProtoForTesting("App Name 2", GURL("https://example2.com"));
  web_app_last_installed_from_shortcut.set_latest_install_source(
      static_cast<uint32_t>(
          webapps::WebappInstallSource::MENU_CREATE_SHORTCUT));

  // Write them to the database.
  database_factory->WriteProtos(
      {web_app_no_scope, web_app_last_installed_from_shortcut});

  // Make sure we are only on version 1
  proto::DatabaseMetadata metadata;
  metadata.set_version(1);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  EXPECT_THAT(histograms.GetAllSamples("WebApp.Migrations.ShortcutAppsToDiy2"),
              base::BucketsAre(base::Bucket(/*min=*/2, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration occurs.
  webapps::AppId no_scope_app_id = GetAppIdFromWebAppProto(web_app_no_scope);
  webapps::AppId last_installed_from_shortcut_app_id =
      GetAppIdFromWebAppProto(web_app_last_installed_from_shortcut);

  ASSERT_TRUE(registrar.GetAppById(no_scope_app_id));
  ASSERT_TRUE(registrar.GetAppById(last_installed_from_shortcut_app_id));

  // Both are DIY.
  EXPECT_TRUE(registrar.GetAppById(no_scope_app_id)->is_diy_app());
  EXPECT_TRUE(
      registrar.GetAppById(last_installed_from_shortcut_app_id)->is_diy_app());

  // Both are marked as migrating from shortcut.
  EXPECT_TRUE(registrar.GetAppById(no_scope_app_id)->was_shortcut_app());
  EXPECT_TRUE(registrar.GetAppById(last_installed_from_shortcut_app_id)
                  ->was_shortcut_app());

  // Both have a scope.
  EXPECT_TRUE(registrar.GetAppById(no_scope_app_id)->scope().is_valid());
  EXPECT_TRUE(registrar.GetAppById(last_installed_from_shortcut_app_id)
                  ->scope()
                  .is_valid());

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseMigrationTest, MigrateInstallSourceAddUserInstalled) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // App 1: Only kSync source (should gain kUserInstalled)
  proto::WebApp web_app_sync_only =
      CreateWebAppProtoForTesting("App 1", GURL("https://app1.com/"));
  web_app_sync_only.mutable_sources()->clear_user_installed();
  web_app_sync_only.mutable_sources()->set_sync(true);

  // App 2: kSync and kUserInstalled (should remain unchanged)
  // This state should be impossible, as there was no kUserInstalled at v0.
  proto::WebApp web_app_sync_and_user =
      CreateWebAppProtoForTesting("App 2", GURL("https://app2.com/"));
  web_app_sync_and_user.mutable_sources()->set_sync(true);
  web_app_sync_and_user.mutable_sources()->set_user_installed(true);

  // App 3: Only kUserInstalled (should remain unchanged)
  // This state should be impossible, as there was no kUserInstalled at v0.
  proto::WebApp web_app_user_only =
      CreateWebAppProtoForTesting("App 3", GURL("https://app3.com/"));
  web_app_user_only.mutable_sources()->clear_sync();
  web_app_user_only.mutable_sources()->set_user_installed(true);

  // App 4: Only kPolicy (should remain unchanged)
  proto::WebApp web_app_policy_only =
      CreateWebAppProtoForTesting("App 4", GURL("https://app4.com/"));
  web_app_policy_only.mutable_sources()->clear_sync();
  web_app_policy_only.mutable_sources()->clear_user_installed();
  web_app_policy_only.mutable_sources()->set_policy(true);

  database_factory->WriteProtos({web_app_sync_only, web_app_sync_and_user,
                                 web_app_user_only, web_app_policy_only});

  // Set version to 0 to trigger the migration to version 1 (and subsequently
  // 2).
  proto::DatabaseMetadata metadata;
  metadata.set_version(0);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;

  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.InstallSourceAddUserInstalled"),
              base::BucketsAre(base::Bucket(/*min=*/1, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration results
  const WebApp* app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(web_app_sync_only));
  const WebApp* app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(web_app_sync_and_user));
  const WebApp* app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(web_app_user_only));
  const WebApp* app4 =
      registrar.GetAppById(GetAppIdFromWebAppProto(web_app_policy_only));

  ASSERT_TRUE(app1);
  ASSERT_TRUE(app2);
  ASSERT_TRUE(app3);
  ASSERT_TRUE(app4);

  // App 1: Should now have kSync and kUserInstalled
  EXPECT_TRUE(app1->GetSources().Has(WebAppManagement::kSync));
  EXPECT_TRUE(app1->GetSources().Has(WebAppManagement::kUserInstalled));
  EXPECT_EQ(app1->GetSources().size(), 2u);

  // App 2: Should remain kSync and kUserInstalled
  EXPECT_TRUE(app2->GetSources().Has(WebAppManagement::kSync));
  EXPECT_TRUE(app2->GetSources().Has(WebAppManagement::kUserInstalled));
  EXPECT_EQ(app2->GetSources().size(), 2u);

  // App 3: Should remain kUserInstalled only
  EXPECT_FALSE(app3->GetSources().Has(WebAppManagement::kSync));
  EXPECT_TRUE(app3->GetSources().Has(WebAppManagement::kUserInstalled));
  EXPECT_EQ(app3->GetSources().size(), 1u);

  // App 4: Should remain kPolicy only
  EXPECT_FALSE(app4->GetSources().Has(WebAppManagement::kSync));
  EXPECT_FALSE(app4->GetSources().Has(WebAppManagement::kUserInstalled));
  EXPECT_TRUE(app4->GetSources().Has(WebAppManagement::kPolicy));
  EXPECT_EQ(app4->GetSources().size(), 1u);

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseMigrationTest,
       MigrateInstallSourceAddUserInstalled_SyncDisabled) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // App 1: Only kSync source (should become kUserInstalled only)
  proto::WebApp web_app_sync_only =
      CreateWebAppProtoForTesting("App 1", GURL("https://app1.com/"));
  web_app_sync_only.mutable_sources()->clear_user_installed();
  web_app_sync_only.mutable_sources()->set_sync(true);

  // App 2: kSync and kUserInstalled (should become kUserInstalled only)
  proto::WebApp web_app_sync_and_user =
      CreateWebAppProtoForTesting("App 2", GURL("https://app2.com/"));
  web_app_sync_and_user.mutable_sources()->set_sync(true);
  web_app_sync_and_user.mutable_sources()->set_user_installed(true);

  // App 3: Only kUserInstalled (should remain unchanged)
  proto::WebApp web_app_user_only =
      CreateWebAppProtoForTesting("App 3", GURL("https://app3.com/"));
  web_app_user_only.mutable_sources()->clear_sync();
  web_app_user_only.mutable_sources()->set_user_installed(true);

  // App 4: Only kPolicy (should remain unchanged)
  proto::WebApp web_app_policy_only =
      CreateWebAppProtoForTesting("App 4", GURL("https://app4.com/"));
  web_app_policy_only.mutable_sources()->clear_sync();
  web_app_policy_only.mutable_sources()->clear_user_installed();
  web_app_policy_only.mutable_sources()->set_policy(true);

  database_factory->WriteProtos({web_app_sync_only, web_app_sync_and_user,
                                 web_app_user_only, web_app_policy_only});

  // Set version to 0 to trigger the migration to version 1 (and subsequently
  // 2).
  proto::DatabaseMetadata metadata;
  metadata.set_version(0);
  database_factory->WriteMetadata(metadata);

  // Explicitly disable sync.
  database_factory->set_is_syncing_apps(false);

  base::HistogramTester histograms;

  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  // Two apps should be affected, as kSync was removed from two.
  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.InstallSourceAddUserInstalled"),
              base::BucketsAre(base::Bucket(/*min=*/2, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration results
  const WebApp* app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(web_app_sync_only));
  const WebApp* app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(web_app_sync_and_user));
  const WebApp* app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(web_app_user_only));
  const WebApp* app4 =
      registrar.GetAppById(GetAppIdFromWebAppProto(web_app_policy_only));

  ASSERT_TRUE(app1);
  ASSERT_TRUE(app2);
  ASSERT_TRUE(app3);
  ASSERT_TRUE(app4);

  // App 1: Should now have only kUserInstalled (kSync removed)
  EXPECT_FALSE(app1->GetSources().Has(WebAppManagement::kSync));
  EXPECT_TRUE(app1->GetSources().Has(WebAppManagement::kUserInstalled));
  EXPECT_EQ(app1->GetSources().size(), 1u);

  // App 2: Should now have only kUserInstalled (kSync removed)
  EXPECT_FALSE(app2->GetSources().Has(WebAppManagement::kSync));
  EXPECT_TRUE(app2->GetSources().Has(WebAppManagement::kUserInstalled));
  EXPECT_EQ(app2->GetSources().size(), 1u);

  // App 3: Should remain kUserInstalled only
  EXPECT_FALSE(app3->GetSources().Has(WebAppManagement::kSync));
  EXPECT_TRUE(app3->GetSources().Has(WebAppManagement::kUserInstalled));
  EXPECT_EQ(app3->GetSources().size(), 1u);

  // App 4: Should remain kPolicy only
  EXPECT_FALSE(app4->GetSources().Has(WebAppManagement::kSync));
  EXPECT_FALSE(app4->GetSources().Has(WebAppManagement::kUserInstalled));
  EXPECT_TRUE(app4->GetSources().Has(WebAppManagement::kPolicy));
  EXPECT_EQ(app4->GetSources().size(), 1u);

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseMigrationTest,
       MigrateDefaultDisplayModeToPlatformDisplayMode) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // App 1: Missing CrOS UDM, has Default UDM (Browser)
  proto::WebApp web_app_missing_cros =
      CreateWebAppProtoForTesting("App 1", GURL("https://app1.com/"));
  web_app_missing_cros.mutable_sync_data()->set_user_display_mode_default(
      sync_pb::WebAppSpecifics_UserDisplayMode_BROWSER);
  web_app_missing_cros.mutable_sync_data()->clear_user_display_mode_cros();

  // App 2: Has CrOS UDM (Standalone), missing Default UDM
  proto::WebApp web_app_missing_default =
      CreateWebAppProtoForTesting("App 2", GURL("https://app2.com/"));
  web_app_missing_default.mutable_sync_data()->set_user_display_mode_cros(
      sync_pb::WebAppSpecifics_UserDisplayMode_STANDALONE);
  web_app_missing_default.mutable_sync_data()
      ->clear_user_display_mode_default();

  // App 3: Missing both UDMs
  proto::WebApp web_app_missing_both =
      CreateWebAppProtoForTesting("App 3", GURL("https://app3.com/"));
  web_app_missing_both.mutable_sync_data()->clear_user_display_mode_cros();
  web_app_missing_both.mutable_sync_data()->clear_user_display_mode_default();

  // App 4: Has both UDMs (correct state, should not change)
  proto::WebApp web_app_correct =
      CreateWebAppProtoForTesting("App 4", GURL("https://app4.com/"));
  web_app_correct.mutable_sync_data()->set_user_display_mode_cros(
      sync_pb::WebAppSpecifics_UserDisplayMode_STANDALONE);
  web_app_correct.mutable_sync_data()->set_user_display_mode_default(
      sync_pb::WebAppSpecifics_UserDisplayMode_BROWSER);

  database_factory->WriteProtos({web_app_missing_cros, web_app_missing_default,
                                 web_app_missing_both, web_app_correct});

  // Set version to 1 to trigger the migration to version 2.
  proto::DatabaseMetadata metadata;
  metadata.set_version(1);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.DefaultDisplayModeToPlatform"),
              base::BucketsAre(base::Bucket(/*min=*/2, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration results
  const WebApp* app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(web_app_missing_cros));
  const WebApp* app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(web_app_missing_default));
  const WebApp* app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(web_app_missing_both));
  const WebApp* app4 =
      registrar.GetAppById(GetAppIdFromWebAppProto(web_app_correct));

  ASSERT_TRUE(app1);
  ASSERT_TRUE(app2);
  ASSERT_TRUE(app3);
  ASSERT_TRUE(app4);

#if BUILDFLAG(IS_CHROMEOS)
  // App 1: CrOS UDM should be populated from Default (Browser)
  EXPECT_TRUE(app1->sync_proto().has_user_display_mode_cros());
  EXPECT_EQ(app1->sync_proto().user_display_mode_cros(),
            sync_pb::WebAppSpecifics_UserDisplayMode_BROWSER);
  EXPECT_TRUE(
      app1->sync_proto().has_user_display_mode_default());  // Default remains
  EXPECT_EQ(app1->sync_proto().user_display_mode_default(),
            sync_pb::WebAppSpecifics_UserDisplayMode_BROWSER);

  // App 2: Default UDM should remain absent (CrOS doesn't populate Default)
  EXPECT_TRUE(app2->sync_proto().has_user_display_mode_cros());  // CrOS remains
  EXPECT_EQ(app2->sync_proto().user_display_mode_cros(),
            sync_pb::WebAppSpecifics_UserDisplayMode_STANDALONE);
  EXPECT_FALSE(app2->sync_proto().has_user_display_mode_default());

  // App 3: CrOS UDM should default to Standalone
  EXPECT_TRUE(app3->sync_proto().has_user_display_mode_cros());
  EXPECT_EQ(app3->sync_proto().user_display_mode_cros(),
            sync_pb::WebAppSpecifics_UserDisplayMode_STANDALONE);
  EXPECT_FALSE(app3->sync_proto()
                   .has_user_display_mode_default());  // Default remains absent
#else  // !BUILDFLAG(IS_CHROMEOS)
  // App 1: Default UDM should remain Browser, CrOS should remain absent
  EXPECT_FALSE(app1->sync_proto().has_user_display_mode_cros());
  EXPECT_TRUE(app1->sync_proto().has_user_display_mode_default());
  EXPECT_EQ(app1->sync_proto().user_display_mode_default(),
            sync_pb::WebAppSpecifics_UserDisplayMode_BROWSER);

  // App 2: Default UDM should be populated (Standalone), CrOS remains
  // Standalone
  EXPECT_TRUE(app2->sync_proto().has_user_display_mode_cros());
  EXPECT_EQ(app2->sync_proto().user_display_mode_cros(),
            sync_pb::WebAppSpecifics_UserDisplayMode_STANDALONE);
  EXPECT_TRUE(app2->sync_proto().has_user_display_mode_default());
  EXPECT_EQ(app2->sync_proto().user_display_mode_default(),
            sync_pb::WebAppSpecifics_UserDisplayMode_STANDALONE);

  // App 3: Default UDM should default to Standalone, CrOS remains absent
  EXPECT_FALSE(app3->sync_proto().has_user_display_mode_cros());
  EXPECT_TRUE(app3->sync_proto().has_user_display_mode_default());
  EXPECT_EQ(app3->sync_proto().user_display_mode_default(),
            sync_pb::WebAppSpecifics_UserDisplayMode_STANDALONE);
#endif

  // App 4: Should remain unchanged
  EXPECT_TRUE(app4->sync_proto().has_user_display_mode_cros());
  EXPECT_EQ(app4->sync_proto().user_display_mode_cros(),
            sync_pb::WebAppSpecifics_UserDisplayMode_STANDALONE);
  EXPECT_TRUE(app4->sync_proto().has_user_display_mode_default());
  EXPECT_EQ(app4->sync_proto().user_display_mode_default(),
            sync_pb::WebAppSpecifics_UserDisplayMode_BROWSER);

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseMigrationTest,
       MigratePartiallyInstalledAppsToCorrectState) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // App 1: Correctly installed with OS integration
  proto::WebApp web_app_correct =
      CreateWebAppProtoForTesting("App 1", GURL("https://app1.com/"));
  web_app_correct.set_install_state(
      proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
  web_app_correct.mutable_current_os_integration_states()
      ->mutable_shortcut();  // Add some OS state

  // App 2: Incorrectly marked as integrated, but no OS state
  proto::WebApp web_app_incorrect =
      CreateWebAppProtoForTesting("App 2", GURL("https://app2.com/"));
  web_app_incorrect.set_install_state(
      proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
  // No OS state added

  // App 3: Correctly installed without OS integration
  proto::WebApp web_app_no_integration =
      CreateWebAppProtoForTesting("App 3", GURL("https://app3.com/"));
  web_app_no_integration.set_install_state(
      proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION);

  database_factory->WriteProtos(
      {web_app_correct, web_app_incorrect, web_app_no_integration});

  // Set version to 1 to trigger the migration to version 2.
  proto::DatabaseMetadata metadata;
  metadata.set_version(1);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.PartiallyInstalledAppsToCorrectState"),
              base::BucketsAre(base::Bucket(/*min=*/1, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration results
  const WebApp* app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(web_app_correct));
  const WebApp* app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(web_app_incorrect));
  const WebApp* app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(web_app_no_integration));

  ASSERT_TRUE(app1);
  ASSERT_TRUE(app2);
  ASSERT_TRUE(app3);

  // App 1: Should remain integrated
  EXPECT_EQ(app1->install_state(),
            proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);

  // App 2: Should be downgraded to not integrated
  EXPECT_EQ(app2->install_state(),
            proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION);

  // App 3: Should remain not integrated
  EXPECT_EQ(app3->install_state(),
            proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION);

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseMigrationTest,
       MigrateDeprecatedLaunchHandlerToClientMode) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();

  // App 1: No launch handler (should remain null, no migration)
  proto::WebApp app_no_handler =
      CreateWebAppProtoForTesting("App 1", GURL("https://app1.com/"));

  // App 2: Modern client_mode already set (should not change, no migration)
  proto::WebApp app_modern =
      CreateWebAppProtoForTesting("App 2", GURL("https://app2.com/"));
  {
    proto::LaunchHandler* launch_handler = app_modern.mutable_launch_handler();
    launch_handler->set_client_mode(
        proto::LaunchHandler::CLIENT_MODE_NAVIGATE_NEW);
  }

  // App 3: Old route_to = NEW_CLIENT
  proto::WebApp app_old_new =
      CreateWebAppProtoForTesting("App 3", GURL("https://app3.com/"));
  {
    proto::LaunchHandler* launch_handler = app_old_new.mutable_launch_handler();
    launch_handler->set_route_to(
        proto::LaunchHandler_DeprecatedRouteTo_NEW_CLIENT);
    // Default to unspecified if not explicitly set, mimicking old state.
    launch_handler->set_client_mode(
        proto::LaunchHandler::CLIENT_MODE_UNSPECIFIED);
  }

  // App 4: Old route_to = EXISTING_CLIENT, navigate = ALWAYS
  proto::WebApp app_old_existing_navigate =
      CreateWebAppProtoForTesting("App 4", GURL("https://app4.com/"));
  {
    proto::LaunchHandler* launch_handler =
        app_old_existing_navigate.mutable_launch_handler();
    launch_handler->set_route_to(
        proto::LaunchHandler_DeprecatedRouteTo_EXISTING_CLIENT);
    launch_handler->set_navigate_existing_client(
        proto::LaunchHandler_DeprecatedNavigateExistingClient_ALWAYS);
    // Default to unspecified if not explicitly set, mimicking old state.
    launch_handler->set_client_mode(
        proto::LaunchHandler::CLIENT_MODE_UNSPECIFIED);
  }

  // App 5: Old route_to = EXISTING_CLIENT, navigate = NEVER (Focus)
  proto::WebApp app_old_existing_focus =
      CreateWebAppProtoForTesting("App 5", GURL("https://app5.com/"));
  {
    proto::LaunchHandler* launch_handler =
        app_old_existing_focus.mutable_launch_handler();
    launch_handler->set_route_to(
        proto::LaunchHandler_DeprecatedRouteTo_EXISTING_CLIENT);
    launch_handler->set_navigate_existing_client(
        proto::LaunchHandler_DeprecatedNavigateExistingClient_NEVER);
    // Default to unspecified if not explicitly set, mimicking old state.
    launch_handler->set_client_mode(
        proto::LaunchHandler::CLIENT_MODE_UNSPECIFIED);
  }

  // App 6: client_mode_valid_and_specified = false (should be impossible state,
  // and migrate).
  proto::WebApp app_invalid_specified =
      CreateWebAppProtoForTesting("App 6", GURL("https://app6.com/"));
  {
    proto::LaunchHandler* launch_handler =
        app_invalid_specified.mutable_launch_handler();
    launch_handler->set_client_mode(
        proto::LaunchHandler::CLIENT_MODE_NAVIGATE_NEW);
    launch_handler->set_client_mode_valid_and_specified(false);
  }

  // App 7: client_mode = UNSPECIFIED, but old fields present (should migrate)
  proto::WebApp app_unspecified_with_old =
      CreateWebAppProtoForTesting("App 7", GURL("https://app7.com/"));
  {
    proto::LaunchHandler* launch_handler =
        app_unspecified_with_old.mutable_launch_handler();
    launch_handler->set_route_to(
        proto::LaunchHandler_DeprecatedRouteTo_NEW_CLIENT);
    launch_handler->set_client_mode(
        proto::LaunchHandler::CLIENT_MODE_UNSPECIFIED);
  }

  // App 8: Modern client_mode, but with the deprecated
  // client_mode_valid_and_specified field set, so migration occurs.
  proto::WebApp app_modern_with_deprecated_field =
      CreateWebAppProtoForTesting("App 8", GURL("https://app8.com/"));
  {
    proto::LaunchHandler* launch_handler =
        app_modern_with_deprecated_field.mutable_launch_handler();
    launch_handler->set_client_mode(
        proto::LaunchHandler::CLIENT_MODE_NAVIGATE_NEW);
    launch_handler->set_client_mode_valid_and_specified(true);
  }

  database_factory->WriteProtos(
      {app_no_handler, app_modern, app_old_new, app_old_existing_navigate,
       app_old_existing_focus, app_invalid_specified, app_unspecified_with_old,
       app_modern_with_deprecated_field});

  // Set version to 2 to trigger the migration to version 3.
  proto::DatabaseMetadata metadata;
  metadata.set_version(2);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  // Apps 3, 4, 5, 6, 7, 8 should have been migrated.
  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.DeprecatedLaunchHandlerToClientMode"),
              base::BucketsAre(base::Bucket(/*min=*/6, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration results
  const WebApp* migrated_app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_no_handler));
  const WebApp* migrated_app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_modern));
  const WebApp* migrated_app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_old_new));
  const WebApp* migrated_app4 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_old_existing_navigate));
  const WebApp* migrated_app5 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_old_existing_focus));
  const WebApp* migrated_app6 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_invalid_specified));
  const WebApp* migrated_app7 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_unspecified_with_old));
  const WebApp* migrated_app8 = registrar.GetAppById(
      GetAppIdFromWebAppProto(app_modern_with_deprecated_field));

  ASSERT_TRUE(migrated_app1);
  ASSERT_TRUE(migrated_app2);
  ASSERT_TRUE(migrated_app3);
  ASSERT_TRUE(migrated_app4);
  ASSERT_TRUE(migrated_app5);
  ASSERT_TRUE(migrated_app6);
  ASSERT_TRUE(migrated_app7);
  ASSERT_TRUE(migrated_app8);

  EXPECT_FALSE(
      migrated_app1->launch_handler().has_value());          // App 1: No change
  ASSERT_TRUE(migrated_app2->launch_handler().has_value());  // App 2: No change
  EXPECT_EQ(migrated_app2->launch_handler()->parsed_client_mode(),
            LaunchHandler::ClientMode::kNavigateNew);
  ASSERT_TRUE(migrated_app3->launch_handler().has_value());  // App 3: Migrated
  EXPECT_EQ(migrated_app3->launch_handler()->parsed_client_mode(),
            LaunchHandler::ClientMode::kNavigateNew);
  ASSERT_TRUE(migrated_app4->launch_handler().has_value());  // App 4: Migrated
  EXPECT_EQ(migrated_app4->launch_handler()->parsed_client_mode(),
            LaunchHandler::ClientMode::kNavigateExisting);
  ASSERT_TRUE(migrated_app5->launch_handler().has_value());  // App 5: Migrated
  EXPECT_EQ(migrated_app5->launch_handler()->parsed_client_mode(),
            LaunchHandler::ClientMode::kFocusExisting);
  ASSERT_TRUE(migrated_app6->launch_handler().has_value());  // App 6: Migrated
  EXPECT_EQ(
      migrated_app6->launch_handler()->parsed_client_mode(),
      LaunchHandler::ClientMode::kNavigateNew);  // Became unspecified -> auto
  ASSERT_TRUE(migrated_app7->launch_handler().has_value());  // App 7: Migrated
  EXPECT_EQ(migrated_app7->launch_handler()->parsed_client_mode(),
            LaunchHandler::ClientMode::kNavigateNew);
  ASSERT_TRUE(migrated_app8->launch_handler().has_value());  // App 8: Migrated
  EXPECT_EQ(migrated_app8->launch_handler()->parsed_client_mode(),
            LaunchHandler::ClientMode::kNavigateNew);

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseMigrationTest, MigrateScopeToRemoveRefAndQuery) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // App 1: Clean scope
  proto::WebApp app_clean =
      CreateWebAppProtoForTesting("App 1", GURL("https://app1.com/start"));
  app_clean.set_scope("https://app1.com/");

  // App 2: Scope with query
  proto::WebApp app_query =
      CreateWebAppProtoForTesting("App 2", GURL("https://app2.com/start"));
  app_query.set_scope("https://app2.com/?query=1");

  // App 3: Scope with ref
  proto::WebApp app_ref =
      CreateWebAppProtoForTesting("App 3", GURL("https://app3.com/start"));
  app_ref.set_scope("https://app3.com/#ref");

  // App 4: Scope with query and ref
  proto::WebApp app_both =
      CreateWebAppProtoForTesting("App 4", GURL("https://app4.com/start"));
  app_both.set_scope("https://app4.com/?query=1#ref");

  // App 5: Invalid scope (should be skipped)
  proto::WebApp app_invalid =
      CreateWebAppProtoForTesting("App 5", GURL("https://app5.com/start"));
  app_invalid.set_scope("invalid-url");

  // App 6: No scope field (should be skipped)
  proto::WebApp app_no_scope =
      CreateWebAppProtoForTesting("App 6", GURL("https://app6.com/start"));
  app_no_scope.clear_scope();

  database_factory->WriteProtos(
      {app_clean, app_query, app_ref, app_both, app_invalid, app_no_scope});

  // Set version to 2 to trigger the migration to version 3.
  proto::DatabaseMetadata metadata;
  metadata.set_version(2);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  // Apps 2, 3, 4 should have been migrated.
  EXPECT_THAT(
      histograms.GetAllSamples("WebApp.Migrations.ScopeRefQueryRemoved"),
      base::BucketsAre(base::Bucket(/*min=*/3, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration results
  const WebApp* migrated_app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_clean));
  const WebApp* migrated_app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_query));
  const WebApp* migrated_app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_ref));
  const WebApp* migrated_app4 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_both));
  const WebApp* migrated_app5 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_invalid));
  const WebApp* migrated_app6 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_no_scope));

  ASSERT_TRUE(migrated_app1);
  ASSERT_TRUE(migrated_app2);
  ASSERT_TRUE(migrated_app3);
  ASSERT_TRUE(migrated_app4);
  ASSERT_TRUE(migrated_app5);
  EXPECT_FALSE(migrated_app6);

  EXPECT_EQ(migrated_app1->scope(), GURL("https://app1.com/"));  // No change
  EXPECT_EQ(migrated_app2->scope(),
            GURL("https://app2.com/"));  // Query removed
  EXPECT_EQ(migrated_app3->scope(), GURL("https://app3.com/"));  // Ref removed
  EXPECT_EQ(migrated_app4->scope(),
            GURL("https://app4.com/"));  // Both removed

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseMigrationTest, MigrateToRelativeManifestIdNoFragment) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  GURL start_url1("https://app1.com/start");
  webapps::ManifestId manifest_id1 =
      GenerateManifestIdFromStartUrlOnly(start_url1);
  std::string correct_relative_path1 = RelativeManifestIdPath(manifest_id1);

  // App 1: Correct relative_manifest_id
  proto::WebApp app_correct = CreateWebAppProtoForTesting("App 1", start_url1);
  app_correct.mutable_sync_data()->set_relative_manifest_id(
      correct_relative_path1);

  // App 2: Missing relative_manifest_id
  GURL start_url2("https://app2.com/start?p=1");
  webapps::ManifestId manifest_id2 =
      GenerateManifestIdFromStartUrlOnly(start_url2);
  std::string correct_relative_path2 = RelativeManifestIdPath(manifest_id2);
  proto::WebApp app_missing = CreateWebAppProtoForTesting("App 2", start_url2);
  app_missing.mutable_sync_data()->clear_relative_manifest_id();
  webapps::AppId app_id2 = GenerateAppIdFromManifestId(manifest_id2);

  // App 3: relative_manifest_id with fragment
  GURL start_url3("https://app3.com/start#frag");
  webapps::ManifestId manifest_id3 =
      GenerateManifestIdFromStartUrlOnly(start_url3);
  std::string correct_relative_path3 = RelativeManifestIdPath(manifest_id3);
  proto::WebApp app_fragment = CreateWebAppProtoForTesting("App 3", start_url3);
  // Manually set incorrect path with fragment
  app_fragment.mutable_sync_data()->set_relative_manifest_id(
      correct_relative_path3 + "#fragment");

  database_factory->WriteProtos({app_correct, app_missing, app_fragment});

  // Set version to 2 to trigger the migration to version 3.
  proto::DatabaseMetadata metadata;
  metadata.set_version(2);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  // App 3 had fragment removed.
  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.RelativeManifestIdFragmentRemoved"),
              base::BucketsAre(base::Bucket(/*min=*/1, /*count=*/1)));
  // Apps 2 and 3 were populated or fixed.
  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.RelativeManifestIdPopulatedOrFixed"),
              base::BucketsAre(base::Bucket(/*min=*/2, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration results
  const WebApp* migrated_app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_correct));
  const WebApp* migrated_app2 = registrar.GetAppById(app_id2);
  const WebApp* migrated_app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_fragment));

  ASSERT_TRUE(migrated_app1);
  ASSERT_TRUE(migrated_app2) << app_id2;
  ASSERT_TRUE(migrated_app3);

  // Check the relative_manifest_id in the sync_proto
  EXPECT_TRUE(migrated_app1->sync_proto().has_relative_manifest_id());
  EXPECT_EQ(migrated_app1->sync_proto().relative_manifest_id(),
            correct_relative_path1);  // No change

  EXPECT_TRUE(migrated_app2->sync_proto().has_relative_manifest_id());
  EXPECT_EQ(migrated_app2->sync_proto().relative_manifest_id(),
            correct_relative_path2);  // Populated

  EXPECT_TRUE(migrated_app3->sync_proto().has_relative_manifest_id());
  EXPECT_EQ(migrated_app3->sync_proto().relative_manifest_id(),
            correct_relative_path3);  // Fragment removed

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseMigrationTest, MigratePendingUpdateInfoWasIgnored) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // App 1: Correct pending update info, everything set.
  GURL start_url1("https://app1.com/start");
  proto::WebApp app_correct_pending_info =
      CreateWebAppProtoForTesting("App 1", start_url1);
  app_correct_pending_info.mutable_pending_update_info()->set_name(
      "App 1 Update");
  app_correct_pending_info.mutable_pending_update_info()->set_was_ignored(true);

  // App 2: No pending info, everything else is set.
  GURL start_url2("https://app2.com/start");
  proto::WebApp app_no_pending_info =
      CreateWebAppProtoForTesting("App 2", start_url2);

  // App 3: Pending update info exists (correct otherwise), no `was_ignored`
  // state.
  GURL start_url3("https://app3.com/start");
  proto::WebApp app_incorrect_pending_info =
      CreateWebAppProtoForTesting("App 3", start_url3);
  app_incorrect_pending_info.mutable_pending_update_info()->set_name(
      "App 3 Update");

  database_factory->WriteProtos({app_correct_pending_info, app_no_pending_info,
                                 app_incorrect_pending_info});

  // Set version to 3 to trigger the migration to version 4.
  proto::DatabaseMetadata metadata;
  metadata.set_version(3);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  // App 3 was migrated.
  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.PendingInfoWasIgnoredMigrated"),
              base::BucketsAre(base::Bucket(/*min=*/1, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration results
  const WebApp* migrated_app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_correct_pending_info));
  const WebApp* migrated_app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_no_pending_info));
  const WebApp* migrated_app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_incorrect_pending_info));

  ASSERT_TRUE(migrated_app1);
  ASSERT_TRUE(migrated_app2);
  ASSERT_TRUE(migrated_app3);

  // Check that app1's `was_ignored` field wasn't updated.
  EXPECT_TRUE(migrated_app1->pending_update_info()->has_was_ignored());
  EXPECT_TRUE(migrated_app1->pending_update_info()->was_ignored());

  // Check that app2 did not have a pending update info set.
  EXPECT_FALSE(migrated_app2->pending_update_info().has_value());

  // Check that app3 now has the `was_ignored` field default set to false.
  EXPECT_TRUE(migrated_app3->pending_update_info()->has_was_ignored());
  EXPECT_FALSE(migrated_app3->pending_update_info()->was_ignored());

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

using WebAppDatabaseIncorrectPendingInfoMigrationTest =
    WebAppDatabaseMigrationTest;

TEST_F(WebAppDatabaseIncorrectPendingInfoMigrationTest, NoTrusted) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // App 1: Correct pending update info, all manifest and trusted icons set.
  GURL start_url1("https://app1.com/start");
  proto::WebApp app_correct_pending_info =
      CreateWebAppProtoForTesting("App 1", start_url1);
  app_correct_pending_info.mutable_pending_update_info()->set_name(
      "App 1 Update");
  app_correct_pending_info.mutable_pending_update_info()->set_was_ignored(
      false);

  // Add manifest icon.
  sync_pb::WebAppIconInfo* manifest_icon =
      app_correct_pending_info.mutable_pending_update_info()
          ->mutable_manifest_icons()
          ->Add();
  manifest_icon->set_url("https://icon1.jpg");
  manifest_icon->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add trusted icon.
  sync_pb::WebAppIconInfo* trusted_icon =
      app_correct_pending_info.mutable_pending_update_info()
          ->mutable_trusted_icons()
          ->Add();
  trusted_icon->set_url("https://icon2.jpg");
  trusted_icon->set_size_in_px(32);
  trusted_icon->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add sizes and purposes
  google::protobuf::RepeatedPtrField<proto::DownloadedIconSizeInfo>
      purpose_size_maps;
  proto::DownloadedIconSizeInfo* downloaded_icon_info_any =
      purpose_size_maps.Add();
  downloaded_icon_info_any->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);
  downloaded_icon_info_any->add_icon_sizes(32);
  *app_correct_pending_info.mutable_pending_update_info()
       ->mutable_downloaded_manifest_icons() = purpose_size_maps;
  *app_correct_pending_info.mutable_pending_update_info()
       ->mutable_downloaded_trusted_icons() = purpose_size_maps;

  // App 2: No pending info, everything else is set.
  GURL start_url2("https://app2.com/start");
  proto::WebApp app_no_pending_info =
      CreateWebAppProtoForTesting("App 2", start_url2);

  // App 3: Invalid pending info, only manifest icons, no trusted icons.
  GURL start_url3("https://app1.com/start");
  proto::WebApp invalid_app = CreateWebAppProtoForTesting("App 3", start_url3);
  invalid_app.mutable_pending_update_info()->set_name("App 3 Update");
  invalid_app.mutable_pending_update_info()->set_was_ignored(false);

  // Add manifest icon.
  sync_pb::WebAppIconInfo* manifest_icon_no =
      invalid_app.mutable_pending_update_info()
          ->mutable_manifest_icons()
          ->Add();
  manifest_icon_no->set_url("https://icon1.jpg");
  manifest_icon_no->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add sizes and purposes
  google::protobuf::RepeatedPtrField<proto::DownloadedIconSizeInfo>
      purpose_maps_2;
  proto::DownloadedIconSizeInfo* icon_info_downloaded = purpose_maps_2.Add();
  icon_info_downloaded->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);
  icon_info_downloaded->add_icon_sizes(32);
  *invalid_app.mutable_pending_update_info()
       ->mutable_downloaded_manifest_icons() = purpose_maps_2;
  *invalid_app.mutable_pending_update_info()
       ->mutable_downloaded_trusted_icons() = purpose_maps_2;

  database_factory->WriteProtos(
      {app_correct_pending_info, app_no_pending_info, invalid_app});

  // Set version to 4 to trigger the migration to version 5.
  proto::DatabaseMetadata metadata;
  metadata.set_version(4);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  // App 3 was migrated.
  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.PendingUpdateInfoIconDataCorrupted"),
              base::BucketsAre(base::Bucket(/*min=*/1, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration results
  const WebApp* migrated_app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_correct_pending_info));
  const WebApp* migrated_app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_no_pending_info));
  const WebApp* migrated_app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(invalid_app));

  ASSERT_TRUE(migrated_app1);
  ASSERT_TRUE(migrated_app2);
  ASSERT_TRUE(migrated_app3);

  // Migrated app should have other pending update fields set properly.
  ASSERT_TRUE(migrated_app3->pending_update_info().has_value());
  ASSERT_TRUE(migrated_app3->pending_update_info()->has_name());
  ASSERT_TRUE(migrated_app3->pending_update_info()->has_was_ignored());

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseIncorrectPendingInfoMigrationTest, NoManifest) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // App 1: Correct pending update info, all manifest and trusted icons set.
  GURL start_url1("https://app1.com/start");
  proto::WebApp app_correct_pending_info =
      CreateWebAppProtoForTesting("App 1", start_url1);
  app_correct_pending_info.mutable_pending_update_info()->set_name(
      "App 1 Update");
  app_correct_pending_info.mutable_pending_update_info()->set_was_ignored(
      false);

  // Add manifest icon.
  sync_pb::WebAppIconInfo* manifest_icon =
      app_correct_pending_info.mutable_pending_update_info()
          ->mutable_manifest_icons()
          ->Add();
  manifest_icon->set_url("https://icon1.jpg");
  manifest_icon->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add trusted icon.
  sync_pb::WebAppIconInfo* trusted_icon =
      app_correct_pending_info.mutable_pending_update_info()
          ->mutable_trusted_icons()
          ->Add();
  trusted_icon->set_url("https://icon2.jpg");
  trusted_icon->set_size_in_px(32);
  trusted_icon->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add sizes and purposes
  google::protobuf::RepeatedPtrField<proto::DownloadedIconSizeInfo>
      purpose_size_maps;
  proto::DownloadedIconSizeInfo* downloaded_icon_info_any =
      purpose_size_maps.Add();
  downloaded_icon_info_any->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);
  downloaded_icon_info_any->add_icon_sizes(32);
  *app_correct_pending_info.mutable_pending_update_info()
       ->mutable_downloaded_manifest_icons() = purpose_size_maps;
  *app_correct_pending_info.mutable_pending_update_info()
       ->mutable_downloaded_trusted_icons() = purpose_size_maps;

  // App 2: No pending info, everything else is set.
  GURL start_url2("https://app2.com/start");
  proto::WebApp app_no_pending_info =
      CreateWebAppProtoForTesting("App 2", start_url2);

  // App 3: Invalid pending info, only manifest icons, no trusted icons.
  GURL start_url3("https://app1.com/start");
  proto::WebApp invalid_app = CreateWebAppProtoForTesting("App 3", start_url3);
  invalid_app.mutable_pending_update_info()->set_name("App 3 Update");
  invalid_app.mutable_pending_update_info()->set_was_ignored(false);

  // Add trusted icon.
  sync_pb::WebAppIconInfo* trusted_icon_no =
      invalid_app.mutable_pending_update_info()->mutable_trusted_icons()->Add();
  trusted_icon_no->set_url("https://icon1.jpg");
  trusted_icon_no->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add sizes and purposes
  google::protobuf::RepeatedPtrField<proto::DownloadedIconSizeInfo>
      purpose_maps_2;
  proto::DownloadedIconSizeInfo* icon_info_downloaded = purpose_maps_2.Add();
  icon_info_downloaded->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);
  icon_info_downloaded->add_icon_sizes(32);
  *invalid_app.mutable_pending_update_info()
       ->mutable_downloaded_manifest_icons() = purpose_maps_2;
  *invalid_app.mutable_pending_update_info()
       ->mutable_downloaded_trusted_icons() = purpose_maps_2;

  database_factory->WriteProtos(
      {app_correct_pending_info, app_no_pending_info, invalid_app});

  // Set version to 4 to trigger the migration to version 5.
  proto::DatabaseMetadata metadata;
  metadata.set_version(4);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  // App 3 was migrated.
  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.PendingUpdateInfoIconDataCorrupted"),
              base::BucketsAre(base::Bucket(/*min=*/1, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration results
  const WebApp* migrated_app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_correct_pending_info));
  const WebApp* migrated_app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_no_pending_info));
  const WebApp* migrated_app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(invalid_app));

  ASSERT_TRUE(migrated_app1);
  ASSERT_TRUE(migrated_app2);
  ASSERT_TRUE(migrated_app3);

  // Migrated app should have other pending update fields set properly.
  ASSERT_TRUE(migrated_app3->pending_update_info().has_value());
  ASSERT_TRUE(migrated_app3->pending_update_info()->has_name());
  ASSERT_TRUE(migrated_app3->pending_update_info()->has_was_ignored());

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseIncorrectPendingInfoMigrationTest,
       NoManifestDownloadedSizes) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // App 1: Correct pending update info, all manifest and trusted icons set.
  GURL start_url1("https://app1.com/start");
  proto::WebApp app_correct_pending_info =
      CreateWebAppProtoForTesting("App 1", start_url1);
  app_correct_pending_info.mutable_pending_update_info()->set_name(
      "App 1 Update");
  app_correct_pending_info.mutable_pending_update_info()->set_was_ignored(
      false);

  // Add manifest icon.
  sync_pb::WebAppIconInfo* manifest_icon =
      app_correct_pending_info.mutable_pending_update_info()
          ->mutable_manifest_icons()
          ->Add();
  manifest_icon->set_url("https://icon1.jpg");
  manifest_icon->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add trusted icon.
  sync_pb::WebAppIconInfo* trusted_icon =
      app_correct_pending_info.mutable_pending_update_info()
          ->mutable_trusted_icons()
          ->Add();
  trusted_icon->set_url("https://icon2.jpg");
  trusted_icon->set_size_in_px(32);
  trusted_icon->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Do not add trusted icon sizes.
  google::protobuf::RepeatedPtrField<proto::DownloadedIconSizeInfo>
      purpose_size_maps;
  proto::DownloadedIconSizeInfo* downloaded_icon_info_any =
      purpose_size_maps.Add();
  downloaded_icon_info_any->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);
  downloaded_icon_info_any->add_icon_sizes(32);
  *app_correct_pending_info.mutable_pending_update_info()
       ->mutable_downloaded_manifest_icons() = purpose_size_maps;

  // App 2: No pending info, everything else is set.
  GURL start_url2("https://app2.com/start");
  proto::WebApp app_no_pending_info =
      CreateWebAppProtoForTesting("App 2", start_url2);

  // App 3: Invalid pending info, only trusted icons, no manifest icons.
  GURL start_url3("https://app1.com/start");
  proto::WebApp invalid_app = CreateWebAppProtoForTesting("App 3", start_url3);
  invalid_app.mutable_pending_update_info()->set_name("App 3 Update");
  invalid_app.mutable_pending_update_info()->set_was_ignored(false);

  // Add trusted icon.
  sync_pb::WebAppIconInfo* manifest_icon_no =
      invalid_app.mutable_pending_update_info()->mutable_trusted_icons()->Add();
  manifest_icon_no->set_url("https://icon1.jpg");
  manifest_icon_no->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add sizes and purposes
  google::protobuf::RepeatedPtrField<proto::DownloadedIconSizeInfo>
      purpose_maps_2;
  proto::DownloadedIconSizeInfo* icon_info_downloaded = purpose_maps_2.Add();
  icon_info_downloaded->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);
  icon_info_downloaded->add_icon_sizes(32);
  *invalid_app.mutable_pending_update_info()
       ->mutable_downloaded_manifest_icons() = purpose_maps_2;
  *invalid_app.mutable_pending_update_info()
       ->mutable_downloaded_trusted_icons() = purpose_maps_2;

  database_factory->WriteProtos(
      {app_correct_pending_info, app_no_pending_info, invalid_app});

  // Set version to 4 to trigger the migration to version 5.
  proto::DatabaseMetadata metadata;
  metadata.set_version(4);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  // App 3 was migrated.
  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.PendingUpdateInfoIconDataCorrupted"),
              base::BucketsAre(base::Bucket(/*min=*/1, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration results
  const WebApp* migrated_app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_correct_pending_info));
  const WebApp* migrated_app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_no_pending_info));
  const WebApp* migrated_app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(invalid_app));

  ASSERT_TRUE(migrated_app1);
  ASSERT_TRUE(migrated_app2);
  ASSERT_TRUE(migrated_app3);

  // Migrated app should have other pending update fields set properly.
  ASSERT_TRUE(migrated_app3->pending_update_info().has_value());
  ASSERT_TRUE(migrated_app3->pending_update_info()->has_name());
  ASSERT_TRUE(migrated_app3->pending_update_info()->has_was_ignored());

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseIncorrectPendingInfoMigrationTest,
       NoTrustedDownloadedSizes) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // App 1: Correct pending update info, all manifest and trusted icons set.
  GURL start_url1("https://app1.com/start");
  proto::WebApp app_correct_pending_info =
      CreateWebAppProtoForTesting("App 1", start_url1);
  app_correct_pending_info.mutable_pending_update_info()->set_name(
      "App 1 Update");
  app_correct_pending_info.mutable_pending_update_info()->set_was_ignored(
      false);

  // Add manifest icon.
  sync_pb::WebAppIconInfo* manifest_icon =
      app_correct_pending_info.mutable_pending_update_info()
          ->mutable_manifest_icons()
          ->Add();
  manifest_icon->set_url("https://icon1.jpg");
  manifest_icon->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add trusted icon.
  sync_pb::WebAppIconInfo* trusted_icon =
      app_correct_pending_info.mutable_pending_update_info()
          ->mutable_trusted_icons()
          ->Add();
  trusted_icon->set_url("https://icon2.jpg");
  trusted_icon->set_size_in_px(32);
  trusted_icon->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add sizes and purposes
  google::protobuf::RepeatedPtrField<proto::DownloadedIconSizeInfo>
      purpose_size_maps;
  proto::DownloadedIconSizeInfo* downloaded_icon_info_any =
      purpose_size_maps.Add();
  downloaded_icon_info_any->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);
  downloaded_icon_info_any->add_icon_sizes(32);
  *app_correct_pending_info.mutable_pending_update_info()
       ->mutable_downloaded_manifest_icons() = purpose_size_maps;

  // App 2: No pending info, everything else is set.
  GURL start_url2("https://app2.com/start");
  proto::WebApp app_no_pending_info =
      CreateWebAppProtoForTesting("App 2", start_url2);

  // App 3: Invalid pending info, only manifest icons, no trusted icons.
  GURL start_url3("https://app1.com/start");
  proto::WebApp invalid_app = CreateWebAppProtoForTesting("App 3", start_url3);
  invalid_app.mutable_pending_update_info()->set_name("App 3 Update");
  invalid_app.mutable_pending_update_info()->set_was_ignored(false);

  // Add trusted icon.
  sync_pb::WebAppIconInfo* manifest_icon_no =
      invalid_app.mutable_pending_update_info()->mutable_trusted_icons()->Add();
  manifest_icon_no->set_url("https://icon1.jpg");
  manifest_icon_no->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Do not add manifest icon sizes.
  google::protobuf::RepeatedPtrField<proto::DownloadedIconSizeInfo>
      purpose_maps_2;
  proto::DownloadedIconSizeInfo* icon_info_downloaded = purpose_maps_2.Add();
  icon_info_downloaded->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);
  icon_info_downloaded->add_icon_sizes(32);
  *invalid_app.mutable_pending_update_info()
       ->mutable_downloaded_manifest_icons() = purpose_maps_2;

  database_factory->WriteProtos(
      {app_correct_pending_info, app_no_pending_info, invalid_app});

  // Set version to 4 to trigger the migration to version 5.
  proto::DatabaseMetadata metadata;
  metadata.set_version(4);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  // App 3 was migrated.
  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.PendingUpdateInfoIconDataCorrupted"),
              base::BucketsAre(base::Bucket(/*min=*/1, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration results
  const WebApp* migrated_app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_correct_pending_info));
  const WebApp* migrated_app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_no_pending_info));
  const WebApp* migrated_app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(invalid_app));

  ASSERT_TRUE(migrated_app1);
  ASSERT_TRUE(migrated_app2);
  ASSERT_TRUE(migrated_app3);

  // Migrated app should have other pending update fields set properly.
  ASSERT_TRUE(migrated_app3->pending_update_info().has_value());
  ASSERT_TRUE(migrated_app3->pending_update_info()->has_name());
  ASSERT_TRUE(migrated_app3->pending_update_info()->has_was_ignored());

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseIncorrectPendingInfoMigrationTest,
       NoNamePendingUpdateCleared) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // App 1: Correct pending update info, all manifest and trusted icons set.
  GURL start_url1("https://app1.com/start");
  proto::WebApp app_correct_pending_info =
      CreateWebAppProtoForTesting("App 1", start_url1);
  app_correct_pending_info.mutable_pending_update_info()->set_name(
      "App 1 Update");
  app_correct_pending_info.mutable_pending_update_info()->set_was_ignored(
      false);

  // Add manifest icon.
  sync_pb::WebAppIconInfo* manifest_icon =
      app_correct_pending_info.mutable_pending_update_info()
          ->mutable_manifest_icons()
          ->Add();
  manifest_icon->set_url("https://icon1.jpg");
  manifest_icon->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add trusted icon.
  sync_pb::WebAppIconInfo* trusted_icon =
      app_correct_pending_info.mutable_pending_update_info()
          ->mutable_trusted_icons()
          ->Add();
  trusted_icon->set_url("https://icon2.jpg");
  trusted_icon->set_size_in_px(32);
  trusted_icon->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add sizes and purposes
  google::protobuf::RepeatedPtrField<proto::DownloadedIconSizeInfo>
      purpose_size_maps;
  proto::DownloadedIconSizeInfo* downloaded_icon_info_any =
      purpose_size_maps.Add();
  downloaded_icon_info_any->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);
  downloaded_icon_info_any->add_icon_sizes(32);
  *app_correct_pending_info.mutable_pending_update_info()
       ->mutable_downloaded_manifest_icons() = purpose_size_maps;

  // App 2: No pending info, everything else is set.
  GURL start_url2("https://app2.com/start");
  proto::WebApp app_no_pending_info =
      CreateWebAppProtoForTesting("App 2", start_url2);

  // App 3: Invalid pending info, only manifest icons, no trusted icons. No name
  // as well, so the whole pending info should be removed.
  GURL start_url3("https://app1.com/start");
  proto::WebApp invalid_app = CreateWebAppProtoForTesting("App 3", start_url3);
  invalid_app.mutable_pending_update_info()->set_was_ignored(false);

  // Only trusted icon.
  sync_pb::WebAppIconInfo* manifest_icon_no =
      invalid_app.mutable_pending_update_info()->mutable_trusted_icons()->Add();
  manifest_icon_no->set_url("https://icon1.jpg");
  manifest_icon_no->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  database_factory->WriteProtos(
      {app_correct_pending_info, app_no_pending_info, invalid_app});

  // Set version to 4 to trigger the migration to version 5.
  proto::DatabaseMetadata metadata;
  metadata.set_version(4);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  // App 3 was migrated.
  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.PendingUpdateInfoIconDataCorrupted"),
              base::BucketsAre(base::Bucket(/*min=*/1, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration results
  const WebApp* migrated_app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_correct_pending_info));
  const WebApp* migrated_app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_no_pending_info));
  const WebApp* migrated_app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(invalid_app));

  ASSERT_TRUE(migrated_app1);
  ASSERT_TRUE(migrated_app2);
  ASSERT_TRUE(migrated_app3);

  // Migrated app should not have the pending update info.
  ASSERT_FALSE(migrated_app3->pending_update_info().has_value());

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseIncorrectPendingInfoMigrationTest,
       IncorrectSizeNoPurpose) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // App 1: Correct pending update info, all manifest and trusted icons set.
  GURL start_url1("https://app1.com/start");
  proto::WebApp app_correct_pending_info =
      CreateWebAppProtoForTesting("App 1", start_url1);
  app_correct_pending_info.mutable_pending_update_info()->set_name(
      "App 1 Update");
  app_correct_pending_info.mutable_pending_update_info()->set_was_ignored(
      false);

  // Add manifest icon.
  sync_pb::WebAppIconInfo* manifest_icon =
      app_correct_pending_info.mutable_pending_update_info()
          ->mutable_manifest_icons()
          ->Add();
  manifest_icon->set_url("https://icon1.jpg");
  manifest_icon->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add trusted icon.
  sync_pb::WebAppIconInfo* trusted_icon =
      app_correct_pending_info.mutable_pending_update_info()
          ->mutable_trusted_icons()
          ->Add();
  trusted_icon->set_url("https://icon2.jpg");
  trusted_icon->set_size_in_px(32);
  trusted_icon->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add sizes and purposes
  google::protobuf::RepeatedPtrField<proto::DownloadedIconSizeInfo>
      purpose_size_maps;
  proto::DownloadedIconSizeInfo* downloaded_icon_info_any =
      purpose_size_maps.Add();
  downloaded_icon_info_any->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);
  downloaded_icon_info_any->add_icon_sizes(32);
  *app_correct_pending_info.mutable_pending_update_info()
       ->mutable_downloaded_manifest_icons() = purpose_size_maps;

  // App 2: No pending info, everything else is set.
  GURL start_url2("https://app2.com/start");
  proto::WebApp app_no_pending_info =
      CreateWebAppProtoForTesting("App 2", start_url2);

  // App 3: Invalid pending info, only manifest icons, no trusted icons.
  GURL start_url3("https://app1.com/start");
  proto::WebApp invalid_app = CreateWebAppProtoForTesting("App 3", start_url3);
  invalid_app.mutable_pending_update_info()->set_name("App 3 Update");
  invalid_app.mutable_pending_update_info()->set_was_ignored(false);

  // Add trusted icon.
  sync_pb::WebAppIconInfo* manifest_icon_no =
      invalid_app.mutable_pending_update_info()->mutable_trusted_icons()->Add();
  manifest_icon_no->set_url("https://icon1.jpg");
  manifest_icon_no->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add manifest icon sizes, but incorrect trusted icon sizes (no purpose).
  google::protobuf::RepeatedPtrField<proto::DownloadedIconSizeInfo>
      purpose_maps_2;
  proto::DownloadedIconSizeInfo* icon_info_downloaded = purpose_maps_2.Add();
  icon_info_downloaded->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);
  icon_info_downloaded->add_icon_sizes(32);
  *invalid_app.mutable_pending_update_info()
       ->mutable_downloaded_manifest_icons() = purpose_maps_2;

  google::protobuf::RepeatedPtrField<proto::DownloadedIconSizeInfo>
      purpose_maps_3;
  purpose_maps_3.Add()->add_icon_sizes(32);
  *invalid_app.mutable_pending_update_info()
       ->mutable_downloaded_trusted_icons() = purpose_maps_3;

  database_factory->WriteProtos(
      {app_correct_pending_info, app_no_pending_info, invalid_app});

  // Set version to 4 to trigger the migration to version 5.
  proto::DatabaseMetadata metadata;
  metadata.set_version(4);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  // App 3 was migrated.
  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.PendingUpdateInfoIconDataCorrupted"),
              base::BucketsAre(base::Bucket(/*min=*/1, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration results
  const WebApp* migrated_app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_correct_pending_info));
  const WebApp* migrated_app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_no_pending_info));
  const WebApp* migrated_app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(invalid_app));

  ASSERT_TRUE(migrated_app1);
  ASSERT_TRUE(migrated_app2);
  ASSERT_TRUE(migrated_app3);

  // Migrated app should have other pending update fields set properly.
  ASSERT_TRUE(migrated_app3->pending_update_info().has_value());
  ASSERT_TRUE(migrated_app3->pending_update_info()->has_name());
  ASSERT_TRUE(migrated_app3->pending_update_info()->has_was_ignored());

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseIncorrectPendingInfoMigrationTest,
       IncorrectMetadataNoPurpose) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // App 1: Correct pending update info, all manifest and trusted icons set.
  GURL start_url1("https://app1.com/start");
  proto::WebApp app_correct_pending_info =
      CreateWebAppProtoForTesting("App 1", start_url1);
  app_correct_pending_info.mutable_pending_update_info()->set_name(
      "App 1 Update");
  app_correct_pending_info.mutable_pending_update_info()->set_was_ignored(
      false);

  // Add manifest icon.
  sync_pb::WebAppIconInfo* manifest_icon =
      app_correct_pending_info.mutable_pending_update_info()
          ->mutable_manifest_icons()
          ->Add();
  manifest_icon->set_url("https://icon1.jpg");
  manifest_icon->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add trusted icon.
  sync_pb::WebAppIconInfo* trusted_icon =
      app_correct_pending_info.mutable_pending_update_info()
          ->mutable_trusted_icons()
          ->Add();
  trusted_icon->set_url("https://icon2.jpg");
  trusted_icon->set_size_in_px(32);
  trusted_icon->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add sizes and purposes
  google::protobuf::RepeatedPtrField<proto::DownloadedIconSizeInfo>
      purpose_size_maps;
  proto::DownloadedIconSizeInfo* downloaded_icon_info_any =
      purpose_size_maps.Add();
  downloaded_icon_info_any->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);
  downloaded_icon_info_any->add_icon_sizes(32);
  *app_correct_pending_info.mutable_pending_update_info()
       ->mutable_downloaded_manifest_icons() = purpose_size_maps;
  *app_correct_pending_info.mutable_pending_update_info()
       ->mutable_downloaded_trusted_icons() = purpose_size_maps;

  // App 2: No pending info, everything else is set.
  GURL start_url2("https://app2.com/start");
  proto::WebApp app_no_pending_info =
      CreateWebAppProtoForTesting("App 2", start_url2);

  // App 3: Invalid pending info, only manifest icons, no trusted icons.
  GURL start_url3("https://app1.com/start");
  proto::WebApp invalid_app = CreateWebAppProtoForTesting("App 3", start_url3);
  invalid_app.mutable_pending_update_info()->set_name("App 3 Update");
  invalid_app.mutable_pending_update_info()->set_was_ignored(false);

  // Add manifest icon.
  sync_pb::WebAppIconInfo* manifest_icon_no =
      invalid_app.mutable_pending_update_info()
          ->mutable_manifest_icons()
          ->Add();
  manifest_icon_no->set_url("https://icon1.jpg");
  manifest_icon_no->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);

  // Add trusted icon, but remove the purpose to signify an error.
  sync_pb::WebAppIconInfo* trusted_icon_no =
      invalid_app.mutable_pending_update_info()->mutable_trusted_icons()->Add();
  trusted_icon_no->set_url("https://icon1.jpg");

  // Add sizes and purposes
  google::protobuf::RepeatedPtrField<proto::DownloadedIconSizeInfo>
      purpose_maps_2;
  proto::DownloadedIconSizeInfo* icon_info_downloaded = purpose_maps_2.Add();
  icon_info_downloaded->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);
  icon_info_downloaded->add_icon_sizes(32);
  *invalid_app.mutable_pending_update_info()
       ->mutable_downloaded_manifest_icons() = purpose_maps_2;
  *invalid_app.mutable_pending_update_info()
       ->mutable_downloaded_trusted_icons() = purpose_maps_2;

  database_factory->WriteProtos(
      {app_correct_pending_info, app_no_pending_info, invalid_app});

  // Set version to 4 to trigger the migration to version 5.
  proto::DatabaseMetadata metadata;
  metadata.set_version(4);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  // Start the system, which should run the migration.
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  // App 3 was migrated.
  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.PendingUpdateInfoIconDataCorrupted"),
              base::BucketsAre(base::Bucket(/*min=*/1, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  // Verify migration results
  const WebApp* migrated_app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_correct_pending_info));
  const WebApp* migrated_app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_no_pending_info));
  const WebApp* migrated_app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(invalid_app));

  ASSERT_TRUE(migrated_app1);
  ASSERT_TRUE(migrated_app2);
  ASSERT_TRUE(migrated_app3);

  // Migrated app should have other pending update fields set properly.
  ASSERT_TRUE(migrated_app3->pending_update_info().has_value());
  ASSERT_TRUE(migrated_app3->pending_update_info()->has_name());
  ASSERT_TRUE(migrated_app3->pending_update_info()->has_was_ignored());

  // Check that the data was also updated in the database.
  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseMigrationTest,
       MigrateDisplayModeOverrideToDisplayOverrides) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // App 1: Only old field (should be migrated).
  proto::WebApp app_old =
      CreateWebAppProtoForTesting("App 1", GURL("https://app1.com/"));
  app_old.add_display_mode_override_deprecated(
      proto::WebApp::DISPLAY_MODE_MINIMAL_UI);
  app_old.add_display_mode_override_deprecated(
      proto::WebApp::DISPLAY_MODE_STANDALONE);

  // App 2: Has both new and old fields (should reset the old field).
  proto::WebApp app_new =
      CreateWebAppProtoForTesting("App 2", GURL("https://app2.com/"));
  auto* override_item = app_new.add_display_overrides();
  override_item->set_display_mode(proto::WebApp::DISPLAY_MODE_FULLSCREEN);
  app_new.add_display_mode_override_deprecated(
      proto::WebApp::DISPLAY_MODE_BROWSER);

  // App 3: No overrides (should remain unchanged).
  proto::WebApp app_none =
      CreateWebAppProtoForTesting("App 3", GURL("https://app3.com/"));

  database_factory->WriteProtos({app_old, app_new, app_none});

  // Trigger migration to version 6.
  proto::DatabaseMetadata metadata;
  metadata.set_version(5);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  EXPECT_THAT(histograms.GetAllSamples(
                  "WebApp.Migrations.DisplayModeOverrideToDisplayOverrides"),
              base::BucketsAre(base::Bucket(/*min=*/2, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  const WebApp* migrated_app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_old));
  const WebApp* migrated_app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_new));
  const WebApp* migrated_app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_none));

  ASSERT_TRUE(migrated_app1);
  ASSERT_TRUE(migrated_app2);
  ASSERT_TRUE(migrated_app3);

  // App 1: Migrated.
  EXPECT_THAT(
      migrated_app1->display_mode_override(),
      testing::ElementsAre(
          DisplayOverride::Create(blink::mojom::DisplayMode::kMinimalUi),
          DisplayOverride::Create(blink::mojom::DisplayMode::kStandalone)));

  // App 2: Unchanged (uses new field).
  EXPECT_THAT(migrated_app2->display_mode_override(),
              testing::ElementsAre(DisplayOverride::Create(
                  blink::mojom::DisplayMode::kFullscreen)));

  // App 3: Unchanged.
  EXPECT_THAT(migrated_app3->display_mode_override(), testing::IsEmpty());

  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

TEST_F(WebAppDatabaseMigrationTest,
       MigrateScopeToStartUrlGetWithoutFilenameIfInvalid) {
  FakeWebAppDatabaseFactory* database_factory =
      fake_provider().GetDatabaseFactory().AsFakeWebAppDatabaseFactory();
  ASSERT_TRUE(database_factory);

  // App 1: Correct scope.
  proto::WebApp app_correct_scope = CreateWebAppProtoForTesting(
      "App 1", GURL("https://example.com/app/start.html"));
  app_correct_scope.set_scope("https://example.com/app/");

  // App 2: Incorrect scope, start_url not in scope.
  proto::WebApp app_incorrect_scope = CreateWebAppProtoForTesting(
      "App 2", GURL("https://anotherexample.com/app/start.html"));
  app_incorrect_scope.set_scope("https://anotherexample.com/different/scope/");

  // App 3: Correct scope (scope is '/').
  proto::WebApp app_correct_scope_root = CreateWebAppProtoForTesting(
      "App 3", GURL("https://example.com/app/start.html"));
  app_correct_scope_root.set_scope("https://example.com/");

  // App 4: No scope (will be caught by deserialization, but migration should
  // not crash).
  proto::WebApp app_no_scope = CreateWebAppProtoForTesting(
      "App 4", GURL("https://example.com/app4/start.html"));
  app_no_scope.clear_scope();

  // App 5: Invalid scope (scope will be set to start_url minus the filename
  // during deserialization).
  proto::WebApp app_invalid_scope = CreateWebAppProtoForTesting(
      "App 5", GURL("https://example.com/app5/start.html"));
  app_invalid_scope.set_scope("invalid");

  database_factory->WriteProtos({app_correct_scope, app_incorrect_scope,
                                 app_correct_scope_root, app_no_scope,
                                 app_invalid_scope});

  proto::DatabaseMetadata metadata;
  metadata.set_version(6);
  database_factory->WriteMetadata(metadata);

  base::HistogramTester histograms;
  test::AwaitStartWebAppProviderAndSubsystems(profile());

  EXPECT_THAT(
      histograms.GetAllSamples("WebApp.Migrations.ScopeMismatchedWithStartUrl"),
      base::BucketsAre(base::Bucket(/*min=*/2, /*count=*/1)));

  WebAppRegistrar& registrar = fake_provider().registrar_unsafe();

  const WebApp* app1 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_correct_scope));
  ASSERT_TRUE(app1);
  EXPECT_EQ(app1->scope(), GURL("https://example.com/"));

  const WebApp* app2 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_incorrect_scope));
  ASSERT_TRUE(app2);
  EXPECT_EQ(app2->scope(), GURL("https://anotherexample.com/app/"));

  const WebApp* app3 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_correct_scope_root));
  ASSERT_TRUE(app3);
  EXPECT_EQ(app3->scope(), GURL("https://example.com/"));

  // App 4 should not be in the registrar as it will fail parsing.
  EXPECT_FALSE(registrar.GetAppById(GetAppIdFromWebAppProto(app_no_scope)));

  const WebApp* app5 =
      registrar.GetAppById(GetAppIdFromWebAppProto(app_invalid_scope));
  ASSERT_TRUE(app5);
  EXPECT_EQ(app5->scope(), GURL("https://example.com/app5/"));

  VerifyDatabaseRegistryEqualToRegistrar(&fake_provider());
}

}  // namespace
}  // namespace web_app
