// Copyright 2017 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/ukm/observers/ukm_consent_state_observer.h"

#include "base/observer_list.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "components/metrics/metrics_features.h"
#include "components/metrics/metrics_profile_pref_names.h"
#include "components/metrics/metrics_reporting_choice_service.h"
#include "components/signin/public/base/consent_level.h"
#include "components/sync/engine/cycle/sync_cycle_snapshot.h"
#include "components/sync/protocol/sync_enums.pb.h"
#include "components/sync/service/sync_token_status.h"
#include "components/sync/test/test_sync_service.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/unified_consent/pref_names.h"
#include "components/unified_consent/unified_consent_service.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/components/kiosk/kiosk_test_utils.h"  // nogncheck
#include "chromeos/components/mgs/managed_guest_session_test_utils.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#endif  // BUILDFLAG(IS_CHROMEOS)

namespace ukm {

namespace {

class MockSyncService : public syncer::TestSyncService {
 public:
  MockSyncService() {
    SetMaxTransportState(TransportState::INITIALIZING);
    SetLastCycleSnapshot(syncer::SyncCycleSnapshot());

#if BUILDFLAG(IS_CHROMEOS)
    SetAppSync(false);
#endif
  }

  MockSyncService(const MockSyncService&) = delete;
  MockSyncService& operator=(const MockSyncService&) = delete;

  ~MockSyncService() override { Shutdown(); }

  void SetStatus(bool has_passphrase, bool history_enabled, bool active) {
    SetMaxTransportState(active ? TransportState::ACTIVE
                                : TransportState::INITIALIZING);
    SetIsUsingExplicitPassphrase(has_passphrase);

    GetUserSettings()->SetSelectedTypes(
        /*sync_everything=*/false,
        /*types=*/history_enabled ? syncer::UserSelectableTypeSet(
                                        {syncer::UserSelectableType::kHistory})
                                  : syncer::UserSelectableTypeSet());

    // It doesn't matter what exactly we set here, it's only relevant that the
    // SyncCycleSnapshot is initialized at all.
    SetLastCycleSnapshot(syncer::SyncCycleSnapshot(
        /*birthday=*/std::string(), /*bag_of_chips=*/std::string(),
        syncer::ModelNeutralState(), syncer::ProgressMarkerMap(), false, 0,
        true, base::Time::Now(), base::Time::Now(),
        sync_pb::SyncEnums::UNKNOWN_ORIGIN, base::Minutes(1), false));

    NotifyObserversOfStateChanged();
  }

  void Shutdown() override {
    for (auto& observer : observers_) {
      observer.OnSyncShutdown(this);
    }
  }

#if BUILDFLAG(IS_CHROMEOS)
  void SetAppSync(bool enabled) {
    auto selected_os_types = GetUserSettings()->GetSelectedOsTypes();

    if (enabled) {
      selected_os_types.Put(syncer::UserSelectableOsType::kOsApps);
    } else {
      selected_os_types.Remove(syncer::UserSelectableOsType::kOsApps);
    }

    GetUserSettings()->SetSelectedOsTypes(false, selected_os_types);
    NotifyObserversOfStateChanged();
  }
#endif  // BUILDFLAG(IS_CHROMEOS)

 private:
  // syncer::TestSyncService:
  void AddObserver(syncer::SyncServiceObserver* observer) override {
    observers_.AddObserver(observer);
  }
  void RemoveObserver(syncer::SyncServiceObserver* observer) override {
    observers_.RemoveObserver(observer);
  }

  void NotifyObserversOfStateChanged() {
    for (auto& observer : observers_) {
      observer.OnStateChanged(this);
    }
  }

  // The list of observers of the SyncService state.
  base::ObserverList<syncer::SyncServiceObserver> observers_;
};

class TestUkmConsentStateObserver : public UkmConsentStateObserver {
 public:
  // Inherits UkmConsentStateObserver constructors.
  using UkmConsentStateObserver::UkmConsentStateObserver;

