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

#include "components/browser_sync/sync_to_signin_migration.h"

#include <memory>
#include <string>
#include <tuple>

#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/strcat.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "components/bookmarks/common/bookmark_constants.h"
#include "components/bookmarks/common/bookmark_features.h"
#include "components/browser_sync/browser_sync_switches.h"
#include "components/prefs/testing_pref_service.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/data_type_histogram.h"
#include "components/sync/base/features.h"
#include "components/sync/base/pref_names.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/service/sync_feature_status_for_migrations_recorder.h"
#include "components/sync/service/sync_prefs.h"
#include "components/sync/test/test_sync_service.h"
#include "extensions/buildflags/buildflags.h"
#include "google_apis/gaia/gaia_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace browser_sync {
namespace {

#if !BUILDFLAG(IS_IOS)

// Parameter controlling whether to use the synchronous or asynchronous
// version of MaybeMigrateSyncingUserToSignedIn(...) function.
enum BlockingState {
  kAllowed,
  kDisallowed,
};

// Returns whether blocking is allowed.
template <typename... Args>
bool BlockingAllowed(const std::tuple<Args...>& param) {
  return std::get<BlockingState>(param) == BlockingState::kAllowed;
}

// Helper to generate a test name with sync and async variants.
template <typename... Args, typename Lambda>
std::string GenerateTestName(const std::tuple<Args...>& param, Lambda lambda) {
  return base::StrCat({
      lambda(param),
      BlockingAllowed(param) ? "Sync" : "Async",
  });
}

// Wrapper around MaybeMigrateSyncingUserToSignedInWrapper(IsBlockingAllowed(),
// ...) which allow to test either the synchronous or asynchronous version of
// the function (checking that the asynchronous version does not block).
void MaybeMigrateSyncingUserToSignedInWrapper(
    bool is_blocking_allowed,
    const base::FilePath& profile_path,
    PrefService* pref_service) {
  if (is_blocking_allowed) {
    MaybeMigrateSyncingUserToSignedIn(profile_path, pref_service);
    return;
  }

  base::RunLoop run_loop;
  {
    // Need to be in a nested block, since we want to block to wait for
    // the callback to be called, but we do not want the function under
    // test to block.
    base::ScopedDisallowBlocking disallow_blocking;
    MaybeMigrateSyncingUserToSignedInAsync(profile_path, pref_service,
                                           run_loop.QuitClosure());
  }
  run_loop.Run();
}

class SyncToSigninMigrationTestBase {
 public:
  SyncToSigninMigrationTestBase(bool migration_feature_enabled,
                                bool force_migration_feature_enabled) {
    features_.InitWithFeatureStates(
        {{syncer::kReplaceSyncPromosWithSignInPromos, true},
         {switches::kMigrateSyncingUserToSignedIn, migration_feature_enabled},
         {switches::kForceMigrateSyncingUserToSignedIn,
          force_migration_feature_enabled}});

    signin::IdentityManager::RegisterProfilePrefs(pref_service_.registry());
    syncer::SyncPrefs::RegisterProfilePrefs(pref_service_.registry());

    sync_prefs_ = std::make_unique<syncer::SyncPrefs>(&pref_service_);

    CHECK(fake_profile_dir_.CreateUniqueTempDir());

    // Sync should be initially active.
    sync_service_.SetSignedIn(signin::ConsentLevel::kSync);
  }
  virtual ~SyncToSigninMigrationTestBase() = default;

  void RecordStateToPrefs(bool include_status_recorder = true) {
    // Populate signin prefs based on the state of the TestSyncService.
    pref_service_.SetString(prefs::kGoogleServicesAccountId,
                            sync_service_.GetAccountInfo().gaia.ToString());
    pref_service_.SetBoolean(prefs::kGoogleServicesConsentedToSync,
                             sync_service_.HasSyncConsent());
    pref_service_.SetString(prefs::kGoogleServicesLastSyncingGaiaId,
                            sync_service_.GetAccountInfo().gaia.ToString());
    pref_service_.SetString(prefs::kGoogleServicesLastSyncingUsername,
                            sync_service_.GetAccountInfo().email);

    // Populate sync prefs. The TestSyncService doesn't write these, so they
    // have to be set manually here.
    syncer::SyncUserSettings* settings = sync_service_.GetUserSettings();
    sync_prefs_->SetSelectedTypesForSyncingUser(
        settings->IsSyncEverythingEnabled(),
        settings->GetRegisteredSelectableTypes(), settings->GetSelectedTypes());
#if !BUILDFLAG(IS_CHROMEOS)
    sync_prefs_->SetInitialSyncFeatureSetupComplete();
#endif  // !BUILDFLAG(IS_CHROMEOS)

    if (include_status_recorder) {
      // Populate migration-specific Sync status prefs.
      syncer::SyncFeatureStatusForMigrationsRecorder recorder(&pref_service_,
                                                              &sync_service_);
      // Before destroying the recorder again, tell it that sync is shutting
      // down to avoid a dangling observer.
      recorder.OnSyncShutdown(&sync_service_);
    }
  }

  void FastForwardBy(base::TimeDelta delta) {
    task_environment_.FastForwardBy(delta);
  }

 private:
  base::test::ScopedFeatureList features_;
  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};

 protected:
  TestingPrefServiceSimple pref_service_;
  std::unique_ptr<syncer::SyncPrefs> sync_prefs_;
  syncer::TestSyncService sync_service_;
  base::ScopedTempDir fake_profile_dir_;
};

// Fixture for tests covering the migration logic. The test param determines
// whether the force-migration feature flag is enabled or not (the regular
// migration is always enabled in this test) via the first parameter. The
// second parameter controls whether the synchronous or asynchronous version
// of MaybeMigrateSyncingUserToSignedIn(...) is tested.
class SyncToSigninMigrationTest
    : public SyncToSigninMigrationTestBase,
      public testing::TestWithParam<std::tuple<bool, BlockingState>> {
 public:
  SyncToSigninMigrationTest()
      : SyncToSigninMigrationTestBase(
            /*migration_feature_enabled=*/true,
            /*force_migration_feature_enabled=*/IsForceMigrationEnabled()) {}

  bool IsForceMigrationEnabled() const { return std::get<bool>(GetParam()); }
  bool IsBlockingAllowed() const { return BlockingAllowed(GetParam()); }
};

TEST_P(SyncToSigninMigrationTest, SyncActive) {
  // Sync is active.
  ASSERT_EQ(sync_service_.GetTransportState(),
            syncer::SyncService::TransportState::ACTIVE);
  ASSERT_TRUE(sync_service_.HasSyncConsent());

  const GaiaId gaia_id = sync_service_.GetAccountInfo().gaia;
  const std::string email = sync_service_.GetAccountInfo().email;

  // Save the above state to prefs.
  RecordStateToPrefs();
  ASSERT_TRUE(sync_prefs_->IsInitialSyncFeatureSetupComplete());

  // Before the migration, there are no per-account selected types.
  ASSERT_TRUE(
      pref_service_.GetDict(syncer::prefs::internal::kSelectedTypesPerAccount)
          .empty());

  // Run the migration. This should change the user to be non-syncing.
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // Note that TestSyncService doesn't consume the prefs, so verify the prefs
  // directly here.
  // The user should still be signed in.
  EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
            gaia_id.ToString());
  // But not syncing anymore.
  EXPECT_FALSE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
#if !BUILDFLAG(IS_CHROMEOS)
  EXPECT_FALSE(sync_prefs_->IsInitialSyncFeatureSetupComplete());
#endif
  // The fact that the user was migrated should be recorded in prefs.
  EXPECT_EQ(pref_service_.GetString(
                prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn),
            gaia_id.ToString());
  EXPECT_EQ(pref_service_.GetString(
                prefs::kGoogleServicesSyncingUsernameMigratedToSignedIn),
            email);
  EXPECT_EQ(
      pref_service_.GetInteger(prefs::kGoogleServicesSyncingUserMigrationType),
      /*kMigrated*/ 1);

  // There should be per-account selected types now. The details of this are
  // covered in SyncPrefs unit tests.
  EXPECT_FALSE(
      pref_service_.GetDict(syncer::prefs::internal::kSelectedTypesPerAccount)
          .empty());
}

TEST_P(SyncToSigninMigrationTest, SyncStatusPrefsUnset) {
  // Everything is active.
  ASSERT_EQ(sync_service_.GetTransportState(),
            syncer::SyncService::TransportState::ACTIVE);
  ASSERT_TRUE(sync_service_.HasSyncConsent());

  // Save the Sync configuration (enabled data types etc) to prefs, but not the
  // migration-specific status prefs. This simulates the case of an old client
  // which has never written those prefs.
  RecordStateToPrefs(/*include_status_recorder=*/false);

  // Take a copy of all current pref values, to verify whether the migration
  // modified any of them.
  const base::DictValue all_prefs =
      pref_service_.user_prefs_store()->GetValues();

  // Trigger the migration - it should only run in this state if the
  // force-migration is enabled.
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // Note that TestSyncService doesn't consume the prefs, so verify the prefs
  // directly here.
  if (IsForceMigrationEnabled()) {
    // There should be per-account selected types now. The details of this are
    // covered in SyncPrefs unit tests.
    EXPECT_FALSE(
        pref_service_.GetDict(syncer::prefs::internal::kSelectedTypesPerAccount)
            .empty());
  } else {
    // Since the migration didn't actually run, the prefs should be unmodified.
    EXPECT_EQ(pref_service_.user_prefs_store()->GetValues(), all_prefs);
  }
}

TEST_P(SyncToSigninMigrationTest, SyncTransport) {
  // There's no Sync consent, but otherwise everything is active (running in
  // transport mode).
  sync_service_.SetSignedIn(signin::ConsentLevel::kSignin);
  ASSERT_EQ(sync_service_.GetTransportState(),
            syncer::SyncService::TransportState::ACTIVE);

  // Save the above state to prefs.
  RecordStateToPrefs();

  // Take a copy of all current pref values, to verify that the migration
  // doesn't modify any of them.
  const base::DictValue all_prefs =
      pref_service_.user_prefs_store()->GetValues();

  // Trigger the migration - it should NOT actually run in this state.
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // Note that TestSyncService doesn't consume the prefs, so verify the prefs
  // directly here.
  // Since the migration didn't actually run, the prefs should be unmodified.
  EXPECT_EQ(pref_service_.user_prefs_store()->GetValues(), all_prefs);
}

