| // 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 "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/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 "google_apis/gaia/gaia_id.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace browser_sync { |
| namespace { |
| |
| // 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()); |
| } |
| 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); |
| |
| // 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::Value::Dict 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::Value::Dict 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); |
| |
| // 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_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_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); |
| // 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_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::Value::Dict 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, 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})); |
| |
| // 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::Value::Dict 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})); |
| |
| // 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); |
| if (IsMigrationEnabled()) { |
| histograms.ExpectTotalCount("Sync.SyncToSigninMigrationOutcome", 1); |
| histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime", 1); |
| } else { |
| histograms.ExpectTotalCount("Sync.SyncToSigninMigrationOutcome", 0); |
| histograms.ExpectTotalCount("Sync.SyncToSigninMigrationTime", 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); |
| } |
| |
| 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); |
| 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})); |
| |
| // 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.ExpectTotalCount( |
| "Sync.SyncToSigninMigrationDecision.DryRun.BOOKMARK", 0); |
| histograms.ExpectTotalCount( |
| "Sync.SyncToSigninMigrationDecision.DryRun.PASSWORD", 0); |
| histograms.ExpectTotalCount( |
| "Sync.SyncToSigninMigrationDecision.DryRun.READING_LIST", 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); |
| } 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); |
| } |
| } |
| |
| 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.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.Migration.BOOKMARK", 0); |
| histograms.ExpectTotalCount( |
| "Sync.SyncToSigninMigrationDecision.Migration.PASSWORD", 0); |
| histograms.ExpectTotalCount( |
| "Sync.SyncToSigninMigrationDecision.Migration.READING_LIST", 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})); |
| |
| // 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.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.Migration.BOOKMARK", 0); |
| histograms.ExpectTotalCount( |
| "Sync.SyncToSigninMigrationDecision.Migration.PASSWORD", 0); |
| histograms.ExpectTotalCount( |
| "Sync.SyncToSigninMigrationDecision.Migration.READING_LIST", 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.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); |
| } 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.ExpectTotalCount( |
| "Sync.SyncToSigninMigrationDecision." + infix + ".BOOKMARK", 0); |
| histograms.ExpectTotalCount( |
| "Sync.SyncToSigninMigrationDecision." + infix + ".PASSWORD", 0); |
| histograms.ExpectTotalCount( |
| "Sync.SyncToSigninMigrationDecision." + infix + ".READING_LIST", 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.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); |
| } |
| } |
| |
| 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); |
| 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); |
| } |
| |
| 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.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); |
| } |
| |
| 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.ExpectTotalCount( |
| "Sync.SyncToSigninMigrationDecision.DryRun.BOOKMARK", 0); |
| histograms.ExpectTotalCount( |
| "Sync.SyncToSigninMigrationDecision.DryRun.PASSWORD", 0); |
| histograms.ExpectTotalCount( |
| "Sync.SyncToSigninMigrationDecision.DryRun.READING_LIST", 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); |
| } 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); |
| } |
| } |
| |
| 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})); |
| |
| // Save the above state to prefs. |
| RecordStateToPrefs(); |
| } |
| |
| base::FilePath GetBookmarksLocalStorePath() const { |
| return fake_profile_dir_.GetPath().AppendASCII("Bookmarks"); |
| } |
| base::FilePath GetBookmarksAccountStorePath() const { |
| return fake_profile_dir_.GetPath().AppendASCII("AccountBookmarks"); |
| } |
| |
| 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) |
| |
| #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) |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| 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})); |
| |
| // 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); |
| |
| // 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()); |
| } |
| |
| 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()); |
| |
| // Take a copy of all current pref values, to verify that the second undo |
| // attempt doesn't modify any of them. |
| const base::Value::Dict 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"; |
| }); |
| }); |
| |
| } // namespace |
| } // namespace browser_sync |