  TestUkmConsentStateObserver(const TestUkmConsentStateObserver&) = delete;
  TestUkmConsentStateObserver& operator=(const TestUkmConsentStateObserver&) =
      delete;

  ~TestUkmConsentStateObserver() override = default;

  bool ResetPurged() {
    bool was_purged = purged_;
    purged_ = false;
    return was_purged;
  }

  bool ResetNotified() {
    bool notified = notified_;
    notified_ = false;
    return notified;
  }

 private:
  // UkmConsentStateObserver:
  void OnUkmAllowedStateChanged(bool must_purge, UkmConsentState) override {
    notified_ = true;
    purged_ = purged_ || must_purge;
  }
  bool purged_ = false;
  bool notified_ = false;
};

class UkmConsentStateObserverTest : public testing::TestWithParam<bool> {
 public:
  UkmConsentStateObserverTest() = default;

  UkmConsentStateObserverTest(const UkmConsentStateObserverTest&) = delete;
  UkmConsentStateObserverTest& operator=(const UkmConsentStateObserverTest&) =
      delete;

  void RegisterUrlKeyedAnonymizedDataCollectionPref(
      sync_preferences::TestingPrefServiceSyncable& prefs) {
    unified_consent::UnifiedConsentService::RegisterPrefs(prefs.registry());
    metrics::MetricsReportingChoiceService::RegisterProfilePrefs(
        prefs.registry());
  }

  void SetUrlKeyedAnonymizedDataCollectionEnabled(PrefService* prefs,
                                                  bool enabled) {
    prefs->SetBoolean(
        unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
        enabled);
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;
};

void ExpectDwaAllowedForAllProfiles(TestUkmConsentStateObserver& observer,
                                    bool expected_allowed) {
  // App sync is turned off by default in CHROMEOS. This results in DWA not
  // being allowed for that particular test setup, however can be enabled for
  // other platforms.
  // DWA and ChromeOS are tested further below.
#if !BUILDFLAG(IS_CHROMEOS)
  EXPECT_EQ(expected_allowed, observer.IsDwaAllowedForAllProfiles());
#endif
}

}  // namespace

TEST_F(UkmConsentStateObserverTest, NoProfiles) {
  TestUkmConsentStateObserver observer;
  EXPECT_FALSE(observer.IsUkmAllowedForAllProfiles());
  ExpectDwaAllowedForAllProfiles(observer, false);
  EXPECT_FALSE(observer.ResetNotified());
  EXPECT_FALSE(observer.ResetPurged());
}

TEST_F(UkmConsentStateObserverTest, NotActive) {
  MockSyncService sync;
  sync.SetStatus(false, true, false);
  sync_preferences::TestingPrefServiceSyncable prefs;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
  TestUkmConsentStateObserver observer;
  observer.StartObserving(&sync, &prefs);
  EXPECT_FALSE(observer.IsUkmAllowedForAllProfiles());
  ExpectDwaAllowedForAllProfiles(observer, false);
  EXPECT_FALSE(observer.ResetNotified());
  EXPECT_FALSE(observer.ResetPurged());
}

TEST_F(UkmConsentStateObserverTest, OneEnabled) {
  MockSyncService sync;
  sync_preferences::TestingPrefServiceSyncable prefs;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, true);
  TestUkmConsentStateObserver observer;
  observer.StartObserving(&sync, &prefs);
  EXPECT_TRUE(observer.IsUkmAllowedForAllProfiles());
  ExpectDwaAllowedForAllProfiles(observer, true);
  EXPECT_TRUE(observer.ResetNotified());
  EXPECT_FALSE(observer.ResetPurged());
}

TEST_F(UkmConsentStateObserverTest, MixedProfiles) {
  sync_preferences::TestingPrefServiceSyncable prefs1;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs1);
  sync_preferences::TestingPrefServiceSyncable prefs2;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs2);

  TestUkmConsentStateObserver observer;
  MockSyncService sync1;
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs1, false);
  observer.StartObserving(&sync1, &prefs1);
  MockSyncService sync2;
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs2, true);
  observer.StartObserving(&sync2, &prefs2);
  EXPECT_FALSE(observer.IsUkmAllowedForAllProfiles());
  ExpectDwaAllowedForAllProfiles(observer, false);
  EXPECT_FALSE(observer.ResetNotified());
  EXPECT_FALSE(observer.ResetPurged());
}