TEST_P(SyncToSigninMigrationTest, SyncDisabledByPolicy) {
  // The user is signed in and opted in to Sync, but Sync is disabled via
  // enterprise policy.
  sync_service_.SetAllowedByEnterprisePolicy(false);
  ASSERT_EQ(sync_service_.GetTransportState(),
            syncer::SyncService::TransportState::DISABLED);
  ASSERT_TRUE(sync_service_.HasSyncConsent());

  const GaiaId gaia_id = sync_service_.GetAccountInfo().gaia;
  const std::string email = sync_service_.GetAccountInfo().email;

  // Save the above state to prefs.
  RecordStateToPrefs();

  // Before the migration, there are no per-account selected types.
  ASSERT_TRUE(
      pref_service_.GetDict(syncer::prefs::internal::kSelectedTypesPerAccount)
          .empty());

  // Run the migration. This should change the user to be non-syncing (even
  // though Sync wasn't actually active).
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // Note that TestSyncService doesn't consume the prefs, so verify the prefs
  // directly here.
  // The user should still be signed in.
  EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
            gaia_id.ToString());
  // But not syncing anymore.
  EXPECT_FALSE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
  // The fact that the user was migrated should be recorded in prefs.
  EXPECT_EQ(pref_service_.GetString(
                prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn),
            gaia_id.ToString());
  EXPECT_EQ(pref_service_.GetString(
                prefs::kGoogleServicesSyncingUsernameMigratedToSignedIn),
            email);
  EXPECT_EQ(
      pref_service_.GetInteger(prefs::kGoogleServicesSyncingUserMigrationType),
      /*kMigrated*/ 1);

  // There should be per-account selected types now. The details of this are
  // covered in SyncPrefs unit tests.
  EXPECT_FALSE(
      pref_service_.GetDict(syncer::prefs::internal::kSelectedTypesPerAccount)
          .empty());
}

TEST_P(SyncToSigninMigrationTest, SyncPaused_MinDelayNotPassed) {
  // Sync-the-feature is enabled, but in the "paused" state due to a persistent
  // auth error.
  sync_service_.SetPersistentAuthError();
  RecordStateToPrefs();
  ASSERT_EQ(sync_service_.GetTransportState(),
            syncer::SyncService::TransportState::PAUSED);
  ASSERT_TRUE(sync_service_.HasSyncConsent());
  ASSERT_TRUE(sync_service_.GetActiveDataTypes().empty());
  ASSERT_TRUE(
      pref_service_.GetDict(syncer::prefs::internal::kSelectedTypesPerAccount)
          .empty());
  const GaiaId gaia_id = sync_service_.GetAccountInfo().gaia;
  const std::string email = sync_service_.GetAccountInfo().email;

  // Attempt to migrate.
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // Note that TestSyncService doesn't consume the prefs, so verify the prefs
  // directly here.
  if (IsForceMigrationEnabled()) {
    // Enabling the forced migration flag causes the min delay requirement to be
    // ignored, immediately moving the user to the signed-in state.
    EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
              gaia_id.ToString());
    EXPECT_FALSE(
        pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
    EXPECT_EQ(pref_service_.GetString(
                  prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn),
              gaia_id.ToString());
    EXPECT_EQ(pref_service_.GetString(
                  prefs::kGoogleServicesSyncingUsernameMigratedToSignedIn),
              email);
    EXPECT_EQ(pref_service_.GetInteger(
                  prefs::kGoogleServicesSyncingUserMigrationType),
              2 /*kForceMigrated*/);
    EXPECT_FALSE(
        pref_service_.GetDict(syncer::prefs::internal::kSelectedTypesPerAccount)
            .empty());
  } else {
    // The migration should not run yet, giving the user some time to resolve
    // the error (switches::kMinDelayToMigrateSyncPaused).
    EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
              gaia_id.ToString());
    EXPECT_TRUE(
        pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
    EXPECT_EQ(pref_service_.GetString(
                  prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn),
              std::string());
    EXPECT_EQ(pref_service_.GetString(
                  prefs::kGoogleServicesSyncingUsernameMigratedToSignedIn),
              std::string());
    EXPECT_EQ(pref_service_.GetInteger(
                  prefs::kGoogleServicesSyncingUserMigrationType),
              0 /*kUnknown*/);
    EXPECT_TRUE(
        pref_service_.GetDict(syncer::prefs::internal::kSelectedTypesPerAccount)
            .empty());
  }
}

TEST_P(SyncToSigninMigrationTest, SyncPaused_MinDelayPassed) {
  if (IsForceMigrationEnabled()) {
    // When the forced migration flag is enabled, there is no waiting for the
    // error to be resolved. The migration runs on the first attempt and that's
    // covered in SyncPaused_MinDelayNotPassed.
    return;
  }

  // Sync-the-feature is enabled but transport is "paused" due to a persistent
  // auth error. Simulate a first migration attempt that does nothing (see
  // SyncPaused_MinDelayNotPassed test).
  sync_service_.SetPersistentAuthError();
  ASSERT_EQ(sync_service_.GetTransportState(),
            syncer::SyncService::TransportState::PAUSED);
  ASSERT_TRUE(sync_service_.HasSyncConsent());
  ASSERT_TRUE(sync_service_.GetActiveDataTypes().empty());
  ASSERT_TRUE(
      pref_service_.GetDict(syncer::prefs::internal::kSelectedTypesPerAccount)
          .empty());
  const GaiaId gaia_id = sync_service_.GetAccountInfo().gaia;
  const std::string email = sync_service_.GetAccountInfo().email;
  RecordStateToPrefs();
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);
  ASSERT_TRUE(sync_service_.HasSyncConsent());

  // Now, enough time has passed and the migration is attempted again.
  FastForwardBy(switches::kMinDelayToMigrateSyncPaused.Get());
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // Note that TestSyncService doesn't consume the prefs, so verify the prefs
  // directly here.
  // The user should still be signed in.
  EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
            gaia_id.ToString());
  // But not syncing anymore.
  EXPECT_FALSE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
  // The fact that the user was migrated should be recorded in prefs.
  EXPECT_EQ(pref_service_.GetString(
                prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn),
            gaia_id.ToString());
  EXPECT_EQ(pref_service_.GetString(
                prefs::kGoogleServicesSyncingUsernameMigratedToSignedIn),
            email);
  EXPECT_EQ(
      pref_service_.GetInteger(prefs::kGoogleServicesSyncingUserMigrationType),
      1 /*kMigrated*/);
  // There should be per-account selected types now. The details of this are
  // covered in SyncPrefs unit tests.
  EXPECT_FALSE(
      pref_service_.GetDict(syncer::prefs::internal::kSelectedTypesPerAccount)
          .empty());
}

TEST_P(SyncToSigninMigrationTest, SyncPaused_AuthErrorResolved) {
  if (IsForceMigrationEnabled()) {
    // When the forced migration flag is enabled, there is no waiting for the
    // error to be resolved. The migration runs on the first attempt and that's
    // covered in SyncPaused_MinDelayNotPassed.
    return;
  }

  // Sync-the-feature is enabled but transport is "paused" due to a persistent
  // auth error. Simulate a first migration attempt that does nothing (see
  // SyncPaused_MinDelayNotPassed test).
  sync_service_.SetPersistentAuthError();
  ASSERT_EQ(sync_service_.GetTransportState(),
            syncer::SyncService::TransportState::PAUSED);
  ASSERT_TRUE(sync_service_.HasSyncConsent());
  ASSERT_TRUE(sync_service_.GetActiveDataTypes().empty());
  ASSERT_TRUE(
      pref_service_.GetDict(syncer::prefs::internal::kSelectedTypesPerAccount)
          .empty());
  const GaiaId gaia_id = sync_service_.GetAccountInfo().gaia;
  const std::string email = sync_service_.GetAccountInfo().email;
  RecordStateToPrefs();
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // Attempt the migration again with the auth error resolved.
  sync_service_.ClearAuthError();
  RecordStateToPrefs();
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The migration should have run.
  EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
            gaia_id.ToString());
  EXPECT_FALSE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
  EXPECT_EQ(pref_service_.GetString(
                prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn),
            gaia_id.ToString());
  EXPECT_EQ(pref_service_.GetString(
                prefs::kGoogleServicesSyncingUsernameMigratedToSignedIn),
            email);
  EXPECT_EQ(
      pref_service_.GetInteger(prefs::kGoogleServicesSyncingUserMigrationType),
      1 /*kMigrated*/);
  EXPECT_FALSE(
      pref_service_.GetDict(syncer::prefs::internal::kSelectedTypesPerAccount)
          .empty());
}

TEST_P(SyncToSigninMigrationTest, SyncInitializing) {
  // The user is signed in and opted in to Sync, but Sync is still initializing.
  sync_service_.SetMaxTransportState(
      syncer::SyncService::TransportState::INITIALIZING);
  ASSERT_TRUE(sync_service_.HasSyncConsent());

  // Save the above state to prefs.
  RecordStateToPrefs();

  // Take a copy of all current pref values, to verify whether the migration
  // modified any of them.
  const base::DictValue all_prefs =
      pref_service_.user_prefs_store()->GetValues();

  // Trigger the migration - it should only run in this state if the
  // force-migration is enabled.
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // Note that TestSyncService doesn't consume the prefs, so verify the prefs
  // directly here.
  if (IsForceMigrationEnabled()) {
    EXPECT_EQ(pref_service_.GetInteger(
                  prefs::kGoogleServicesSyncingUserMigrationType),
              2 /*kForceMigrated*/);
    // There should be per-account selected types now. The details of this are
    // covered in SyncPrefs unit tests.
    EXPECT_FALSE(
        pref_service_.GetDict(syncer::prefs::internal::kSelectedTypesPerAccount)
            .empty());
  } else {
    // Since the migration didn't actually run, the prefs should be unmodified.
    EXPECT_EQ(pref_service_.user_prefs_store()->GetValues(), all_prefs);
  }
}

TEST_P(SyncToSigninMigrationTest, UndoFeaturePreventsMigration) {
  base::test::ScopedFeatureList undo_feature;
  undo_feature.InitAndEnableFeature(
      switches::kUndoMigrationOfSyncingUserToSignedIn);

  // Everything is active.
  ASSERT_EQ(sync_service_.GetTransportState(),
            syncer::SyncService::TransportState::ACTIVE);
  ASSERT_TRUE(sync_service_.HasSyncConsent());
  ASSERT_TRUE(sync_service_.GetActiveDataTypes().HasAll({
      syncer::BOOKMARKS,
      syncer::PASSWORDS,
      syncer::READING_LIST,
#if BUILDFLAG(ENABLE_EXTENSIONS)
      syncer::EXTENSIONS,
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
      syncer::THEMES,
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  }));

  // Save the above state to prefs.
  RecordStateToPrefs();

  // Take a copy of all current pref values, to verify that the migration
  // doesn't modify any of them.
  const base::DictValue all_prefs =
      pref_service_.user_prefs_store()->GetValues();

  base::HistogramTester histograms;

  // Trigger the migration.
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // Even though the user would be eligible, the "undo" feature should have
  // prevented the migration from happening. (And since there was nothing to
  // undo, it shouldn't have had any effect either.)
  EXPECT_EQ(pref_service_.user_prefs_store()->GetValues(), all_prefs);

  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision",
      /*SyncToSigninMigrationDecision::kUndoNotNecessary*/ 7, 1);
}

INSTANTIATE_TEST_SUITE_P(
    ,
    SyncToSigninMigrationTest,
    testing::Combine(testing::Bool(),
                     testing::Values(BlockingState::kAllowed,
                                     BlockingState::kDisallowed)),
    [](const auto& info) {
      return GenerateTestName(info.param, [](const auto& param) {
        return std::get<bool>(param) ? "ForceMigrationEnabled"
                                     : "ForceMigrationDisabled";
      });
    });

enum class FeatureState {
  kMigrationDisabled,
  kMigrationEnabled,
  kMigrationForced,
};