TEST_F(UkmConsentStateObserverTest, TwoEnabled) {
  sync_preferences::TestingPrefServiceSyncable prefs1;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs1);
  sync_preferences::TestingPrefServiceSyncable prefs2;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs2);
  TestUkmConsentStateObserver observer;
  MockSyncService sync1;
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs1, true);
  observer.StartObserving(&sync1, &prefs1);
  EXPECT_TRUE(observer.ResetNotified());
  MockSyncService sync2;
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs2, true);
  observer.StartObserving(&sync2, &prefs2);
  EXPECT_TRUE(observer.IsUkmAllowedForAllProfiles());
  ExpectDwaAllowedForAllProfiles(observer, true);
  EXPECT_FALSE(observer.ResetNotified());
  EXPECT_FALSE(observer.ResetPurged());
}

TEST_F(UkmConsentStateObserverTest, OneAddRemove) {
  sync_preferences::TestingPrefServiceSyncable prefs;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
  TestUkmConsentStateObserver observer;
  MockSyncService sync;

  observer.StartObserving(&sync, &prefs);
  EXPECT_FALSE(observer.IsUkmAllowedForAllProfiles());
  ExpectDwaAllowedForAllProfiles(observer, false);
  EXPECT_FALSE(observer.ResetNotified());
  EXPECT_FALSE(observer.ResetPurged());
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, true);
  EXPECT_TRUE(observer.IsUkmAllowedForAllProfiles());
  ExpectDwaAllowedForAllProfiles(observer, true);
  EXPECT_TRUE(observer.ResetNotified());
  EXPECT_FALSE(observer.ResetPurged());
  sync.Shutdown();
  EXPECT_FALSE(observer.IsUkmAllowedForAllProfiles());
  ExpectDwaAllowedForAllProfiles(observer, false);
  EXPECT_TRUE(observer.ResetNotified());
  EXPECT_FALSE(observer.ResetPurged());
}

TEST_F(UkmConsentStateObserverTest, PurgeOnDisable) {
  sync_preferences::TestingPrefServiceSyncable prefs;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
  TestUkmConsentStateObserver observer;
  MockSyncService sync;
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, true);
  observer.StartObserving(&sync, &prefs);
  EXPECT_TRUE(observer.IsUkmAllowedForAllProfiles());
  ExpectDwaAllowedForAllProfiles(observer, true);
  EXPECT_TRUE(observer.ResetNotified());
  EXPECT_FALSE(observer.ResetPurged());
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, false);
  EXPECT_FALSE(observer.IsUkmAllowedForAllProfiles());
  ExpectDwaAllowedForAllProfiles(observer, false);
  EXPECT_TRUE(observer.ResetNotified());
  EXPECT_TRUE(observer.ResetPurged());
  sync.Shutdown();
  EXPECT_FALSE(observer.IsUkmAllowedForAllProfiles());
  ExpectDwaAllowedForAllProfiles(observer, false);
  EXPECT_FALSE(observer.ResetNotified());
  EXPECT_FALSE(observer.ResetPurged());
}