// Fixture for tests covering migration metrics. The first param determines
// whether the migration feature flag and possibly also the force-migration
// feature flag are enabled. The second parameter controls whether the
// synchronous or asynchronous version of MaybeMigrateSyncingUserToSignedIn(...)
// is tested.
class SyncToSigninMigrationMetricsTest
    : public SyncToSigninMigrationTestBase,
      public testing::TestWithParam<std::tuple<FeatureState, BlockingState>> {
 public:
  SyncToSigninMigrationMetricsTest()
      : SyncToSigninMigrationTestBase(
            /*migration_feature_enabled=*/IsMigrationEnabled(),
            /*force_migration_feature_enabled=*/IsForceMigrationEnabled()) {}

  FeatureState GetFeature() const { return std::get<FeatureState>(GetParam()); }
  bool IsMigrationEnabled() const {
    switch (GetFeature()) {
      case FeatureState::kMigrationDisabled:
        return false;
      case FeatureState::kMigrationEnabled:
      case FeatureState::kMigrationForced:
        return true;
    }
  }
  bool IsForceMigrationEnabled() const {
    switch (GetFeature()) {
      case FeatureState::kMigrationDisabled:
      case FeatureState::kMigrationEnabled:
        return false;
      case FeatureState::kMigrationForced:
        return true;
    }
  }
  bool IsBlockingAllowed() const { return BlockingAllowed(GetParam()); }

  std::string GetTypeDecisionHistogramInfix() const {
    return IsMigrationEnabled() ? "Migration" : "DryRun";
  }
};

TEST_P(SyncToSigninMigrationMetricsTest, SyncAndAllDataTypesActive) {
  // Everything is active.
  ASSERT_EQ(sync_service_.GetTransportState(),
            syncer::SyncService::TransportState::ACTIVE);
  ASSERT_TRUE(sync_service_.HasSyncConsent());
  ASSERT_TRUE(sync_service_.GetActiveDataTypes().HasAll({
      syncer::BOOKMARKS,
      syncer::PASSWORDS,
      syncer::READING_LIST,
#if BUILDFLAG(ENABLE_EXTENSIONS)
      syncer::EXTENSIONS,
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
      syncer::THEMES,
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  }));

  // Save the above state to prefs.
  RecordStateToPrefs();

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The overall migration should run, except if the feature flag is disabled.
  int expected_decision =
      IsMigrationEnabled()
          ? /*SyncToSigninMigrationDecision::kMigrate*/ 0
          : /*SyncToSigninMigrationDecision::kDontMigrateFlagDisabled*/ 5;
  histograms.ExpectUniqueSample("Sync.SyncToSigninMigrationDecision",
                                expected_decision, 1);
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigration.MigrationType",
      IsMigrationEnabled() ? /*kMigrated*/ 1 : /*kNotMigratedYet*/ 3, 1);

  if (IsMigrationEnabled()) {
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationOutcome", 1);
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime", 1);
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationExecutionMode",
        IsBlockingAllowed()
            ? /*SyncToSigninMigrationExecutionMode::kSynchronous*/ 0
            : /*SyncToSigninMigrationExecutionMode::kAsynchronous*/ 1,
        1);

  } else {
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationOutcome", 0);
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime", 0);
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationExecutionMode", 0);
  }
  // All the data type migrations should run - in "DryRun" mode if the feature
  // flag is disabled.
  std::string infix = GetTypeDecisionHistogramInfix();
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".BOOKMARK",
      /*SyncToSigninMigrationDataTypeDecision::kMigrate*/ 0, 1);
#if BUILDFLAG(IS_ANDROID)
  // PASSWORDS is migrated by other layers on Android.
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision." + infix + ".PASSWORD", 0);
#else
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".PASSWORD",
      /*SyncToSigninMigrationDataTypeDecision::kMigrate*/ 0, 1);
#endif
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".READING_LIST",
      /*SyncToSigninMigrationDataTypeDecision::kMigrate*/ 0, 1);
#if BUILDFLAG(ENABLE_EXTENSIONS)
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".EXTENSION",
      /*SyncToSigninMigrationDataTypeDecision::kMigrate*/ 0, 1);
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".THEME",
      /*SyncToSigninMigrationDataTypeDecision::kMigrate*/ 0, 1);
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
}

TEST_P(SyncToSigninMigrationMetricsTest, SyncActiveButNotDataTypes) {
  // Sync-the-feature is active.
  ASSERT_EQ(sync_service_.GetTransportState(),
            syncer::SyncService::TransportState::ACTIVE);
  ASSERT_TRUE(sync_service_.HasSyncConsent());

  // ReadingList is not selected.
  sync_service_.GetUserSettings()->SetSelectedTypes(
      /*sync_everything=*/false, {syncer::UserSelectableType::kBookmarks,
                                  syncer::UserSelectableType::kPasswords});
  // Passwords is selected, but failed to actually start up (e.g. disabled by
  // policy).
  sync_service_.SetFailedDataTypes({syncer::PASSWORDS});

  ASSERT_TRUE(sync_service_.GetActiveDataTypes().Has(syncer::BOOKMARKS));
  ASSERT_FALSE(sync_service_.GetActiveDataTypes().Has(syncer::PASSWORDS));
  ASSERT_FALSE(sync_service_.GetActiveDataTypes().Has(syncer::READING_LIST));

  // Save the above state to prefs.
  RecordStateToPrefs();

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The overall migration should run, except if the feature flag is disabled.
  int expected_decision =
      IsMigrationEnabled()
          ? /*SyncToSigninMigrationDecision::kMigrate*/ 0
          : /*SyncToSigninMigrationDecision::kDontMigrateFlagDisabled*/ 5;
  histograms.ExpectUniqueSample("Sync.SyncToSigninMigrationDecision",
                                expected_decision, 1);
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigration.MigrationType",
      IsMigrationEnabled() ? /*kMigrated*/ 1 : /*kNotMigratedYet*/ 3, 1);
  if (IsMigrationEnabled()) {
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationOutcome", 1);
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime", 1);
  } else {
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationOutcome", 0);
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime", 0);
  }

  std::string infix = GetTypeDecisionHistogramInfix();
  // Bookmarks was active, so its migration should run.
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".BOOKMARK",
      /*SyncToSigninMigrationDataTypeDecision::kMigrate*/ 0, 1);
#if BUILDFLAG(IS_ANDROID)
  // PASSWORDS is migrated by other layers on Android.
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision." + infix + ".PASSWORD", 0);
#else
  // Passwords was not active, even though it was enabled.
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".PASSWORD",
      /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
      1);
#endif  // BUILDFLAG(IS_ANDROID)
  // ReadingList was disabled by the user.
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".READING_LIST",
      /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeDisabled*/ 1, 1);
}

TEST_P(SyncToSigninMigrationMetricsTest, SyncStatusPrefsUnset) {
  // Everything is active.
  ASSERT_EQ(sync_service_.GetTransportState(),
            syncer::SyncService::TransportState::ACTIVE);
  ASSERT_TRUE(sync_service_.HasSyncConsent());
  ASSERT_TRUE(sync_service_.GetActiveDataTypes().HasAll({
      syncer::BOOKMARKS,
      syncer::PASSWORDS,
      syncer::READING_LIST,
#if BUILDFLAG(ENABLE_EXTENSIONS)
      syncer::EXTENSIONS,
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
      syncer::THEMES,
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  }));

  // Save the Sync configuration (enabled data types etc) to prefs, but not the
  // migration-specific status prefs. This simulates the case of an old client
  // which has never written those prefs.
  RecordStateToPrefs(/*include_status_recorder=*/false);

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // With the missing/undefined status, the overall migration should only run if
  // the force-migration flag was enabled.
  int expected_decision =
      IsForceMigrationEnabled()
          ? /*SyncToSigninMigrationDecision::kMigrateForced*/ 8
          : /*SyncToSigninMigrationDecision::kDontMigrateSyncStatusUndefined*/
          3;
  histograms.ExpectUniqueSample("Sync.SyncToSigninMigrationDecision",
                                expected_decision, 1);
  histograms.ExpectTotalCount("Sync.SyncToSigninMigrationOutcome",
                              IsForceMigrationEnabled() ? 1 : 0);
  histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime",
                              IsForceMigrationEnabled() ? 1 : 0);
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigration.MigrationType",
      IsForceMigrationEnabled() ? /*kForceMigrated*/ 2 : /*kNotMigratedYet*/ 3,
      1);

  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.BOOKMARK", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.PASSWORD", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.READING_LIST", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.EXTENSION", 0);
  histograms.ExpectTotalCount("Sync.SyncToSigninMigrationDecision.DryRun.THEME",
                              0);
  if (IsForceMigrationEnabled()) {
    // The individual data types were not active and so should not be migrated.
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision.Migration.BOOKMARK",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#if !BUILDFLAG(IS_ANDROID)
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision.Migration.PASSWORD",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#endif
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision.Migration.READING_LIST",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#if BUILDFLAG(ENABLE_EXTENSIONS)
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision.Migration.EXTENSION",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision.Migration.THEME",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  } else {
    // The overall migration didn't run.
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision.Migration.BOOKMARK", 0);
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision.Migration.PASSWORD", 0);
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision.Migration.READING_LIST", 0);
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision.Migration.EXTENSION", 0);
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision.Migration.THEME", 0);
  }
}

TEST_P(SyncToSigninMigrationMetricsTest, NotSignedIn) {
  // There's no signed-in user.
  sync_service_.SetSignedOut();
  ASSERT_TRUE(sync_service_.GetActiveDataTypes().empty());

  // Save the above state to prefs.
  RecordStateToPrefs();

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The migration should not run since there's no signed-in user.
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision",
      /*SyncToSigninMigrationDecision::kDontMigrateNotSignedIn*/ 1, 1);
  histograms.ExpectTotalCount("Sync.SyncToSigninMigrationOutcome", 0);
  histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime", 0);
  histograms.ExpectUniqueSample("Sync.SyncToSigninMigration.MigrationType",
                                /*kMigrationNotNeeded*/ 4, 1);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.BOOKMARK", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.PASSWORD", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.READING_LIST", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.EXTENSION", 0);
  histograms.ExpectTotalCount("Sync.SyncToSigninMigrationDecision.DryRun.THEME",
                              0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.Migration.BOOKMARK", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.Migration.PASSWORD", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.Migration.READING_LIST", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.Migration.EXTENSION", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.Migration.THEME", 0);
}

TEST_P(SyncToSigninMigrationMetricsTest, SyncTransport) {
  // There's no Sync consent, but otherwise everything is active (running in
  // transport mode).
  sync_service_.SetSignedIn(signin::ConsentLevel::kSignin);
  ASSERT_EQ(sync_service_.GetTransportState(),
            syncer::SyncService::TransportState::ACTIVE);
  ASSERT_TRUE(sync_service_.GetActiveDataTypes().HasAll({
      syncer::BOOKMARKS,
      syncer::PASSWORDS,
      syncer::READING_LIST,
#if BUILDFLAG(ENABLE_EXTENSIONS)
      syncer::EXTENSIONS,
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
      syncer::THEMES,
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  }));

  // Save the above state to prefs.
  RecordStateToPrefs();

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The migration should not run since this is not a Sync-the-feature user.
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision",
      /*SyncToSigninMigrationDecision::kDontMigrateNotSyncing*/ 2, 1);
  histograms.ExpectTotalCount("Sync.SyncToSigninMigrationOutcome", 0);
  histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime", 0);
  histograms.ExpectUniqueSample("Sync.SyncToSigninMigration.MigrationType",
                                /*kMigrationNotNeeded*/ 4, 1);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.BOOKMARK", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.PASSWORD", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.READING_LIST", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.EXTENSION", 0);
  histograms.ExpectTotalCount("Sync.SyncToSigninMigrationDecision.DryRun.THEME",
                              0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.Migration.BOOKMARK", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.Migration.PASSWORD", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.Migration.READING_LIST", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.Migration.EXTENSION", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.Migration.THEME", 0);
}

TEST_P(SyncToSigninMigrationMetricsTest, SyncPaused_MinDelayNotPassed) {
  sync_service_.SetPersistentAuthError();
  ASSERT_EQ(sync_service_.GetTransportState(),
            syncer::SyncService::TransportState::PAUSED);
  ASSERT_TRUE(sync_service_.HasSyncConsent());
  ASSERT_TRUE(sync_service_.GetActiveDataTypes().empty());
  RecordStateToPrefs();
  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  std::string infix = GetTypeDecisionHistogramInfix();
  if (IsForceMigrationEnabled()) {
    // Enabling the forced migration flag causes the min delay requirement to be
    // ignored, immediately moving the user to the signed-in state. Individual
    // data types were not active and so should not be migrated.
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision",
        /*SyncToSigninMigrationDecision::kMigrateForced*/ 8, 1);
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime", 1);
    histograms.ExpectUniqueSample("Sync.SyncToSigninMigration.MigrationType",
                                  /*kForceMigrated*/ 2, 1);
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision." + infix + ".BOOKMARK",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#if BUILDFLAG(IS_ANDROID)
    // PASSWORDS is migrated by other layers on Android.
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision." + infix + ".PASSWORD", 0);
#else
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision." + infix + ".PASSWORD",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#endif  // BUILDFLAG(IS_ANDROID)
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision." + infix + ".READING_LIST",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#if BUILDFLAG(ENABLE_EXTENSIONS)
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision." + infix + ".EXTENSION",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision." + infix + ".THEME",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  } else if (IsMigrationEnabled()) {
    // The migration should not run because not enough time passed since the
    // auth error was detected. There's still a chance the user will resolve it.
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision",
        /*SyncToSigninMigrationDecision::kDontMigrateAuthError*/ 9, 1);
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime", 0);
    histograms.ExpectUniqueSample("Sync.SyncToSigninMigration.MigrationType",
                                  /*kNotMigratedYet*/ 3, 1);
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision." + infix + ".BOOKMARK", 0);
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision." + infix + ".PASSWORD", 0);
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision." + infix + ".READING_LIST", 0);
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision." + infix + ".EXTENSION", 0);
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision." + infix + ".THEME", 0);
  } else {
    // The migration should not run because the flag is disabled. The per type
    // metrics are still recorded for historical reasons.
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision",
        /*SyncToSigninMigrationDecision::kDontMigrateFlagDisabled*/ 5, 1);
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime", 0);
    histograms.ExpectUniqueSample("Sync.SyncToSigninMigration.MigrationType",
                                  /*kNotMigratedYet*/ 3, 1);
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision." + infix + ".BOOKMARK",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#if BUILDFLAG(IS_ANDROID)
    // PASSWORDS is migrated by other layers on Android.
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision." + infix + ".PASSWORD", 0);
#else
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision." + infix + ".PASSWORD",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#endif  // BUILDFLAG(IS_ANDROID)
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision." + infix + ".READING_LIST",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#if BUILDFLAG(ENABLE_EXTENSIONS)
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision." + infix + ".EXTENSION",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision." + infix + ".THEME",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  }
}

TEST_P(SyncToSigninMigrationMetricsTest, SyncPaused_MinDelayPassed) {
  if (GetFeature() != FeatureState::kMigrationEnabled) {
    // For kMigrationForced, the duration of the auth error is irrelevant,
    // the migration succeeds on the first attempt and that's covered in
    // SyncPaused_MinDelayNotPassed.
    // For kMigrationDisabled, waiting won't change anything, the second attempt
    // would fail just like the first one, as in SyncPaused_MinDelayNotPassed.
    return;
  }

  // Simulate a first migration attempt while sync-the-feature is enabled but
  // transport is "paused" due to a persistent auth error. The first attempt
  // does nothing (see SyncPaused_MinDelayNotPassed test).
  sync_service_.SetPersistentAuthError();
  ASSERT_EQ(sync_service_.GetTransportState(),
            syncer::SyncService::TransportState::PAUSED);
  ASSERT_TRUE(sync_service_.HasSyncConsent());
  ASSERT_TRUE(sync_service_.GetActiveDataTypes().empty());
  RecordStateToPrefs();
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);
  base::HistogramTester histograms;

  // Now, enough time has passed and the migration is attempted again.
  FastForwardBy(switches::kMinDelayToMigrateSyncPaused.Get());
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The overall migration should run, except if the feature flag is disabled.
  int expected_decision =
      IsMigrationEnabled()
          ? /*SyncToSigninMigrationDecision::kMigrate*/ 0
          : /*SyncToSigninMigrationDecision::kDontMigrateFlagDisabled*/ 5;
  histograms.ExpectUniqueSample("Sync.SyncToSigninMigrationDecision",
                                expected_decision, 1);
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigration.MigrationType",
      IsMigrationEnabled() ? /*kMigrated*/ 1 : /*kNotMigratedYet*/ 3, 1);
  if (IsMigrationEnabled()) {
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationOutcome", 1);
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime", 1);
  } else {
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationOutcome", 0);
    histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime", 0);
  }

  // However, the individual data types were by definition not active and so
  // should not be migrated.
  std::string infix = GetTypeDecisionHistogramInfix();
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".BOOKMARK",
      /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
      1);
#if BUILDFLAG(IS_ANDROID)
  // PASSWORDS is migrated by other layers on Android.
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision." + infix + ".PASSWORD", 0);
#else
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".PASSWORD",
      /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
      1);
#endif  // BUILDFLAG(IS_ANDROID)
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".READING_LIST",
      /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
      1);
#if BUILDFLAG(ENABLE_EXTENSIONS)
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".EXTENSION",
      /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
      1);
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".THEME",
      /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
      1);
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
}

TEST_P(SyncToSigninMigrationMetricsTest, SyncPaused_AuthErrorResolved) {
  if (GetFeature() != FeatureState::kMigrationEnabled) {
    // For kMigrationForced, the duration of the auth error is irrelevant,
    // the migration succeeds on the first attempt and that's covered in
    // SyncPaused_MinDelayNotPassed.
    // For kMigrationDisabled, waiting won't change anything, the second attempt
    // would fail just like the first one, as in SyncPaused_MinDelayNotPassed.
    return;
  }

  // Sync-the-feature is enabled but transport is "paused" due to a persistent
  // auth error. Simulate a first migration attempt that does nothing (see
  // SyncPaused_MinDelayNotPassed test). After that, the error is resolved.
  sync_service_.SetPersistentAuthError();
  ASSERT_EQ(sync_service_.GetTransportState(),
            syncer::SyncService::TransportState::PAUSED);
  ASSERT_TRUE(sync_service_.HasSyncConsent());
  ASSERT_TRUE(sync_service_.GetActiveDataTypes().empty());
  RecordStateToPrefs();
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);
  sync_service_.ClearAuthError();
  RecordStateToPrefs();
  base::HistogramTester histograms;

  // Attempt the migration again with the auth error resolved.
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The migration should run.
  std::string infix = GetTypeDecisionHistogramInfix();
  histograms.ExpectUniqueSample("Sync.SyncToSigninMigrationDecision",
                                /*SyncToSigninMigrationDecision::kMigrate*/ 0,
                                1);
  histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime", 1);
  histograms.ExpectUniqueSample("Sync.SyncToSigninMigration.MigrationType",
                                /*kMigrated*/ 1, 1);
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".BOOKMARK",
      /*SyncToSigninMigrationDataTypeDecision::kMigrate*/ 0, 1);
#if BUILDFLAG(IS_ANDROID)
  // PASSWORDS is migrated by other layers on Android.
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision." + infix + ".PASSWORD", 0);
#else
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".PASSWORD",
      /*SyncToSigninMigrationDataTypeDecision::kMigrate*/ 0, 1);
#endif  // BUILDFLAG(IS_ANDROID)
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".READING_LIST",
      /*SyncToSigninMigrationDataTypeDecision::kMigrate*/ 0, 1);
#if BUILDFLAG(ENABLE_EXTENSIONS)
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".EXTENSION",
      /*SyncToSigninMigrationDataTypeDecision::kMigrate*/ 0, 1);
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision." + infix + ".THEME",
      /*SyncToSigninMigrationDataTypeDecision::kMigrate*/ 0, 1);
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
}

TEST_P(SyncToSigninMigrationMetricsTest, SyncInitializing) {
  sync_service_.SetMaxTransportState(
      syncer::SyncService::TransportState::INITIALIZING);
  ASSERT_TRUE(sync_service_.HasSyncConsent());
  ASSERT_TRUE(sync_service_.GetActiveDataTypes().empty());

  // Save the above state to prefs.
  RecordStateToPrefs();

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // If Sync was still initializing, the overall migration should only run if
  // the force-migration flag was enabled.
  int expected_decision =
      IsForceMigrationEnabled()
          ? /*SyncToSigninMigrationDecision::kMigrateForced*/ 8
          : /*SyncToSigninMigrationDecision::kDontMigrateSyncStatusInitializing*/
          4;
  histograms.ExpectUniqueSample("Sync.SyncToSigninMigrationDecision",
                                expected_decision, 1);
  histograms.ExpectTotalCount("Sync.SyncToSigninMigrationOutcome",
                              IsForceMigrationEnabled() ? 1 : 0);
  histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime",
                              IsForceMigrationEnabled() ? 1 : 0);
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigration.MigrationType",
      IsForceMigrationEnabled() ? /*kForceMigrated*/ 2 : /*kNotMigratedYet*/ 3,
      1);

  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.BOOKMARK", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.PASSWORD", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.READING_LIST", 0);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationDecision.DryRun.EXTENSION", 0);
  histograms.ExpectTotalCount("Sync.SyncToSigninMigrationDecision.DryRun.THEME",
                              0);
  if (IsForceMigrationEnabled()) {
    // The individual data types were not active and so should not be migrated.
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision.Migration.BOOKMARK",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#if !BUILDFLAG(IS_ANDROID)
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision.Migration.PASSWORD",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#endif
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision.Migration.READING_LIST",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#if BUILDFLAG(ENABLE_EXTENSIONS)
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision.Migration.EXTENSION",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
    histograms.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision.Migration.THEME",
        /*SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive*/ 2,
        1);
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  } else {
    // The overall migration didn't run.
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision.Migration.BOOKMARK", 0);
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision.Migration.PASSWORD", 0);
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision.Migration.READING_LIST", 0);
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision.Migration.EXTENSION", 0);
    histograms.ExpectTotalCount(
        "Sync.SyncToSigninMigrationDecision.Migration.THEME", 0);
  }
}