TEST_F(UkmConsentStateObserverTest, NoInitialUkmConsentState) {
  MockSyncService sync;
  sync.SetStatus(false, true, false);
  sync_preferences::TestingPrefServiceSyncable prefs;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
  TestUkmConsentStateObserver observer(NoInitialUkmConsentState);
  observer.StartObserving(&sync, &prefs);
  EXPECT_FALSE(observer.IsUkmAllowedForAllProfiles());
  ExpectDwaAllowedForAllProfiles(observer, false);
  EXPECT_TRUE(observer.ResetNotified());
  EXPECT_FALSE(observer.ResetPurged());
  sync.Shutdown();
}

#if BUILDFLAG(IS_CHROMEOS)
TEST_F(UkmConsentStateObserverTest, VerifyConsentStates) {
  sync_preferences::TestingPrefServiceSyncable prefs;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
  TestUkmConsentStateObserver observer;
  MockSyncService sync;
  // Disable app sync consent.
  sync.SetAppSync(false);

  // Enable MSBB consent.
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, /*enabled=*/true);
  observer.StartObserving(&sync, &prefs);

  UkmConsentState state = observer.GetUkmConsentState();

  EXPECT_TRUE(observer.IsUkmAllowedForAllProfiles());
  EXPECT_FALSE(observer.IsDwaAllowedForAllProfiles());
  // MSBB and Extensions are enabled while App Sync is disabled.
  EXPECT_TRUE(state.Has(MSBB));
  EXPECT_TRUE(state.Has(EXTENSIONS));
  EXPECT_FALSE(state.Has(APPS));

  // UKM is enabled and the consent state was changed. Purge will not happen
  // because UKM was enabled not disabled.
  EXPECT_TRUE(observer.ResetNotified());
  EXPECT_FALSE(observer.ResetPurged());

  // Turn on app sync.
  sync.SetAppSync(true);
  state = observer.GetUkmConsentState();

  // Verify that DWA is now enablead the rest of the these values remain
  // unchanged with App-sync enablement.
  EXPECT_TRUE(observer.IsUkmAllowedForAllProfiles());
  EXPECT_TRUE(observer.IsDwaAllowedForAllProfiles());
  EXPECT_TRUE(state.Has(MSBB));
  EXPECT_TRUE(state.Has(EXTENSIONS));
  EXPECT_TRUE(observer.ResetNotified());
  EXPECT_FALSE(observer.ResetPurged());

  // Check for the updated consent propagated correctly.
  EXPECT_TRUE(state.Has(APPS));

  // Turn off MSBB.
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, /*enabled=*/false);

  state = observer.GetUkmConsentState();

  // UKM will remain allowed.
  EXPECT_TRUE(observer.IsUkmAllowedForAllProfiles());
  // DWA should be off as it requires both MSBB and APPS to be on.
  EXPECT_FALSE(observer.IsDwaAllowedForAllProfiles());
  // MSBB should be off.
  EXPECT_FALSE(state.Has(MSBB));
  // Extensions should be off, implicitly.
  EXPECT_FALSE(state.Has(EXTENSIONS));
  // App sync will stay on.
  EXPECT_TRUE(state.Has(APPS));
  EXPECT_TRUE(observer.ResetNotified());
  // UKM is still allowed, total purge is not triggered.
  EXPECT_FALSE(observer.ResetPurged());

  // Finally, turn off app sync.
  sync.SetAppSync(false);

  state = observer.GetUkmConsentState();
  // All consents should be off.
  EXPECT_FALSE(state.Has(MSBB));
  EXPECT_FALSE(state.Has(EXTENSIONS));
  EXPECT_FALSE(state.Has(APPS));
  EXPECT_TRUE(observer.ResetNotified());

  // All UKM consent is turned off, total purge will be triggered.
  EXPECT_TRUE(observer.ResetPurged());
}