TEST_P(SyncToSigninMigrationMetricsTest, NeverMigrated) {
  // There's no Sync consent, but otherwise everything is active (running in
  // transport mode).
  sync_service_.SetSignedIn(signin::ConsentLevel::kSignin);

  // Save the above state to prefs.
  RecordStateToPrefs();

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The migration should not run since there's no sync-consented user.
  histograms.ExpectUniqueSample("Sync.SyncToSigninMigration.MigrationType",
                                /*kMigrationNotNeeded*/ 4, 1);
}

TEST_P(SyncToSigninMigrationMetricsTest, AlreadyMigrated) {
  if (!IsMigrationEnabled()) {
    return;
  }

  // Sync-the-feature user.
  ASSERT_TRUE(sync_service_.HasSyncConsent());

  // Save the above state to prefs.
  RecordStateToPrefs();

  ASSERT_EQ(
      pref_service_.GetInteger(prefs::kGoogleServicesSyncingUserMigrationType),
      /*kUnknown*/ 0);

  {
    base::HistogramTester histograms;
    MaybeMigrateSyncingUserToSignedInWrapper(
        IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);
    histograms.ExpectUniqueSample("Sync.SyncToSigninMigration.MigrationType",
                                  /*kMigrated*/ 1, 1);
  }

  ASSERT_EQ(
      pref_service_.GetInteger(prefs::kGoogleServicesSyncingUserMigrationType),
      /*kMigrated*/ 1);

  {
    base::HistogramTester histograms;
    MaybeMigrateSyncingUserToSignedInWrapper(
        IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);
    histograms.ExpectUniqueSample("Sync.SyncToSigninMigration.MigrationType",
                                  /*kMigrated*/ 1, 1);
  }
}

TEST_P(SyncToSigninMigrationMetricsTest, AlreadyForceMigrated) {
  if (!IsMigrationEnabled()) {
    return;
  }

  // Sync-the-feature user but in INITIALIZING state.
  sync_service_.SetMaxTransportState(
      syncer::SyncService::TransportState::INITIALIZING);
  ASSERT_TRUE(sync_service_.HasSyncConsent());

  // Save the above state to prefs.
  RecordStateToPrefs();

  ASSERT_EQ(
      pref_service_.GetInteger(prefs::kGoogleServicesSyncingUserMigrationType),
      /*kUnknown*/ 0);

  {
    base::HistogramTester histograms;
    MaybeMigrateSyncingUserToSignedInWrapper(
        IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);
    if (IsForceMigrationEnabled()) {
      histograms.ExpectUniqueSample("Sync.SyncToSigninMigration.MigrationType",
                                    /*kForceMigrated*/ 2, 1);
    } else {
      histograms.ExpectUniqueSample("Sync.SyncToSigninMigration.MigrationType",
                                    /*kNotMigratedYet*/ 3, 1);
    }
  }

  ASSERT_EQ(
      pref_service_.GetInteger(prefs::kGoogleServicesSyncingUserMigrationType),
      IsForceMigrationEnabled() ? /*kForceMigrated*/ 2 : /*kUnknown*/ 0);

  {
    base::HistogramTester histograms;
    MaybeMigrateSyncingUserToSignedInWrapper(
        IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);
    if (IsForceMigrationEnabled()) {
      histograms.ExpectUniqueSample("Sync.SyncToSigninMigration.MigrationType",
                                    /*kForceMigrated*/ 2, 1);
    } else {
      histograms.ExpectUniqueSample("Sync.SyncToSigninMigration.MigrationType",
                                    /*kNotMigratedYet*/ 3, 1);
    }
  }
}

TEST_P(SyncToSigninMigrationMetricsTest, AlreadyMigrated_Unknown) {
  if (!IsMigrationEnabled()) {
    return;
  }
  // Sync-the-transport user.
  sync_service_.SetSignedIn(signin::ConsentLevel::kSignin);

  // Save the above state to prefs.
  RecordStateToPrefs();

  // Simulate migration having already happened.
  pref_service_.SetString(prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn,
                          sync_service_.GetAccountInfo().gaia.ToString());
  ASSERT_EQ(
      pref_service_.GetInteger(prefs::kGoogleServicesSyncingUserMigrationType),
      /*kUnknown*/ 0);

  base::HistogramTester histograms;
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);
  histograms.ExpectUniqueSample("Sync.SyncToSigninMigration.MigrationType",
                                /*kUnknown*/ 0, 1);
}

TEST_P(SyncToSigninMigrationMetricsTest, IneligibleForRegularMigration) {
  if (!IsMigrationEnabled()) {
    return;
  }
  // The user is signed in and opted in to Sync, but Sync is still initializing.
  sync_service_.SetMaxTransportState(
      syncer::SyncService::TransportState::INITIALIZING);
  ASSERT_TRUE(sync_service_.HasSyncConsent());

  // Save the above state to prefs.
  RecordStateToPrefs();

  ASSERT_EQ(
      pref_service_.GetInteger(prefs::kGoogleServicesSyncingUserMigrationType),
      /*kUnknown*/ 0);

  base::HistogramTester histograms;
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigration.MigrationType",
      IsForceMigrationEnabled() ? /*kForceMigrated*/ 2 : /*kNotMigratedYet*/ 3,
      1);
}

INSTANTIATE_TEST_SUITE_P(
    ,
    SyncToSigninMigrationMetricsTest,
    testing::Combine(testing::Values(FeatureState::kMigrationDisabled,
                                     FeatureState::kMigrationEnabled,
                                     FeatureState::kMigrationForced),
                     testing::Values(BlockingState::kAllowed,
                                     BlockingState::kDisallowed)),
    [](const auto& info) {
      return GenerateTestName(info.param, [](const auto& param) {
        switch (std::get<FeatureState>(param)) {
          case FeatureState::kMigrationDisabled:
            return "MigrationDisabled";
          case FeatureState::kMigrationEnabled:
            return "MigrationEnabled";
          case FeatureState::kMigrationForced:
            return "MigrationForced";
        }
        NOTREACHED();
      });
    });

// The test parameter controls whether the synchronous or asynchronous version
// of MaybeMigrateSyncingUserToSignedIn(...) is tested.
class SyncToSigninMigrationDataTypesTest
    : public SyncToSigninMigrationTestBase,
      public testing::TestWithParam<std::tuple<BlockingState>> {
 public:
  SyncToSigninMigrationDataTypesTest()
      : SyncToSigninMigrationTestBase(
            /*migration_feature_enabled=*/true,
            /*force_migration_feature_enabled=*/false) {}

  void SetUp() override {
    // Everything is active.
    ASSERT_EQ(sync_service_.GetTransportState(),
              syncer::SyncService::TransportState::ACTIVE);
    ASSERT_TRUE(sync_service_.HasSyncConsent());
    ASSERT_TRUE(sync_service_.GetActiveDataTypes().HasAll({
        syncer::BOOKMARKS,
        syncer::PASSWORDS,
        syncer::READING_LIST,
#if BUILDFLAG(ENABLE_EXTENSIONS)
        syncer::EXTENSIONS,
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
        syncer::THEMES,
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
    }));

    // Save the above state to prefs.
    RecordStateToPrefs();
  }

  base::FilePath GetBookmarksLocalStorePath() const {
    return fake_profile_dir_.GetPath().Append(
        bookmarks::kLocalOrSyncableBookmarksFileName);
  }
  base::FilePath GetBookmarksAccountStorePath() const {
    return fake_profile_dir_.GetPath().Append(
        bookmarks::kAccountBookmarksFileName);
  }

  base::FilePath GetEncryptedBookmarksLocalStorePath() const {
    return fake_profile_dir_.GetPath().Append(
        bookmarks::kEncryptedLocalOrSyncableBookmarksFileName);
  }
  base::FilePath GetEncryptedBookmarksAccountStorePath() const {
    return fake_profile_dir_.GetPath().Append(
        bookmarks::kEncryptedAccountBookmarksFileName);
  }

  base::FilePath GetPasswordsLocalStorePath() const {
    return fake_profile_dir_.GetPath().AppendASCII("Login Data");
  }
  base::FilePath GetPasswordsAccountStorePath() const {
    return fake_profile_dir_.GetPath().AppendASCII("Login Data For Account");
  }

  bool IsBlockingAllowed() const { return BlockingAllowed(GetParam()); }
};

TEST_P(SyncToSigninMigrationDataTypesTest, MoveBookmarks_BothExist) {
  // Both bookmark stores exist on disk. The account store is empty, since it
  // was unused pre-migration. This is the typical pre-migration state.
  base::WriteFile(GetBookmarksLocalStorePath(), "local bookmarks");
  base::WriteFile(GetBookmarksAccountStorePath(), "");

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The local file should have been moved over the account one.
  EXPECT_FALSE(base::PathExists(GetBookmarksLocalStorePath()));
  EXPECT_TRUE(base::PathExists(GetBookmarksAccountStorePath()));

  std::string account_contents;
  ASSERT_TRUE(base::ReadFileToString(GetBookmarksAccountStorePath(),
                                     &account_contents));
  EXPECT_EQ(account_contents, "local bookmarks");

  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationOutcome.BookmarksFileMove",
      -base::File::FILE_OK, 1);
}

TEST_P(SyncToSigninMigrationDataTypesTest, MoveBookmarks_OnlyLocalExists) {
  // Only the local store exists on disk; the account store doesn't. This is
  // uncommon, but could happen upgrades directly from an old Chrome version
  // that didn't have an account store yet.
  base::WriteFile(GetBookmarksLocalStorePath(), "local bookmarks");

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The local file should have been renamed to the account one.
  EXPECT_FALSE(base::PathExists(GetBookmarksLocalStorePath()));
  EXPECT_TRUE(base::PathExists(GetBookmarksAccountStorePath()));

  std::string account_contents;
  ASSERT_TRUE(base::ReadFileToString(GetBookmarksAccountStorePath(),
                                     &account_contents));
  EXPECT_EQ(account_contents, "local bookmarks");

  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationOutcome.BookmarksFileMove",
      -base::File::FILE_OK, 1);
}

TEST_P(SyncToSigninMigrationDataTypesTest, MoveBookmarks_OnlyAccountExists) {
  // Only the account store exists on disk; the local store doesn't. This
  // should be impossible in practice, except maybe in rare error cases.
  base::WriteFile(GetBookmarksAccountStorePath(), "account bookmarks");

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The migration shouldn't have done anything; the account store should still
  // exist with the same contents.
  EXPECT_FALSE(base::PathExists(GetBookmarksLocalStorePath()));
  EXPECT_TRUE(base::PathExists(GetBookmarksAccountStorePath()));

  std::string account_contents;
  ASSERT_TRUE(base::ReadFileToString(GetBookmarksAccountStorePath(),
                                     &account_contents));
  EXPECT_EQ(account_contents, "account bookmarks");

  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationOutcome.BookmarksFileMove",
      -base::File::FILE_ERROR_NOT_FOUND, 1);
}

TEST_P(SyncToSigninMigrationDataTypesTest, MoveBookmarks_NoneExists) {
  // Neither of the two stores exist on disk. This should be impossible in
  // practice, except maybe in rare error cases.

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The migration shouldn't have done anything; still neither of the stores
  // should exist.
  EXPECT_FALSE(base::PathExists(GetBookmarksLocalStorePath()));
  EXPECT_FALSE(base::PathExists(GetBookmarksAccountStorePath()));

  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationOutcome.BookmarksFileMove",
      -base::File::FILE_ERROR_NOT_FOUND, 1);
}

#if BUILDFLAG(IS_POSIX)
TEST_P(SyncToSigninMigrationDataTypesTest, MoveBookmarks_FolderNotWritable) {
  // Both bookmark stores exist on disk. The account store is empty, since it
  // was unused pre-migration. This is the typical pre-migration state.
  base::WriteFile(GetBookmarksLocalStorePath(), "local bookmarks");
  base::WriteFile(GetBookmarksAccountStorePath(), "");
  // However, the folder containing the files is (for some reason) not writable,
  // so the move/rename can't actually happen. This should not happen in
  // practice (if it does, Chrome will likely be very broken). This test mostly
  // verifies that nothing catastrophic happens, e.g. no crash.
  int mode = 0;
  ASSERT_TRUE(
      base::GetPosixFilePermissions(fake_profile_dir_.GetPath(), &mode));
  mode &= ~base::FILE_PERMISSION_WRITE_BY_USER;
  mode &= ~base::FILE_PERMISSION_WRITE_BY_GROUP;
  mode &= ~base::FILE_PERMISSION_WRITE_BY_OTHERS;
  ASSERT_TRUE(base::SetPosixFilePermissions(fake_profile_dir_.GetPath(), mode));

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // Nothing should have changed.
  EXPECT_TRUE(base::PathExists(GetBookmarksLocalStorePath()));
  EXPECT_TRUE(base::PathExists(GetBookmarksAccountStorePath()));

  std::string local_contents;
  ASSERT_TRUE(
      base::ReadFileToString(GetBookmarksLocalStorePath(), &local_contents));
  EXPECT_EQ(local_contents, "local bookmarks");
  std::string account_contents;
  ASSERT_TRUE(base::ReadFileToString(GetBookmarksAccountStorePath(),
                                     &account_contents));
  EXPECT_EQ(account_contents, "");

  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationOutcome.BookmarksFileMove",
      -base::File::FILE_ERROR_ACCESS_DENIED, 1);
}
#endif  // BUILDFLAG(IS_POSIX)

TEST_P(SyncToSigninMigrationDataTypesTest,
       MoveBookmarks_EncryptionEnabledAndEncryptionFilesExist) {
  base::test::ScopedFeatureList encrypted_bookmarks_features(
      bookmarks::kEncryptBookmarks);

  // Both bookmark stores exist on disk for clear text and encrypted cases. The
  // account store is empty, since it was unused pre-migration. This is the
  // typical pre-migration state when the encryption feature is enabled.
  base::WriteFile(GetBookmarksLocalStorePath(), "local bookmarks");
  base::WriteFile(GetBookmarksAccountStorePath(), "");
  base::WriteFile(GetEncryptedBookmarksLocalStorePath(),
                  "encrypted local bookmarks");
  base::WriteFile(GetEncryptedBookmarksAccountStorePath(), "");

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The local file should have been moved over the account one.
  EXPECT_FALSE(base::PathExists(GetBookmarksLocalStorePath()));
  EXPECT_TRUE(base::PathExists(GetBookmarksAccountStorePath()));
  EXPECT_FALSE(base::PathExists(GetEncryptedBookmarksLocalStorePath()));
  EXPECT_TRUE(base::PathExists(GetEncryptedBookmarksAccountStorePath()));

  std::string account_contents;
  ASSERT_TRUE(base::ReadFileToString(GetBookmarksAccountStorePath(),
                                     &account_contents));
  EXPECT_EQ(account_contents, "local bookmarks");
  std::string encrypted_account_contents;
  ASSERT_TRUE(base::ReadFileToString(GetEncryptedBookmarksAccountStorePath(),
                                     &encrypted_account_contents));
  EXPECT_EQ(encrypted_account_contents, "encrypted local bookmarks");

  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationOutcome.BookmarksFileMove",
      -base::File::FILE_OK, 1);
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationOutcome.EncryptedBookmarksFileMove",
      -base::File::FILE_OK, 1);
}

TEST_P(SyncToSigninMigrationDataTypesTest,
       MoveBookmarks_EncryptionFilesExistButEncryptionDisabled) {
  base::test::ScopedFeatureList encrypted_bookmarks_features;
  encrypted_bookmarks_features.InitAndDisableFeature(
      bookmarks::kEncryptBookmarks);

  // Both bookmark stores exist on disk for clear text and encrypted cases. The
  // account store is empty, since it was unused pre-migration. This could
  // happen if the encryption feature was enabled and then disabled.
  base::WriteFile(GetBookmarksLocalStorePath(), "local bookmarks");
  base::WriteFile(GetBookmarksAccountStorePath(), "");
  base::WriteFile(GetEncryptedBookmarksLocalStorePath(),
                  "encrypted local bookmarks");
  base::WriteFile(GetEncryptedBookmarksAccountStorePath(), "");

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // Clear text bookmark files should have been moved but not the encrypted
  // ones.
  EXPECT_FALSE(base::PathExists(GetBookmarksLocalStorePath()));
  EXPECT_TRUE(base::PathExists(GetBookmarksAccountStorePath()));
  EXPECT_TRUE(base::PathExists(GetEncryptedBookmarksLocalStorePath()));
  EXPECT_TRUE(base::PathExists(GetEncryptedBookmarksAccountStorePath()));

  std::string account_contents;
  ASSERT_TRUE(base::ReadFileToString(GetBookmarksAccountStorePath(),
                                     &account_contents));
  EXPECT_EQ(account_contents, "local bookmarks");
  std::string encrypted_account_contents;
  ASSERT_TRUE(base::ReadFileToString(GetEncryptedBookmarksAccountStorePath(),
                                     &encrypted_account_contents));
  // Encrypted account store shouldn't have changed.
  EXPECT_EQ(encrypted_account_contents, "");

  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationOutcome.BookmarksFileMove",
      -base::File::FILE_OK, 1);
  histograms.ExpectTotalCount(
      "Sync.SyncToSigninMigrationOutcome.EncryptedBookmarksFileMove", 0);
}

TEST_P(SyncToSigninMigrationDataTypesTest,
       MoveBookmarks_EncryptionEnabledButEncryptionFilesDoNotExist) {
  base::test::ScopedFeatureList encrypted_bookmarks_features(
      bookmarks::kEncryptBookmarks);

  // Both clear text bookmark stores exist on disk only but encrypted stores are
  // missing. This could happen if this is the first time the profile loads
  // since encryption was enabled or if an error occurs while creating encrypted
  // files.
  base::WriteFile(GetBookmarksLocalStorePath(), "local bookmarks");
  base::WriteFile(GetBookmarksAccountStorePath(), "");

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The clear text local file should have been moved over the account one.
  // Encrypted files should still be missing.
  EXPECT_FALSE(base::PathExists(GetBookmarksLocalStorePath()));
  EXPECT_TRUE(base::PathExists(GetBookmarksAccountStorePath()));
  EXPECT_FALSE(base::PathExists(GetEncryptedBookmarksLocalStorePath()));
  EXPECT_FALSE(base::PathExists(GetEncryptedBookmarksAccountStorePath()));

  std::string account_contents;
  ASSERT_TRUE(base::ReadFileToString(GetBookmarksAccountStorePath(),
                                     &account_contents));
  EXPECT_EQ(account_contents, "local bookmarks");

  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationOutcome.BookmarksFileMove",
      -base::File::FILE_OK, 1);
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationOutcome.EncryptedBookmarksFileMove",
      -base::File::FILE_ERROR_NOT_FOUND, 1);
}

#if BUILDFLAG(IS_ANDROID)
TEST_P(SyncToSigninMigrationDataTypesTest, MovePasswords_NoMoveOnAndroid) {
  base::WriteFile(GetPasswordsLocalStorePath(), "local passwords");
  base::WriteFile(GetPasswordsAccountStorePath(), "account passwords");
  base::HistogramTester histogram_tester;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The files should be unchanged.
  std::string local_contents;
  std::string account_contents;
  ASSERT_TRUE(
      base::ReadFileToString(GetPasswordsLocalStorePath(), &local_contents));
  ASSERT_TRUE(base::ReadFileToString(GetPasswordsAccountStorePath(),
                                     &account_contents));
  EXPECT_EQ(local_contents, "local passwords");
  EXPECT_EQ(account_contents, "account passwords");
  histogram_tester.ExpectTotalCount(
      "Sync.SyncToSigninMigrationOutcome.PasswordsFileMove", 0);
}
#else
TEST_P(SyncToSigninMigrationDataTypesTest, MovePasswords_BothExist) {
  // Both password stores exist on disk. The account store is empty, since it
  // was unused pre-migration. This is the typical pre-migration state.
  base::WriteFile(GetPasswordsLocalStorePath(), "local passwords");
  base::WriteFile(GetPasswordsAccountStorePath(), "");

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The local file should have been moved over the account one.
  EXPECT_FALSE(base::PathExists(GetPasswordsLocalStorePath()));
  EXPECT_TRUE(base::PathExists(GetPasswordsAccountStorePath()));

  std::string account_contents;
  ASSERT_TRUE(base::ReadFileToString(GetPasswordsAccountStorePath(),
                                     &account_contents));
  EXPECT_EQ(account_contents, "local passwords");

  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationOutcome.PasswordsFileMove",
      -base::File::FILE_OK, 1);
}

TEST_P(SyncToSigninMigrationDataTypesTest, MovePasswords_OnlyLocalExists) {
  // Only the local store exists on disk; the account store doesn't. This is
  // uncommon, but could happen upgrades directly from an old Chrome version
  // that didn't have an account store yet.
  base::WriteFile(GetPasswordsLocalStorePath(), "local passwords");

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The local file should have been renamed to the account one.
  EXPECT_FALSE(base::PathExists(GetPasswordsLocalStorePath()));
  EXPECT_TRUE(base::PathExists(GetPasswordsAccountStorePath()));

  std::string account_contents;
  ASSERT_TRUE(base::ReadFileToString(GetPasswordsAccountStorePath(),
                                     &account_contents));
  EXPECT_EQ(account_contents, "local passwords");

  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationOutcome.PasswordsFileMove",
      -base::File::FILE_OK, 1);
}

TEST_P(SyncToSigninMigrationDataTypesTest, MovePasswords_OnlyAccountExists) {
  // Only the account store exists on disk; the local store doesn't. This
  // should be impossible in practice, except maybe in rare error cases.
  base::WriteFile(GetPasswordsAccountStorePath(), "account passwords");

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The migration shouldn't have done anything; the account store should still
  // exist with the same contents.
  EXPECT_FALSE(base::PathExists(GetPasswordsLocalStorePath()));
  EXPECT_TRUE(base::PathExists(GetPasswordsAccountStorePath()));

  std::string account_contents;
  ASSERT_TRUE(base::ReadFileToString(GetPasswordsAccountStorePath(),
                                     &account_contents));
  EXPECT_EQ(account_contents, "account passwords");

  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationOutcome.PasswordsFileMove",
      -base::File::FILE_ERROR_NOT_FOUND, 1);
}