TEST_F(UkmConsentStateObserverTest, VerifyConflictingProfilesRevokesConsent) {
  sync_preferences::TestingPrefServiceSyncable prefs1;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs1);
  sync_preferences::TestingPrefServiceSyncable prefs2;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs2);

  // Add Profile 1 with MSBB consent but not App Sync.
  TestUkmConsentStateObserver observer;
  MockSyncService sync1;
  sync1.SetAppSync(false);
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs1, /*enabled=*/true);
  observer.StartObserving(&sync1, &prefs1);
  EXPECT_TRUE(observer.ResetNotified());
  EXPECT_FALSE(observer.ResetPurged());
  const UkmConsentState consent_state = observer.GetUkmConsentState();
  EXPECT_TRUE(consent_state.Has(MSBB));
  EXPECT_FALSE(consent_state.Has(APPS));

  // Add Profile 2 with App Sync consent but not MSBB.
  MockSyncService sync2;
  sync2.SetAppSync(true);
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs2, /*enabled=*/false);
  observer.StartObserving(&sync2, &prefs2);
  EXPECT_TRUE(observer.ResetNotified());

  // Consents of MSBB and App-sync for each profile conflicts with either other
  // resulting in all types of consent being false.
  // MSBB is off because Profile 1 consents but Profile 2 doesn't.
  // APP sync is off because Profile 2 consents but Profile 1 doesn't.
  UkmConsentState state = observer.GetUkmConsentState();
  EXPECT_FALSE(observer.IsUkmAllowedForAllProfiles());
  EXPECT_FALSE(observer.IsDwaAllowedForAllProfiles());
  EXPECT_FALSE(state.Has(MSBB));
  EXPECT_FALSE(state.Has(EXTENSIONS));
  EXPECT_FALSE(state.Has(APPS));
  EXPECT_FALSE(observer.ResetPurged());
}

// Test consent state for kiosk.
class KioskUkmConsentStateObserverTest : public UkmConsentStateObserverTest {
 public:
  bool is_ukm_collection_enabled() const { return GetParam(); }
};

TEST_P(KioskUkmConsentStateObserverTest, VerifyDefaultConsent) {
  // Enter Kiosk session.
  TestingPrefServiceSimple local_state;
  user_manager::UserManager::RegisterPrefs(local_state.registry());
  user_manager::ScopedUserManager user_manager(
      std::make_unique<user_manager::FakeUserManager>(&local_state));
  chromeos::SetUpFakeChromeAppKioskSession();

  sync_preferences::TestingPrefServiceSyncable prefs;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
  TestUkmConsentStateObserver observer;
  MockSyncService sync;
  // Disable app sync consent.
  // In Kiosk mode, UKM consent is meant to ignore Apps Sync as it's not
  // relevant - there's no user-facing app list. Instead it's based on MSBB
  // consent.
  // For further context, see:
  // https://chromium-review.googlesource.com/c/chromium/src/+/4414724/22/components/ukm/observers/ukm_consent_state_observer.cc#35
  sync.SetAppSync(false);

  // Enable MSBB consent.
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs,
                                             is_ukm_collection_enabled());
  observer.StartObserving(&sync, &prefs);

  UkmConsentState state = observer.GetUkmConsentState();

  EXPECT_EQ(is_ukm_collection_enabled(), observer.IsUkmAllowedForAllProfiles());
  // In Kiosk mode, APPS consent does not depend on App Sync, but on MSBB.
  // Therefore, DWA is allowed when MSBB is on, regardless of App Sync state.
  EXPECT_EQ(is_ukm_collection_enabled(), observer.IsDwaAllowedForAllProfiles());
  // MSBB and Extensions are enabled while App Sync is disabled.
  EXPECT_EQ(is_ukm_collection_enabled(), state.Has(MSBB));
  EXPECT_EQ(is_ukm_collection_enabled(), state.Has(APPS));
}

INSTANTIATE_TEST_SUITE_P(KioskUkmConsentStateObserverTest,
                         KioskUkmConsentStateObserverTest,
                         ::testing::Bool());

// Test consent state for managed guest session (MGS).
class MgsUkmConsentStateObserverTest : public UkmConsentStateObserverTest {
 public:
  bool is_ukm_collection_enabled() const { return GetParam(); }