TEST_P(SyncToSigninMigrationDataTypesTest, MovePasswords_NoneExists) {
  // Neither of the two stores exist on disk. This should be impossible in
  // practice, except maybe in rare error cases.

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The migration shouldn't have done anything; still neither of the stores
  // should exist.
  EXPECT_FALSE(base::PathExists(GetPasswordsLocalStorePath()));
  EXPECT_FALSE(base::PathExists(GetPasswordsAccountStorePath()));

  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationOutcome.PasswordsFileMove",
      -base::File::FILE_ERROR_NOT_FOUND, 1);
}

#if BUILDFLAG(IS_POSIX)
TEST_P(SyncToSigninMigrationDataTypesTest, MovePasswords_FolderNotWritable) {
  // Both password stores exist on disk. The account store is empty, since it
  // was unused pre-migration. This is the typical pre-migration state.
  base::WriteFile(GetPasswordsLocalStorePath(), "local passwords");
  base::WriteFile(GetPasswordsAccountStorePath(), "");
  // However, the folder containing the files is (for some reason) not writable,
  // so the move/rename can't actually happen. This should not happen in
  // practice (if it does, Chrome will likely be very broken). This test mostly
  // verifies that nothing catastrophic happens, e.g. no crash.
  int mode = 0;
  ASSERT_TRUE(
      base::GetPosixFilePermissions(fake_profile_dir_.GetPath(), &mode));
  mode &= ~base::FILE_PERMISSION_WRITE_BY_USER;
  mode &= ~base::FILE_PERMISSION_WRITE_BY_GROUP;
  mode &= ~base::FILE_PERMISSION_WRITE_BY_OTHERS;
  ASSERT_TRUE(base::SetPosixFilePermissions(fake_profile_dir_.GetPath(), mode));

  base::HistogramTester histograms;

  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // Nothing should have changed.
  EXPECT_TRUE(base::PathExists(GetPasswordsLocalStorePath()));
  EXPECT_TRUE(base::PathExists(GetPasswordsAccountStorePath()));

  std::string local_contents;
  ASSERT_TRUE(
      base::ReadFileToString(GetPasswordsLocalStorePath(), &local_contents));
  EXPECT_EQ(local_contents, "local passwords");
  std::string account_contents;
  ASSERT_TRUE(base::ReadFileToString(GetPasswordsAccountStorePath(),
                                     &account_contents));
  EXPECT_EQ(account_contents, "");

  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationOutcome.PasswordsFileMove",
      -base::File::FILE_ERROR_ACCESS_DENIED, 1);
}
#endif  // BUILDFLAG(IS_POSIX)

TEST_P(SyncToSigninMigrationDataTypesTest, MarkStatsTableToBeCleanedUp) {
  ASSERT_FALSE(pref_service_.GetBoolean(
      syncer::prefs::kCleanUpStatsTableFromAccountPasswordStore));

  base::HistogramTester histograms;
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The clean-up does not happen right away, but rather a pref is set
  // to mark that it should be cleaned up.
  EXPECT_TRUE(pref_service_.GetBoolean(
      syncer::prefs::kCleanUpStatsTableFromAccountPasswordStore));
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigration.StatsTableCleanupStep",
      syncer::SyncToSigninMigrationStatsTableCleanupStep::kCleanupRequested, 1);
}
#endif  // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(ENABLE_EXTENSIONS)
TEST_P(SyncToSigninMigrationDataTypesTest, MarkExtensionsToBeMigrated) {
  ASSERT_FALSE(pref_service_.GetBoolean(
      syncer::prefs::internal::kMigrateExtensionsFromLocalToAccount));

  base::HistogramTester histograms;
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The migration does not happen right away, but rather the extensions are
  // marked to be migrated.
  EXPECT_TRUE(pref_service_.GetBoolean(
      syncer::prefs::internal::kMigrateExtensionsFromLocalToAccount));
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigration.ExtensionsMigrationStep",
      syncer::SyncToSigninMigrationExtensionsStep::kMigrationRequested, 1);
}
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
TEST_P(SyncToSigninMigrationDataTypesTest, MarkThemeToBeMigrated) {
  ASSERT_FALSE(pref_service_.GetBoolean(
      syncer::prefs::internal::kMigrateThemeFromLocalToAccount));

  base::HistogramTester histograms;
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The migration does not happen right away, but rather the theme is
  // marked to be migrated.
  EXPECT_TRUE(pref_service_.GetBoolean(
      syncer::prefs::internal::kMigrateThemeFromLocalToAccount));
  histograms.ExpectUniqueSample(
      "Sync.SyncToSigninMigration.ThemeMigrationStep",
      syncer::SyncToSigninMigrationThemeStep::kMigrationRequested, 1);
}
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)

INSTANTIATE_TEST_SUITE_P(
    ,
    SyncToSigninMigrationDataTypesTest,
    testing::Combine(testing::Values(BlockingState::kAllowed,
                                     BlockingState::kDisallowed)),
    [](const auto& info) {
      // Hack because the test has only one parameter.
      return GenerateTestName(info.param, [](const auto& param) { return ""; });
    });

// A test fixture that performs the SyncToSignin migration, then enables the
// "undo migration" feature.The first test param determines whether the
// force-migration feature flag is enabled or not (the regular migration is
// always enabled in this test). The second parameter controls whether the
// synchronous or asynchronous version of MaybeMigrateSyncingUserToSignedIn(...)
// is tested.
class SyncToSigninMigrationUndoTest
    : public SyncToSigninMigrationTestBase,
      public testing::TestWithParam<std::tuple<bool, BlockingState>> {
 public:
  SyncToSigninMigrationUndoTest()
      : SyncToSigninMigrationTestBase(
            /*migration_feature_enabled=*/true,
            /*force_migration_feature_enabled=*/IsForceMigrationEnabled()) {}

  bool IsForceMigrationEnabled() const { return std::get<bool>(GetParam()); }
  bool IsBlockingAllowed() const { return BlockingAllowed(GetParam()); }

  void SetUp() override {
    // Everything is active.
    ASSERT_EQ(sync_service_.GetTransportState(),
              syncer::SyncService::TransportState::ACTIVE);
    ASSERT_TRUE(sync_service_.HasSyncConsent());
    ASSERT_TRUE(sync_service_.GetActiveDataTypes().HasAll({
        syncer::BOOKMARKS,
        syncer::PASSWORDS,
        syncer::READING_LIST,
#if BUILDFLAG(ENABLE_EXTENSIONS)
        syncer::EXTENSIONS,
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
        syncer::THEMES,
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
    }));

    // Save the above state to prefs.
    RecordStateToPrefs();

    // Run the migration, so that there is something to undo.
    MaybeMigrateSyncingUserToSignedInWrapper(
        IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

    undo_feature_.InitAndEnableFeature(
        switches::kUndoMigrationOfSyncingUserToSignedIn);
  }

 private:
  base::test::ScopedFeatureList undo_feature_;
};

TEST_P(SyncToSigninMigrationUndoTest, UndoesMigration) {
  // The user is in the migrated state - signed-in:
  ASSERT_FALSE(
      pref_service_.GetString(prefs::kGoogleServicesAccountId).empty());
  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
            sync_service_.GetAccountInfo().gaia.ToString());
  // Not syncing:
  ASSERT_FALSE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
  ASSERT_TRUE(
      pref_service_.GetString(prefs::kGoogleServicesLastSyncingGaiaId).empty());
  ASSERT_TRUE(pref_service_.GetString(prefs::kGoogleServicesLastSyncingUsername)
                  .empty());
#if !BUILDFLAG(IS_CHROMEOS)
  ASSERT_FALSE(sync_prefs_->IsInitialSyncFeatureSetupComplete());
#endif
  // Marked as "migrated":
  ASSERT_EQ(pref_service_.GetString(
                prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn),
            sync_service_.GetAccountInfo().gaia.ToString());
  ASSERT_EQ(pref_service_.GetString(
                prefs::kGoogleServicesSyncingUsernameMigratedToSignedIn),
            sync_service_.GetAccountInfo().email);
  ASSERT_EQ(
      pref_service_.GetInteger(prefs::kGoogleServicesSyncingUserMigrationType),
      1 /*kMigrated*/);

  // Trigger the "undo" migration.
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The migration should've been undone, and the user should be back in the
  // "syncing" state.
  ASSERT_FALSE(
      pref_service_.GetString(prefs::kGoogleServicesAccountId).empty());
  EXPECT_TRUE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
  EXPECT_TRUE(sync_prefs_->IsInitialSyncFeatureSetupComplete());
  // The "last syncing user" prefs should also have been restored.
  EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingGaiaId),
            sync_service_.GetAccountInfo().gaia.ToString());
  EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingUsername),
            sync_service_.GetAccountInfo().email);
  // And the "was migrated" prefs should've been cleared.
  EXPECT_TRUE(
      pref_service_
          .GetString(prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn)
          .empty());
  EXPECT_TRUE(
      pref_service_
          .GetString(prefs::kGoogleServicesSyncingUsernameMigratedToSignedIn)
          .empty());
  EXPECT_FALSE(pref_service_.GetUserPrefValue(
      prefs::kGoogleServicesSyncingUserMigrationType));
}

TEST_P(SyncToSigninMigrationUndoTest, Idempotent) {
  // Trigger the "undo" migration.
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The user is now back in the "syncing" state.
  ASSERT_FALSE(
      pref_service_.GetString(prefs::kGoogleServicesAccountId).empty());
  ASSERT_TRUE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
  ASSERT_TRUE(
      pref_service_
          .GetString(prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn)
          .empty());
  EXPECT_FALSE(pref_service_.GetUserPrefValue(
      prefs::kGoogleServicesSyncingUserMigrationType));

  // Take a copy of all current pref values, to verify that the second undo
  // attempt doesn't modify any of them.
  const base::DictValue all_prefs =
      pref_service_.user_prefs_store()->GetValues();

  // Trigger the (undo) migration again - it should have no further effect.
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The prefs should be unmodified.
  EXPECT_EQ(pref_service_.user_prefs_store()->GetValues(), all_prefs);
}

TEST_P(SyncToSigninMigrationUndoTest, DoesNotUndoMigrationIfSignedOut) {
  // The user is in the "migrated" state - signed-in, not syncing, marked as
  // migrated.
  ASSERT_FALSE(
      pref_service_.GetString(prefs::kGoogleServicesAccountId).empty());
  ASSERT_FALSE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
  ASSERT_FALSE(
      pref_service_
          .GetString(prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn)
          .empty());

  // The account gets signed out.
  pref_service_.ClearPref(prefs::kGoogleServicesAccountId);

  // Trigger the "undo" migration.
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The migration should NOT have been undone, since the account isn't signed
  // in anymore.
  ASSERT_TRUE(pref_service_.GetString(prefs::kGoogleServicesAccountId).empty());
  EXPECT_FALSE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
}