 private:
  chromeos::FakeManagedGuestSession managed_guest_session;
};

TEST_P(MgsUkmConsentStateObserverTest, VerifyAppsOnlyConsent) {
  sync_preferences::TestingPrefServiceSyncable prefs;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
  TestUkmConsentStateObserver observer;
  MockSyncService sync;
  // Disable app sync consent.
  sync.SetAppSync(false);

  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs,
                                             is_ukm_collection_enabled());
  observer.StartObserving(&sync, &prefs);

  UkmConsentState state = observer.GetUkmConsentState();

  // MGS should report AppKM if policy is enabled.
  EXPECT_EQ(is_ukm_collection_enabled(), state.Has(APPS));
  EXPECT_EQ(false, state.Has(MSBB));
}

INSTANTIATE_TEST_SUITE_P(MgsUkmConsentStateObserverTest,
                         MgsUkmConsentStateObserverTest,
                         ::testing::Bool());

#endif  // BUILDFLAG(IS_CHROMEOS)

class UkmConsentStateObserverMigrationTest
    : public UkmConsentStateObserverTest {
 public:
  UkmConsentStateObserverMigrationTest() {
    scoped_feature_list_.InitAndEnableFeature(
        metrics::features::kRestructureMetricsConsentSettings);
  }
};

TEST_F(UkmConsentStateObserverMigrationTest, Migration_SignedOut_MSBBEnabled) {
  base::HistogramTester histogram_tester;
  MockSyncService sync;
  sync.SetSignedOut();

  sync_preferences::TestingPrefServiceSyncable prefs;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, true);

  TestUkmConsentStateObserver observer;
  observer.StartObserving(&sync, &prefs);

  EXPECT_TRUE(prefs.GetBoolean(metrics::prefs::kAdvancedReportingEnabled));
  EXPECT_TRUE(
      prefs.GetBoolean(metrics::prefs::kAdvancedReportingProfileMigrationDone));

  histogram_tester.ExpectUniqueSample(
      "UKM.ConsentObserver.ConsentStateBeforeRestructure",
      /*sample=*/2, 1);  // kMsbbEnabledWithAppAndExtensionSync
}

TEST_F(UkmConsentStateObserverMigrationTest, Migration_SignedIn_AllEnabled) {
  base::HistogramTester histogram_tester;
  MockSyncService sync;
  sync.SetSignedIn(signin::ConsentLevel::kSignin);

#if BUILDFLAG(IS_CHROMEOS)
  sync.SetAppSync(true);
#endif

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  // Mark apps and extensions sync as supported (registered) and enabled.
  syncer::UserSelectableTypeSet registered_types = {
      syncer::UserSelectableType::kExtensions};
  syncer::UserSelectableTypeSet selected_types = {
      syncer::UserSelectableType::kExtensions};
#if !BUILDFLAG(IS_CHROMEOS)
  registered_types.Put(syncer::UserSelectableType::kApps);
  selected_types.Put(syncer::UserSelectableType::kApps);
#endif
  sync.GetUserSettings()->SetRegisteredSelectableTypes(registered_types);
  sync.GetUserSettings()->SetSelectedTypes(/*sync_everything=*/false,
                                           selected_types);
#endif

  sync_preferences::TestingPrefServiceSyncable prefs;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, true);

  TestUkmConsentStateObserver observer;
  observer.StartObserving(&sync, &prefs);

  EXPECT_TRUE(prefs.GetBoolean(metrics::prefs::kAdvancedReportingEnabled));
  EXPECT_TRUE(
      prefs.GetBoolean(metrics::prefs::kAdvancedReportingProfileMigrationDone));

  histogram_tester.ExpectUniqueSample(
      "UKM.ConsentObserver.ConsentStateBeforeRestructure",
      /*sample=*/2, 1);  // kMsbbEnabledWithAppAndExtensionSync
}