TEST_P(SyncToSigninMigrationUndoTest, DoesNotUndoMigrationIfDiffentAccount) {
  // The user is in the "migrated" state - signed-in, not syncing, marked as
  // migrated.
  ASSERT_FALSE(
      pref_service_.GetString(prefs::kGoogleServicesAccountId).empty());
  ASSERT_FALSE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
  ASSERT_FALSE(
      pref_service_
          .GetString(prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn)
          .empty());

  // The account gets signed out, and a different account signed in.
  pref_service_.SetString(prefs::kGoogleServicesAccountId, "different_gaia");
  ASSERT_NE(pref_service_.GetString(prefs::kGoogleServicesAccountId),
            pref_service_.GetString(
                prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn));

  // Trigger the "undo" migration.
  MaybeMigrateSyncingUserToSignedInWrapper(
      IsBlockingAllowed(), fake_profile_dir_.GetPath(), &pref_service_);

  // The migration should NOT have been undone, since a different account is
  // signed in now.
  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
            "different_gaia");
  EXPECT_FALSE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
}

INSTANTIATE_TEST_SUITE_P(
    ,
    SyncToSigninMigrationUndoTest,
    testing::Combine(testing::Bool(),
                     testing::Values(BlockingState::kAllowed,
                                     BlockingState::kDisallowed)),
    [](const auto& info) {
      return GenerateTestName(info.param, [](const auto& param) {
        return std::get<bool>(param) ? "ForceMigrationEnabled"
                                     : "ForceMigrationDisabled";
      });
    });

#if !BUILDFLAG(IS_CHROMEOS)
class SyncSetupIncompleteMigrationTest : public SyncToSigninMigrationTestBase,
                                         public testing::Test {
 public:
  SyncSetupIncompleteMigrationTest()
      : SyncToSigninMigrationTestBase(
            /*migration_feature_enabled=*/false,
            /*force_migration_feature_enabled=*/false) {
    feature_list_.InitWithFeatures(
        {syncer::kUnoPhase2FollowUp,
         switches::kMigrateOutOfSyncSetupIncompleteState},
        {});
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

TEST_F(SyncSetupIncompleteMigrationTest, Migrate) {
  ASSERT_TRUE(sync_service_.HasSyncConsent());

  const GaiaId gaia_id = sync_service_.GetAccountInfo().gaia;
  const std::string email = sync_service_.GetAccountInfo().email;

  // Save the above state to prefs.
  RecordStateToPrefs();
  // Mark sync setup incomplete.
  sync_prefs_->ClearInitialSyncFeatureSetupComplete();

  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
            gaia_id.ToString());
  ASSERT_TRUE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingGaiaId),
            gaia_id.ToString());
  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingUsername),
            email);
  ASSERT_FALSE(sync_prefs_->IsInitialSyncFeatureSetupComplete());

  base::HistogramTester histogram_tester;
  MaybeMigrateSyncingUserToSignedIn(fake_profile_dir_.GetPath(),
                                    &pref_service_);

  histogram_tester.ExpectUniqueSample(
      "Sync.SyncSetupIncompleteMigrationDecision",
      /*kMigrate*/ 0, 1);
  histogram_tester.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision",
      /*SyncToSigninMigrationDecision::kDontMigrateNotSyncing*/ 2, 1);

  // Note that TestSyncService doesn't consume the prefs, so verify the prefs
  // directly here.
  // The user should still be signed in.
  EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
            gaia_id.ToString());
  // But not syncing anymore.
  EXPECT_FALSE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
  EXPECT_FALSE(
      pref_service_.GetUserPrefValue(prefs::kGoogleServicesLastSyncingGaiaId));
  EXPECT_FALSE(pref_service_.GetUserPrefValue(
      prefs::kGoogleServicesLastSyncingUsername));
  EXPECT_FALSE(pref_service_.GetUserPrefValue(
      syncer::prefs::internal::kSyncInitialSyncFeatureSetupComplete));
}

TEST_F(SyncSetupIncompleteMigrationTest, ShouldNotMigrateIfNotSignedIn) {
  // Signed-out user.
  sync_service_.SetSignedOut();
  // Save the above state to prefs.
  RecordStateToPrefs();

  base::HistogramTester histogram_tester;
  MaybeMigrateSyncingUserToSignedIn(fake_profile_dir_.GetPath(),
                                    &pref_service_);

  histogram_tester.ExpectUniqueSample(
      "Sync.SyncSetupIncompleteMigrationDecision",
      /*kDontMigrateNotSignedIn*/ 1, 1);
  histogram_tester.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision",
      /*SyncToSigninMigrationDecision::kDontMigrateNotSignedIn*/ 1, 1);
}

TEST_F(SyncSetupIncompleteMigrationTest, ShouldNotMigrateIfNotSyncing) {
  // Signed-in non-syncing user.
  sync_service_.SetSignedIn(signin::ConsentLevel::kSignin);
  // Save the above state to prefs.
  RecordStateToPrefs();

  base::HistogramTester histogram_tester;
  MaybeMigrateSyncingUserToSignedIn(fake_profile_dir_.GetPath(),
                                    &pref_service_);

  histogram_tester.ExpectUniqueSample(
      "Sync.SyncSetupIncompleteMigrationDecision",
      /*kDontMigrateNotSyncing*/ 2, 1);
  histogram_tester.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision",
      /*SyncToSigninMigrationDecision::kDontMigrateNotSyncing*/ 2, 1);
}

TEST_F(SyncSetupIncompleteMigrationTest, ShouldNotMigrateIfSyncSetupComplete) {
  ASSERT_TRUE(sync_service_.HasSyncConsent());

  const GaiaId gaia_id = sync_service_.GetAccountInfo().gaia;
  const std::string email = sync_service_.GetAccountInfo().email;

  // Save the above state to prefs.
  RecordStateToPrefs();

  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
            gaia_id.ToString());
  ASSERT_TRUE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingGaiaId),
            gaia_id.ToString());
  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingUsername),
            email);
  ASSERT_TRUE(sync_prefs_->IsInitialSyncFeatureSetupComplete());

  base::HistogramTester histogram_tester;
  MaybeMigrateSyncingUserToSignedIn(fake_profile_dir_.GetPath(),
                                    &pref_service_);

  histogram_tester.ExpectUniqueSample(
      "Sync.SyncSetupIncompleteMigrationDecision",
      /*kDontMigrateSyncSetupComplete*/ 3, 1);
  histogram_tester.ExpectUniqueSample(
      "Sync.SyncToSigninMigrationDecision",
      /*SyncToSigninMigrationDecision::kDontMigrateFlagDisabled*/ 5, 1);

  // Note that TestSyncService doesn't consume the prefs, so verify the prefs
  // directly here.
  EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
            gaia_id.ToString());
  EXPECT_TRUE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
  EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingGaiaId),
            gaia_id.ToString());
  EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingUsername),
            email);
  EXPECT_TRUE(sync_prefs_->IsInitialSyncFeatureSetupComplete());
}

TEST_F(SyncSetupIncompleteMigrationTest, ShouldNotMigrateIfFlagDisabled) {
  ASSERT_TRUE(sync_service_.HasSyncConsent());

  const GaiaId gaia_id = sync_service_.GetAccountInfo().gaia;
  const std::string email = sync_service_.GetAccountInfo().email;

  // Save the above state to prefs.
  RecordStateToPrefs();
  // Mark sync setup incomplete.
  sync_prefs_->ClearInitialSyncFeatureSetupComplete();

  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
            gaia_id.ToString());
  ASSERT_TRUE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingGaiaId),
            gaia_id.ToString());
  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingUsername),
            email);
  ASSERT_FALSE(sync_prefs_->IsInitialSyncFeatureSetupComplete());

  // Kill-switch active.
  {
    base::test::ScopedFeatureList feature_list;
    feature_list.InitWithFeatures(
        /*enabled_features=*/{syncer::kUnoPhase2FollowUp},
        /*disabled_features=*/
        {switches::kMigrateOutOfSyncSetupIncompleteState});

    base::HistogramTester histogram_tester;
    MaybeMigrateSyncingUserToSignedIn(fake_profile_dir_.GetPath(),
                                      &pref_service_);

    histogram_tester.ExpectUniqueSample(
        "Sync.SyncSetupIncompleteMigrationDecision",
        /*kDontMigrateFlagDisabled*/ 4, 1);
    histogram_tester.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision",
        /*SyncToSigninMigrationDecision::kDontMigrateFlagDisabled*/ 5, 1);

    // Note that TestSyncService doesn't consume the prefs, so verify the prefs
    // directly here.
    EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
              gaia_id.ToString());
    EXPECT_TRUE(
        pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
    EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingGaiaId),
              gaia_id.ToString());
    EXPECT_EQ(
        pref_service_.GetString(prefs::kGoogleServicesLastSyncingUsername),
        email);
  }
  // Fast-follows flag disabled.
  {
    base::test::ScopedFeatureList feature_list;
    feature_list.InitWithFeatures(
        /*enabled_features=*/{switches::kMigrateOutOfSyncSetupIncompleteState},
        /*disabled_features=*/{syncer::kUnoPhase2FollowUp});

    base::HistogramTester histogram_tester;
    MaybeMigrateSyncingUserToSignedIn(fake_profile_dir_.GetPath(),
                                      &pref_service_);

    histogram_tester.ExpectUniqueSample(
        "Sync.SyncSetupIncompleteMigrationDecision",
        /*kDontMigrateFlagDisabled*/ 4, 1);
    histogram_tester.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision",
        /*SyncToSigninMigrationDecision::kDontMigrateFlagDisabled*/ 5, 1);

    // Note that TestSyncService doesn't consume the prefs, so verify the prefs
    // directly here.
    EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
              gaia_id.ToString());
    EXPECT_TRUE(
        pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
    EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingGaiaId),
              gaia_id.ToString());
    EXPECT_EQ(
        pref_service_.GetString(prefs::kGoogleServicesLastSyncingUsername),
        email);
  }
  // Both flags enabled.
  {
    base::test::ScopedFeatureList feature_list;
    feature_list.InitWithFeatures(
        /*enabled_features=*/{syncer::kUnoPhase2FollowUp,
                              switches::kMigrateOutOfSyncSetupIncompleteState},
        /*disabled_features=*/{});

    base::HistogramTester histogram_tester;
    MaybeMigrateSyncingUserToSignedIn(fake_profile_dir_.GetPath(),
                                      &pref_service_);

    histogram_tester.ExpectUniqueSample(
        "Sync.SyncSetupIncompleteMigrationDecision",
        /*kMigrate*/ 0, 1);
    histogram_tester.ExpectUniqueSample(
        "Sync.SyncToSigninMigrationDecision",
        /*SyncToSigninMigrationDecision::kDontMigrateNotSyncing*/ 2, 1);

    // Note that TestSyncService doesn't consume the prefs, so verify the prefs
    // directly here.
    EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
              gaia_id.ToString());
    EXPECT_FALSE(
        pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
    EXPECT_FALSE(pref_service_.GetUserPrefValue(
        prefs::kGoogleServicesLastSyncingGaiaId));
    EXPECT_FALSE(pref_service_.GetUserPrefValue(
        prefs::kGoogleServicesLastSyncingUsername));
    EXPECT_FALSE(pref_service_.GetUserPrefValue(
        syncer::prefs::internal::kSyncInitialSyncFeatureSetupComplete));
  }
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

#endif  // !BUILDFLAG(IS_IOS)

}  // namespace
}  // namespace browser_sync