TEST_F(UkmConsentStateObserverMigrationTest, Migration_SignedIn_APPSDisabled) {
  base::HistogramTester histogram_tester;
  MockSyncService sync;
  sync.SetSignedIn(signin::ConsentLevel::kSignin);

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  // Mark apps and extensions sync as supported, but only extensions enabled.
  syncer::UserSelectableTypeSet registered_types = {
      syncer::UserSelectableType::kExtensions};
#if !BUILDFLAG(IS_CHROMEOS)
  registered_types.Put(syncer::UserSelectableType::kApps);
#endif
  sync.GetUserSettings()->SetRegisteredSelectableTypes(registered_types);
  sync.GetUserSettings()->SetSelectedTypes(
      /*sync_everything=*/false, {syncer::UserSelectableType::kExtensions});
#endif

  sync_preferences::TestingPrefServiceSyncable prefs;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, true);

  TestUkmConsentStateObserver observer;
  observer.StartObserving(&sync, &prefs);

#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
  // On mobile platforms, apps sync is not supported, so it is ignored. MSBB is
  // enabled, so migration succeeds!
  EXPECT_TRUE(prefs.GetBoolean(metrics::prefs::kAdvancedReportingEnabled));
  histogram_tester.ExpectUniqueSample(
      "UKM.ConsentObserver.ConsentStateBeforeRestructure",
      /*sample=*/2, 1);  // kMsbbEnabledWithAppAndExtensionSync
#else
  // On desktop platforms, apps sync is supported but disabled, so migration
  // fails!
  EXPECT_FALSE(prefs.GetBoolean(metrics::prefs::kAdvancedReportingEnabled));
  histogram_tester.ExpectUniqueSample(
      "UKM.ConsentObserver.ConsentStateBeforeRestructure",
      /*sample=*/1, 1);  // kMsbbEnabledWithoutAppOrExtensionSync
#endif
  EXPECT_TRUE(
      prefs.GetBoolean(metrics::prefs::kAdvancedReportingProfileMigrationDone));
}

TEST_F(UkmConsentStateObserverMigrationTest, Migration_AlreadyDone) {
  base::HistogramTester histogram_tester;
  MockSyncService sync;
  sync.SetSignedIn(signin::ConsentLevel::kSignin);

#if BUILDFLAG(IS_CHROMEOS)
  sync.SetAppSync(true);
#endif

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  syncer::UserSelectableTypeSet registered_types = {
      syncer::UserSelectableType::kExtensions};
  syncer::UserSelectableTypeSet selected_types = {
      syncer::UserSelectableType::kExtensions};
#if !BUILDFLAG(IS_CHROMEOS)
  registered_types.Put(syncer::UserSelectableType::kApps);
  selected_types.Put(syncer::UserSelectableType::kApps);
#endif
  sync.GetUserSettings()->SetRegisteredSelectableTypes(registered_types);
  sync.GetUserSettings()->SetSelectedTypes(/*sync_everything=*/false,
                                           selected_types);
#endif

  sync_preferences::TestingPrefServiceSyncable prefs;
  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, true);

  // Set post-migration states where user disabled advanced reporting.
  prefs.SetBoolean(metrics::prefs::kAdvancedReportingEnabled, false);
  prefs.SetBoolean(metrics::prefs::kAdvancedReportingProfileMigrationDone,
                   true);

  TestUkmConsentStateObserver observer;
  observer.StartObserving(&sync, &prefs);

  // Migration should not re-run.
  EXPECT_FALSE(prefs.GetBoolean(metrics::prefs::kAdvancedReportingEnabled));
  EXPECT_TRUE(
      prefs.GetBoolean(metrics::prefs::kAdvancedReportingProfileMigrationDone));

  histogram_tester.ExpectUniqueSample(
      "UKM.ConsentObserver.ConsentStateBeforeRestructure",
      /*sample=*/2, 1);  // kMsbbEnabledWithAppAndExtensionSync
}

}  // namespace ukm
