// Copyright 2019 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/sync/service/sync_user_settings_impl.h"

#include <memory>

#include "base/feature_list.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "components/os_crypt/async/browser/test_utils.h"
#include "components/os_crypt/async/common/encryptor.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/saved_tab_groups/public/pref_names.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/base/signin_prefs.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/sync/base/custom_passphrase_bootstrap_token.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/features.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/service/glue/sync_transport_data_prefs.h"
#include "components/sync/service/sync_prefs.h"
#include "components/sync/service/sync_service_crypto.h"
#include "components/sync/test/sync_service_crypto_test_utils.h"
#include "components/trusted_vault/test/fake_trusted_vault_client.h"
#include "google_apis/gaia/gaia_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace syncer {

namespace {

using testing::ContainerEq;
using testing::Return;

MATCHER_P(MatchesToken, expected_token, "") {
  return arg.ToProto().SerializeAsString() ==
         expected_token.ToProto().SerializeAsString();
}

constexpr GaiaId::Literal kTestGaiaId("1111");

DataTypeSet GetUserTypes() {
  DataTypeSet user_types = UserTypes();
#if !BUILDFLAG(IS_CHROMEOS)
  // Ignore all Chrome OS types on non-Chrome OS platforms.
  user_types.RemoveAll({APP_LIST, ARC_PACKAGE, OS_PREFERENCES,
                        OS_PRIORITY_PREFERENCES, PRINTERS,
                        PRINTERS_AUTHORIZATION_SERVERS, WIFI_CONFIGURATIONS});
#endif
  return user_types;
}

DataTypeSet GetPreferredUserTypes(
    const SyncUserSettingsImpl& sync_user_settings) {
  return Intersection(UserTypes(), sync_user_settings.GetPreferredDataTypes());
}

class MockSyncServiceCryptoDelegate : public SyncServiceCrypto::Delegate {
 public:
  MockSyncServiceCryptoDelegate() = default;
  ~MockSyncServiceCryptoDelegate() override = default;

  MOCK_METHOD(void, CryptoStateChanged, (), (override));
  MOCK_METHOD(void, CryptoRequiredUserActionChanged, (), (override));
  MOCK_METHOD(void, ReconfigureDataTypesDueToCrypto, (), (override));
  MOCK_METHOD(void, PassphraseTypeChanged, (PassphraseType), (override));
  MOCK_METHOD(std::optional<PassphraseType>,
              GetPassphraseType,
              (),
              (const override));
  MOCK_METHOD(void,
              SetEncryptionBootstrapToken,
              (const CustomPassphraseBootstrapToken&,
               const os_crypt_async::Encryptor&),
              (override));
  MOCK_METHOD(CustomPassphraseBootstrapToken,
              GetEncryptionBootstrapToken,
              (const os_crypt_async::Encryptor&),
              (const override));
};

class MockDelegate : public SyncUserSettingsImpl::Delegate {
 public:
  MockDelegate() = default;
  ~MockDelegate() override = default;

  MOCK_METHOD(bool, IsCustomPassphraseAllowed, (), (const override));
  MOCK_METHOD(SyncPrefs::SyncAccountState,
              GetSyncAccountStateForPrefs,
              (),
              (const override));
  MOCK_METHOD(CoreAccountInfo,
              GetSyncAccountInfoForPrefs,
              (),
              (const override));
  MOCK_METHOD(void, OnSyncClientDisabledByPolicyChanged, (), (override));
  MOCK_METHOD(void, OnSelectedTypesChanged, (), (override));
#if BUILDFLAG(IS_CHROMEOS)
  MOCK_METHOD(void, OnSyncFeatureDisabledViaDashboardCleared, (), (override));
#else   // BUILDFLAG(IS_CHROMEOS)
  MOCK_METHOD(void, OnInitialSyncFeatureSetupCompleted, (), (override));
#endif  // BUILDFLAG(IS_CHROMEOS)
};

class SyncUserSettingsImplTest : public testing::Test {
 protected:
  SyncUserSettingsImplTest() {
    SyncPrefs::RegisterProfilePrefs(pref_service_.registry());
    SigninPrefs::RegisterProfilePrefs(pref_service_.registry());
    SyncTransportDataPrefs::RegisterProfilePrefs(pref_service_.registry());
    signin::IdentityManager::RegisterProfilePrefs(pref_service_.registry());
    // TODO(crbug.com/368409110): Necessary for a workaround in
    // SyncPrefs::KeepAccountSettingsPrefsOnlyForUsers(); see TODO there.
    pref_service_.registry()->RegisterDictionaryPref(
        tab_groups::prefs::kLocallyClosedRemoteTabGroupIds, base::DictValue());
    sync_prefs_ = std::make_unique<SyncPrefs>(&pref_service_);

    sync_service_crypto_ = std::make_unique<SyncServiceCrypto>(
        &sync_service_crypto_delegate_, &trusted_vault_client_);
    sync_service_crypto_->SetEncryptor(GetEncryptorForTest());

    ON_CALL(delegate_, IsCustomPassphraseAllowed).WillByDefault(Return(true));
    ON_CALL(delegate_, GetSyncAccountStateForPrefs)
        .WillByDefault(Return(SyncPrefs::SyncAccountState::kSyncing));
    ON_CALL(delegate_, GetSyncAccountInfoForPrefs).WillByDefault([]() {
      CoreAccountInfo account;
      account.email = "name@account.com";
      account.gaia = kTestGaiaId;
      account.account_id = CoreAccountId::FromGaiaId(account.gaia);
      return account;
    });
  }

  void SetSyncAccountState(SyncPrefs::SyncAccountState sync_account_state) {
    ON_CALL(delegate_, GetSyncAccountStateForPrefs)
        .WillByDefault(Return(sync_account_state));

    if (IsReplaceSyncPromosWithSignInPromosEnabled()) {
      // Enabling `kBookmarks`, `kReadingList` and `kExtensions` require a
      // sign-in pref to be set. This is handled in the `PrimaryAccountManager`
      // on Sign-in/startup. Set manually for this unittest.
      SigninPrefs(pref_service_)
          .SetBookmarksExplicitBrowserSignin(kTestGaiaId, true);
      SigninPrefs(pref_service_)
          .SetExtensionsExplicitBrowserSignin(kTestGaiaId, true);
    }
  }

  std::unique_ptr<SyncUserSettingsImpl> MakeSyncUserSettings(
      DataTypeSet registered_types) {
    return std::make_unique<SyncUserSettingsImpl>(
        &delegate_, sync_service_crypto_.get(), sync_prefs_.get(),
        registered_types);
  }

  base::test::SingleThreadTaskEnvironment task_environment_;
  // The order of fields matters because it determines destruction order and
  // fields are dependent.
  TestingPrefServiceSimple pref_service_;
  std::unique_ptr<SyncPrefs> sync_prefs_;
  testing::NiceMock<MockSyncServiceCryptoDelegate>
      sync_service_crypto_delegate_;
  testing::NiceMock<MockDelegate> delegate_;
  trusted_vault::FakeTrustedVaultClient trusted_vault_client_;
  std::unique_ptr<SyncServiceCrypto> sync_service_crypto_;
};

TEST_F(SyncUserSettingsImplTest, PreferredTypesSyncEverything) {
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());

  DataTypeSet expected_types = GetUserTypes();
  UserSelectableTypeSet all_registered_types =
      sync_user_settings->GetRegisteredSelectableTypes();

  // TODO(crbug.com/445840788): In CL #3, delete (CONTEXTUAL_TASK is now mapped
  // to a selectable type.
  expected_types.Remove(CONTEXTUAL_TASK);

#if BUILDFLAG(IS_CHROMEOS)
  expected_types.RemoveAll({WEB_APKS});
#endif  // BUILDFLAG(IS_CHROMEOS)

  EXPECT_TRUE(sync_user_settings->IsSyncEverythingEnabled());
  EXPECT_THAT(GetPreferredUserTypes(*sync_user_settings),
              ContainerEq(expected_types));

  for (UserSelectableType type : all_registered_types) {
    sync_user_settings->SetSelectedTypes(/*sync_everything=*/true, {type});
    EXPECT_THAT(GetPreferredUserTypes(*sync_user_settings),
                ContainerEq(expected_types));
  }
}

TEST_F(SyncUserSettingsImplTest, GetSelectedTypesWhileSignedOut) {
  // Sanity check: signed-in there are selected types.
  SetSyncAccountState(SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent);
  ASSERT_FALSE(
      MakeSyncUserSettings(GetUserTypes())->GetSelectedTypes().empty());

  // But signed out there are none.
  SetSyncAccountState(SyncPrefs::SyncAccountState::kNotSignedIn);
  EXPECT_TRUE(MakeSyncUserSettings(GetUserTypes())->GetSelectedTypes().empty());
}

// kReplaceSyncPromosWithSignInPromos has been enabled by default on mobile
// platforms for a long time, so the feature-disabled case is not worth testing.
#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
TEST_F(SyncUserSettingsImplTest,
       DefaultSelectedTypesWhileSignedIn_SyncToSigninDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{switches::kSyncEnableBookmarksInTransportMode,
                            kReadingListEnableSyncTransportModeUponSignIn,
                            kSeparateLocalAndAccountSearchEngines,
                            syncer::kSeparateLocalAndAccountThemes,
                            switches::kEnablePreferencesAccountStorage},
      /*disabled_features=*/{kReplaceSyncPromosWithSignInPromos,
                             kReplaceSyncPromosWithSigninPromosNewSignin});

  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());
  SetSyncAccountState(SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent);

  UserSelectableTypeSet expected_types = {UserSelectableType::kPasswords,
                                          UserSelectableType::kAutofill,
                                          UserSelectableType::kPayments};

  EXPECT_THAT(sync_user_settings->GetSelectedTypes(),
              ContainerEq(expected_types));

  // Some types may be enabled via opt-in.
  pref_service_.SetBoolean(
      ::prefs::kPrefsThemesSearchEnginesAccountStorageEnabled, true);
  expected_types.Put(UserSelectableType::kPreferences);
  expected_types.Put(UserSelectableType::kThemes);
  EXPECT_THAT(sync_user_settings->GetSelectedTypes(),
              ContainerEq(expected_types));

#if !BUILDFLAG(IS_CHROMEOS)
  SigninPrefs(pref_service_)
      .SetBookmarksExplicitBrowserSignin(kTestGaiaId, true);
  expected_types.Put(UserSelectableType::kBookmarks);
  expected_types.Put(UserSelectableType::kReadingList);
  EXPECT_THAT(sync_user_settings->GetSelectedTypes(),
              ContainerEq(expected_types));
#endif  // !BUILDFLAG(IS_CHROMEOS)
}
#endif  // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)

TEST_F(SyncUserSettingsImplTest,
       DefaultSelectedTypesWhileSignedIn_SyncToSigninEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{switches::kSyncEnableBookmarksInTransportMode,
                            kReplaceSyncPromosWithSignInPromos,
#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
                            kReadingListEnableSyncTransportModeUponSignIn,
                            kSeparateLocalAndAccountSearchEngines,
                            syncer::kSeparateLocalAndAccountThemes,
#endif
                            switches::kEnablePreferencesAccountStorage},
      /*disabled_features=*/{});

  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());
  SetSyncAccountState(SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent);

  const UserSelectableTypeSet registered_types =
      sync_user_settings->GetRegisteredSelectableTypes();

  UserSelectableTypeSet expected_disabled_types = {};

#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID)
  // Themes is not supported on mobile.
  expected_disabled_types.Put(UserSelectableType::kThemes);
#endif
#if !BUILDFLAG(IS_CHROMEOS)
  // History, Tabs and Saved Tab Groups are enabled by default on ChromeOS,
  // while they require a separate opt-in on the other platforms.
  expected_disabled_types.Put(UserSelectableType::kHistory);
  expected_disabled_types.Put(UserSelectableType::kTabs);
  expected_disabled_types.Put(UserSelectableType::kSavedTabGroups);
  // Cookies is only supported on ChromeOS.
  expected_disabled_types.Put(UserSelectableType::kCookies);
#endif  // BUILDFLAG(IS_CHROMEOS)

  EXPECT_THAT(
      sync_user_settings->GetSelectedTypes(),
      ContainerEq(Difference(registered_types, expected_disabled_types)));
}

TEST_F(SyncUserSettingsImplTest, SetSelectedTypeInTransportMode) {
  SetSyncAccountState(SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent);
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());
  const UserSelectableTypeSet default_types =
      sync_user_settings->GetSelectedTypes();

  sync_user_settings->SetSelectedType(UserSelectableType::kPayments, false);

  EXPECT_THAT(
      sync_user_settings->GetSelectedTypes(),
      ContainerEq(Difference(default_types, {UserSelectableType::kPayments})));

  EXPECT_CALL(delegate_, OnSelectedTypesChanged());
  sync_user_settings->SetSelectedType(UserSelectableType::kPayments, true);

  EXPECT_EQ(sync_user_settings->GetSelectedTypes(), default_types);
}

#if BUILDFLAG(IS_CHROMEOS)
TEST_F(SyncUserSettingsImplTest,
       SetSelectedTypeInTransportModeChromeOsWithReplaceSyncPromosEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(kReplaceSyncPromosWithSignInPromos);

  SetSyncAccountState(SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent);
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());

  const UserSelectableTypeSet default_types =
      sync_user_settings->GetSelectedTypes();
  ASSERT_TRUE(default_types.Has(UserSelectableType::kPayments));

  // Exactly one notification is expected when the type is changed, even though
  // two underlying preferences are updated.
  EXPECT_CALL(delegate_, OnSelectedTypesChanged()).Times(1);

  sync_user_settings->SetSelectedType(UserSelectableType::kPayments, false);

  // The active types (for account) should be updated.
  EXPECT_THAT(
      sync_user_settings->GetSelectedTypes(),
      ContainerEq(Difference(default_types, {UserSelectableType::kPayments})));

  // The syncing user types should ALSO be updated. This is verified by
  // transitioning to kSyncing state (which makes GetSelectedTypes() read from
  // syncing user prefs).
  SetSyncAccountState(SyncPrefs::SyncAccountState::kSyncing);
  EXPECT_THAT(
      sync_user_settings->GetSelectedTypes(),
      ContainerEq(Difference(default_types, {UserSelectableType::kPayments})));
}

TEST_F(SyncUserSettingsImplTest,
       SetSelectedTypeInTransportModeChromeOsWithReplaceSyncPromosDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(kReplaceSyncPromosWithSignInPromos);

  // Measure default syncing user types first.
  SetSyncAccountState(SyncPrefs::SyncAccountState::kSyncing);
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());
  const UserSelectableTypeSet default_syncing_types =
      sync_user_settings->GetSelectedTypes();
  ASSERT_TRUE(default_syncing_types.Has(UserSelectableType::kPayments));

  // Switch to transport mode for the actual test.
  SetSyncAccountState(SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent);
  const UserSelectableTypeSet default_transport_types =
      sync_user_settings->GetSelectedTypes();
  ASSERT_TRUE(default_transport_types.Has(UserSelectableType::kPayments));

  // Exactly one notification is expected when the type is changed.
  EXPECT_CALL(delegate_, OnSelectedTypesChanged()).Times(1);

  sync_user_settings->SetSelectedType(UserSelectableType::kPayments, false);

  // The active types (for account) should be updated.
  EXPECT_THAT(sync_user_settings->GetSelectedTypes(),
              ContainerEq(Difference(default_transport_types,
                                     {UserSelectableType::kPayments})));

  // The syncing user types should NOT be updated (should still be
  // default_syncing_types).
  SetSyncAccountState(SyncPrefs::SyncAccountState::kSyncing);
  EXPECT_THAT(sync_user_settings->GetSelectedTypes(),
              ContainerEq(default_syncing_types));
}
#endif  // BUILDFLAG(IS_CHROMEOS)

TEST_F(SyncUserSettingsImplTest, SetSelectedTypeInFullSyncMode) {
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());
  SetSyncAccountState(SyncPrefs::SyncAccountState::kSyncing);

  UserSelectableTypeSet registered_types =
      sync_user_settings->GetRegisteredSelectableTypes();

  const UserSelectableTypeSet registered_types_except_passwords =
      base::Difference(registered_types,
                       UserSelectableTypeSet({UserSelectableType::kPasswords}));

  ASSERT_NE(registered_types, registered_types_except_passwords);
  ASSERT_EQ(sync_user_settings->GetSelectedTypes(), registered_types);

  // Disable the sync-everything toggle first, which is required to change
  // individual toggles.
  EXPECT_CALL(delegate_, OnSelectedTypesChanged());
  sync_user_settings->SetSelectedTypes(/*sync_everything=*/false,
                                       /*types=*/registered_types);
  ASSERT_EQ(sync_user_settings->GetSelectedTypes(), registered_types);
  ASSERT_FALSE(sync_user_settings->IsSyncEverythingEnabled());
  testing::Mock::VerifyAndClearExpectations(&delegate_);

  EXPECT_CALL(delegate_, OnSelectedTypesChanged());
  sync_user_settings->SetSelectedType(UserSelectableType::kPasswords, false);
  EXPECT_EQ(sync_user_settings->GetSelectedTypes(),
            registered_types_except_passwords);
  testing::Mock::VerifyAndClearExpectations(&delegate_);

  EXPECT_CALL(delegate_, OnSelectedTypesChanged());
  sync_user_settings->SetSelectedType(UserSelectableType::kPasswords, true);
  EXPECT_EQ(sync_user_settings->GetSelectedTypes(), registered_types);
}

#if BUILDFLAG(IS_CHROMEOS)
TEST_F(SyncUserSettingsImplTest, PreferredTypesSyncAllOsTypes) {
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());

  DataTypeSet expected_types = GetUserTypes();
  expected_types.RemoveAll({WEB_APKS});
  // TODO(crbug.com/397767033): In CL #3, delete (AI_THREAD is now mapped to a
  // selectable type.
  expected_types.Remove(CONTEXTUAL_TASK);

  EXPECT_TRUE(sync_user_settings->IsSyncAllOsTypesEnabled());
  EXPECT_THAT(GetPreferredUserTypes(*sync_user_settings),
              ContainerEq(expected_types));

  for (UserSelectableOsType type : UserSelectableOsTypeSet::All()) {
    sync_user_settings->SetSelectedOsTypes(/*sync_all_os_types=*/true,
                                           /*types=*/{type});
    EXPECT_THAT(GetPreferredUserTypes(*sync_user_settings),
                ContainerEq(expected_types));
  }
}
#endif  // BUILDFLAG(IS_CHROMEOS)

TEST_F(SyncUserSettingsImplTest, PreferredTypesNotKeepEverythingSynced) {
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());

  sync_user_settings->SetSelectedTypes(
      /*sync_everything=*/false,
      /*types=*/UserSelectableTypeSet());
#if BUILDFLAG(IS_CHROMEOS)
  // GetPreferredUserTypes() returns DataTypes, which includes both browser
  // and OS types. However, this test exercises browser UserSelectableTypes,
  // so disable OS selectable types.
  sync_user_settings->SetSelectedOsTypes(/*sync_all_os_types=*/false,
                                         UserSelectableOsTypeSet());
#endif  // BUILDFLAG(IS_CHROMEOS)
  // No user selectable types are enabled, so only the "always preferred" types
  // are preferred.
  ASSERT_EQ(AlwaysPreferredUserTypes(),
            GetPreferredUserTypes(*sync_user_settings));

  UserSelectableTypeSet all_registered_types =
      sync_user_settings->GetRegisteredSelectableTypes();

  for (UserSelectableType type : all_registered_types) {
    DataTypeSet expected_preferred_types =
        UserSelectableTypeToAllDataTypes(type);
    expected_preferred_types.PutAll(AlwaysPreferredUserTypes());
    sync_user_settings->SetSelectedTypes(/*sync_everything=*/false,
                                         /*types=*/{type});
    EXPECT_EQ(expected_preferred_types,
              GetPreferredUserTypes(*sync_user_settings));
  }
}

#if BUILDFLAG(IS_CHROMEOS)
TEST_F(SyncUserSettingsImplTest, PreferredTypesNotAllOsTypesSynced) {
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());

  sync_user_settings->SetSelectedTypes(
      /*sync_everything=*/false,
      /*types=*/UserSelectableTypeSet());
  sync_user_settings->SetSelectedOsTypes(
      /*sync_all_os_types=*/false,
      /*types=*/UserSelectableOsTypeSet());
  EXPECT_FALSE(sync_user_settings->IsSyncEverythingEnabled());
  EXPECT_FALSE(sync_user_settings->IsSyncAllOsTypesEnabled());
  EXPECT_EQ(AlwaysPreferredUserTypes(),
            GetPreferredUserTypes(*sync_user_settings));

  for (UserSelectableOsType type : UserSelectableOsTypeSet::All()) {
    DataTypeSet expected_preferred_types =
        UserSelectableOsTypeToAllDataTypes(type);
    expected_preferred_types.PutAll(AlwaysPreferredUserTypes());
    sync_user_settings->SetSelectedOsTypes(/*sync_all_os_types=*/false,
                                           /*types=*/{type});
    EXPECT_EQ(expected_preferred_types,
              GetPreferredUserTypes(*sync_user_settings));
  }
}
#endif  // BUILDFLAG(IS_CHROMEOS)

// Device info should always be enabled.
TEST_F(SyncUserSettingsImplTest, DeviceInfo) {
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());
  EXPECT_TRUE(sync_user_settings->GetPreferredDataTypes().Has(DEVICE_INFO));

  UserSelectableTypeSet all_registered_types =
      sync_user_settings->GetRegisteredSelectableTypes();
  sync_user_settings->SetSelectedTypes(
      /*sync_everything=*/true,
      /*types=*/all_registered_types);
  EXPECT_TRUE(sync_user_settings->GetPreferredDataTypes().Has(DEVICE_INFO));

  sync_user_settings->SetSelectedTypes(
      /*sync_everything=*/false,
      /*types=*/all_registered_types);
  EXPECT_TRUE(sync_user_settings->GetPreferredDataTypes().Has(DEVICE_INFO));

  sync_user_settings = MakeSyncUserSettings({DEVICE_INFO});
  sync_user_settings->SetSelectedTypes(
      /*sync_everything=*/false,
      /*types=*/UserSelectableTypeSet());
  EXPECT_TRUE(sync_user_settings->GetPreferredDataTypes().Has(DEVICE_INFO));
}

// User Consents should always be enabled.
TEST_F(SyncUserSettingsImplTest, UserConsents) {
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());
  EXPECT_TRUE(sync_user_settings->GetPreferredDataTypes().Has(USER_CONSENTS));

  UserSelectableTypeSet all_registered_types =
      sync_user_settings->GetRegisteredSelectableTypes();
  sync_user_settings->SetSelectedTypes(
      /*sync_everything=*/true,
      /*types=*/all_registered_types);
  EXPECT_TRUE(sync_user_settings->GetPreferredDataTypes().Has(USER_CONSENTS));

  sync_user_settings->SetSelectedTypes(
      /*sync_everything=*/false,
      /*types=*/all_registered_types);
  EXPECT_TRUE(sync_user_settings->GetPreferredDataTypes().Has(USER_CONSENTS));

  sync_user_settings = MakeSyncUserSettings({USER_CONSENTS});
  sync_user_settings->SetSelectedTypes(
      /*sync_everything=*/false,
      /*types=*/UserSelectableTypeSet());
  EXPECT_TRUE(sync_user_settings->GetPreferredDataTypes().Has(USER_CONSENTS));
}

#if BUILDFLAG(IS_CHROMEOS)
TEST_F(SyncUserSettingsImplTest, AlwaysPreferredTypes_ChromeOS) {
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());

  // Disable all browser types.
  sync_user_settings->SetSelectedTypes(
      /*sync_everything=*/false,
      /*types=*/UserSelectableTypeSet());

  // Disable all OS types.
  sync_user_settings->SetSelectedOsTypes(
      /*sync_all_os_types=*/false,
      /*types=*/UserSelectableOsTypeSet());

  // Important types are still preferred.
  DataTypeSet preferred_types = sync_user_settings->GetPreferredDataTypes();
  EXPECT_TRUE(preferred_types.Has(DEVICE_INFO));
  EXPECT_TRUE(preferred_types.Has(USER_CONSENTS));
}

TEST_F(SyncUserSettingsImplTest, AppsAreHandledByOsSettings) {
  std::unique_ptr<SyncUserSettingsImpl> settings =
      MakeSyncUserSettings(GetUserTypes());

  ASSERT_TRUE(settings->IsSyncEverythingEnabled());
  ASSERT_TRUE(settings->IsSyncAllOsTypesEnabled());

  // App data types are enabled.
  EXPECT_TRUE(settings->GetPreferredDataTypes().Has(APP_LIST));
  EXPECT_TRUE(settings->GetPreferredDataTypes().Has(APP_SETTINGS));
  EXPECT_TRUE(settings->GetPreferredDataTypes().Has(APPS));
  EXPECT_TRUE(settings->GetPreferredDataTypes().Has(ARC_PACKAGE));
  EXPECT_TRUE(settings->GetPreferredDataTypes().Has(WEB_APPS));

  // Disable browser types.
  settings->SetSelectedTypes(
      /*sync_everything=*/false,
      /*types=*/UserSelectableTypeSet());

  // App data types are still enabled.
  EXPECT_TRUE(settings->GetPreferredDataTypes().Has(APP_LIST));
  EXPECT_TRUE(settings->GetPreferredDataTypes().Has(APP_SETTINGS));
  EXPECT_TRUE(settings->GetPreferredDataTypes().Has(APPS));
  EXPECT_TRUE(settings->GetPreferredDataTypes().Has(ARC_PACKAGE));
  EXPECT_TRUE(settings->GetPreferredDataTypes().Has(WEB_APPS));

  // Disable OS types.
  settings->SetSelectedOsTypes(
      /*sync_all_os_types=*/false,
      /*types=*/UserSelectableOsTypeSet());

  // Apps are disabled.
  EXPECT_FALSE(settings->GetPreferredDataTypes().Has(APP_LIST));
  EXPECT_FALSE(settings->GetPreferredDataTypes().Has(APP_SETTINGS));
  EXPECT_FALSE(settings->GetPreferredDataTypes().Has(APPS));
  EXPECT_FALSE(settings->GetPreferredDataTypes().Has(ARC_PACKAGE));
  EXPECT_FALSE(settings->GetPreferredDataTypes().Has(WEB_APPS));
}
#endif  // BUILDFLAG(IS_CHROMEOS)

TEST_F(SyncUserSettingsImplTest, ShouldSyncSessionsOnlyIfOpenTabsIsSelected) {
  ASSERT_FALSE(AlwaysPreferredUserTypes().Has(HISTORY));
  ASSERT_FALSE(AlwaysPreferredUserTypes().Has(HISTORY_DELETE_DIRECTIVES));
  ASSERT_FALSE(AlwaysPreferredUserTypes().Has(SESSIONS));

  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());

#if BUILDFLAG(IS_CHROMEOS)
  // GetPreferredUserTypes() returns DataTypes, which includes both browser
  // and OS types. However, this test exercises browser UserSelectableTypes,
  // so disable OS selectable types.
  sync_user_settings->SetSelectedOsTypes(/*sync_all_os_types=*/false,
                                         UserSelectableOsTypeSet());
#endif  // BUILDFLAG(IS_CHROMEOS)

  // History and OpenTabs enabled: All the history-related DataTypes should be
  // enabled.
  sync_user_settings->SetSelectedTypes(
      /*sync_everything=*/false,
      /*types=*/{UserSelectableType::kHistory, UserSelectableType::kTabs});
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
  // For android and iOS, we enable SAVED_TAB_GROUP under OpenTabs as well.
  EXPECT_EQ(GetPreferredUserTypes(*sync_user_settings),
            Union(AlwaysPreferredUserTypes(),
                  {COLLABORATION_GROUP, HISTORY, HISTORY_DELETE_DIRECTIVES,
                   SAVED_TAB_GROUP, SHARED_COMMENT, SHARED_TAB_GROUP_DATA,
                   SESSIONS, USER_EVENTS, SHARED_TAB_GROUP_ACCOUNT_DATA,
                   WORKSPACE_DESK, ENCRYPTED_TAB_CONTEXT_CONTAINER,
                   ENCRYPTED_TAB_CONTEXT_ITEM, NOTEBOOK, JOURNEY}));
#else
  EXPECT_EQ(GetPreferredUserTypes(*sync_user_settings),
            Union(AlwaysPreferredUserTypes(),
                  {HISTORY, HISTORY_DELETE_DIRECTIVES, SESSIONS, USER_EVENTS,
                   WORKSPACE_DESK, ENCRYPTED_TAB_CONTEXT_CONTAINER,
                   ENCRYPTED_TAB_CONTEXT_ITEM, NOTEBOOK, JOURNEY}));
#endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)

  // History only: SESSIONS-related types are gone.
  sync_user_settings->SetSelectedTypes(
      /*sync_everything=*/false,
      /*types=*/{UserSelectableType::kHistory});
  EXPECT_EQ(GetPreferredUserTypes(*sync_user_settings),
            Union(AlwaysPreferredUserTypes(),
                  {HISTORY, HISTORY_DELETE_DIRECTIVES, USER_EVENTS, JOURNEY}));

  // OpenTabs only: HISTORY-related types are gone.
  sync_user_settings->SetSelectedTypes(
      /*sync_everything=*/false,
      /*types=*/{UserSelectableType::kTabs});
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
  EXPECT_EQ(
      GetPreferredUserTypes(*sync_user_settings),
      Union(AlwaysPreferredUserTypes(),
            {COLLABORATION_GROUP, SAVED_TAB_GROUP, SESSIONS,
             SHARED_TAB_GROUP_DATA, SHARED_TAB_GROUP_ACCOUNT_DATA,
             WORKSPACE_DESK, SHARED_COMMENT, ENCRYPTED_TAB_CONTEXT_CONTAINER,
             ENCRYPTED_TAB_CONTEXT_ITEM, NOTEBOOK}));
#else
  EXPECT_EQ(GetPreferredUserTypes(*sync_user_settings),
            Union(AlwaysPreferredUserTypes(),
                  {SESSIONS, WORKSPACE_DESK, ENCRYPTED_TAB_CONTEXT_CONTAINER,
                   ENCRYPTED_TAB_CONTEXT_ITEM, NOTEBOOK}));
#endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)

// SavedTabGroups enabled on desktop. It should enable both saved tab groups and
// shared tab groups.
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  sync_user_settings->SetSelectedTypes(
      /*sync_everything=*/false,
      /*types=*/{UserSelectableType::kSavedTabGroups});
  EXPECT_EQ(GetPreferredUserTypes(*sync_user_settings),
            Union(AlwaysPreferredUserTypes(),
                  {COLLABORATION_GROUP, SAVED_TAB_GROUP, SHARED_COMMENT,
                   SHARED_TAB_GROUP_DATA, SHARED_TAB_GROUP_ACCOUNT_DATA}));
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
}

TEST_F(SyncUserSettingsImplTest, ShouldMutePassphrasePrompt) {
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());

  EXPECT_FALSE(
      sync_user_settings->IsPassphrasePromptMutedForCurrentProductVersion());

  sync_user_settings->MarkPassphrasePromptMutedForCurrentProductVersion();
  EXPECT_TRUE(
      sync_user_settings->IsPassphrasePromptMutedForCurrentProductVersion());

  // Clearing the preference should unmute the prompt.
  sync_prefs_->ClearPassphrasePromptMutedProductVersion();
  EXPECT_FALSE(
      sync_user_settings->IsPassphrasePromptMutedForCurrentProductVersion());
}

TEST_F(SyncUserSettingsImplTest, ShouldClearPassphrasePromptMuteUponUpgrade) {
  // Mimic an old product version being written to prefs.
  sync_prefs_->SetPassphrasePromptMutedProductVersion(/*major_version=*/73);

  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());

  EXPECT_FALSE(
      sync_user_settings->IsPassphrasePromptMutedForCurrentProductVersion());

  // Muting should still work.
  sync_user_settings->MarkPassphrasePromptMutedForCurrentProductVersion();
  EXPECT_TRUE(
      sync_user_settings->IsPassphrasePromptMutedForCurrentProductVersion());
}

TEST_F(SyncUserSettingsImplTest, EncryptionBootstrapTokenForSyncingUser) {
  SetSyncAccountState(SyncPrefs::SyncAccountState::kSyncing);
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());
  scoped_refptr<os_crypt_async::Encryptor> encryptor =
      os_crypt_async::GetTestEncryptorForTesting();
  ASSERT_TRUE(
      sync_user_settings->GetEncryptionBootstrapToken(*encryptor).IsEmpty());

  CustomPassphraseBootstrapToken token =
      CustomPassphraseBootstrapToken::CreateFakeForTesting(1);

  sync_user_settings->SetEncryptionBootstrapToken(token, *encryptor);
  EXPECT_THAT(sync_user_settings->GetEncryptionBootstrapToken(*encryptor),
              MatchesToken(token));
  EXPECT_THAT(sync_user_settings->GetEncryptionBootstrapToken(*encryptor),
              MatchesToken(sync_prefs_->GetEncryptionBootstrapTokenForAccount(
                  *encryptor, kTestGaiaId)));
  sync_prefs_->ClearEncryptionBootstrapTokenForAccount(kTestGaiaId);
  EXPECT_TRUE(
      sync_user_settings->GetEncryptionBootstrapToken(*encryptor).IsEmpty());
}

TEST_F(SyncUserSettingsImplTest, EncryptionBootstrapTokenPerAccountSignedOut) {
  SetSyncAccountState(SyncPrefs::SyncAccountState::kNotSignedIn);
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());
  scoped_refptr<os_crypt_async::Encryptor> encryptor =
      os_crypt_async::GetTestEncryptorForTesting();
  EXPECT_TRUE(
      sync_user_settings->GetEncryptionBootstrapToken(*encryptor).IsEmpty());
}

TEST_F(SyncUserSettingsImplTest, EncryptionBootstrapTokenPerAccount) {
  SetSyncAccountState(SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent);
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());
  scoped_refptr<os_crypt_async::Encryptor> encryptor =
      os_crypt_async::GetTestEncryptorForTesting();
  ASSERT_TRUE(
      sync_user_settings->GetEncryptionBootstrapToken(*encryptor).IsEmpty());

  CustomPassphraseBootstrapToken token =
      CustomPassphraseBootstrapToken::CreateFakeForTesting(1);

  sync_user_settings->SetEncryptionBootstrapToken(token, *encryptor);
  EXPECT_THAT(sync_user_settings->GetEncryptionBootstrapToken(*encryptor),
              MatchesToken(token));
  EXPECT_THAT(sync_user_settings->GetEncryptionBootstrapToken(*encryptor),
              MatchesToken(sync_prefs_->GetEncryptionBootstrapTokenForAccount(
                  *encryptor, kTestGaiaId)));
}

TEST_F(SyncUserSettingsImplTest, ClearEncryptionBootstrapTokenPerAccount) {
  SetSyncAccountState(SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent);
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());
  scoped_refptr<os_crypt_async::Encryptor> encryptor =
      os_crypt_async::GetTestEncryptorForTesting();
  ASSERT_TRUE(
      sync_user_settings->GetEncryptionBootstrapToken(*encryptor).IsEmpty());

  CustomPassphraseBootstrapToken token =
      CustomPassphraseBootstrapToken::CreateFakeForTesting(1);

  sync_user_settings->SetEncryptionBootstrapToken(token, *encryptor);
  sync_user_settings->KeepAccountSettingsPrefsOnlyForUsers({kTestGaiaId});
  EXPECT_THAT(sync_user_settings->GetEncryptionBootstrapToken(*encryptor),
              MatchesToken(token));
  sync_user_settings->KeepAccountSettingsPrefsOnlyForUsers({});
  EXPECT_TRUE(
      sync_user_settings->GetEncryptionBootstrapToken(*encryptor).IsEmpty());
}

#if BUILDFLAG(IS_CHROMEOS)
TEST_F(SyncUserSettingsImplTest, SyncFeatureDisabledViaDashboard) {
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());

  ASSERT_FALSE(sync_user_settings->IsSyncFeatureDisabledViaDashboard());

  EXPECT_CALL(delegate_, OnSyncFeatureDisabledViaDashboardCleared).Times(0);
  sync_user_settings->SetSyncFeatureDisabledViaDashboard();
  EXPECT_TRUE(sync_user_settings->IsSyncFeatureDisabledViaDashboard());

  EXPECT_CALL(delegate_, OnSyncFeatureDisabledViaDashboardCleared);
  sync_user_settings->ClearSyncFeatureDisabledViaDashboard();
  EXPECT_FALSE(sync_user_settings->IsSyncFeatureDisabledViaDashboard());

  // Calling it for the second time should be harmless (no-op).
  EXPECT_CALL(delegate_, OnSyncFeatureDisabledViaDashboardCleared).Times(0);
  sync_user_settings->ClearSyncFeatureDisabledViaDashboard();
  EXPECT_FALSE(sync_user_settings->IsSyncFeatureDisabledViaDashboard());
}

TEST_F(
    SyncUserSettingsImplTest,
    SetSyncFeatureDisabledViaDashboard_SignedInWithoutSyncConsentWithTheFlagEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(syncer::kReplaceSyncPromosWithSignInPromos);

  SetSyncAccountState(SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent);
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());

  // Ensure OS types are enabled by default.
  ASSERT_TRUE(sync_user_settings->IsSyncAllOsTypesEnabled());
  // Ensure browser types are enabled by default.
  ASSERT_TRUE(sync_user_settings->GetPreferredDataTypes().HasAll(
      {NIGORI, DEVICE_INFO, PASSWORDS}));

  sync_user_settings->SetSyncFeatureDisabledViaDashboard();

  EXPECT_FALSE(sync_user_settings->IsSyncAllOsTypesEnabled());
  EXPECT_TRUE(sync_user_settings->GetSelectedOsTypes().empty());
  // When the flag is enabled and user is not syncing, the dashboard reset does
  // NOT clear preferred browser types.
  EXPECT_TRUE(sync_user_settings->GetPreferredDataTypes().HasAll(
      {NIGORI, DEVICE_INFO, PASSWORDS}));
}

TEST_F(SyncUserSettingsImplTest,
       SetSyncFeatureDisabledViaDashboard_SyncingWithTheFlagEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(syncer::kReplaceSyncPromosWithSignInPromos);

  SetSyncAccountState(SyncPrefs::SyncAccountState::kSyncing);
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());

  // Ensure OS types are enabled by default.
  ASSERT_TRUE(sync_user_settings->IsSyncAllOsTypesEnabled());
  // Ensure browser types are enabled by default.
  ASSERT_TRUE(sync_user_settings->GetPreferredDataTypes().HasAll(
      {NIGORI, DEVICE_INFO, BOOKMARKS, PASSWORDS}));

  sync_user_settings->SetSyncFeatureDisabledViaDashboard();

  EXPECT_TRUE(sync_user_settings->IsSyncAllOsTypesEnabled());
  // Even when the feature is enabled, if the user is syncing, the dashboard
  // reset clears preferred browser types. Only a minimal set of datatypes
  // should sync.
  EXPECT_TRUE(sync_user_settings->GetPreferredDataTypes().HasAll(
      {NIGORI, DEVICE_INFO}));
  EXPECT_FALSE(sync_user_settings->GetPreferredDataTypes().Has(BOOKMARKS));
  EXPECT_FALSE(sync_user_settings->GetPreferredDataTypes().Has(PASSWORDS));
}

TEST_F(SyncUserSettingsImplTest,
       SetSyncFeatureDisabledViaDashboard_SyncingWithTheFlagDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(
      syncer::kReplaceSyncPromosWithSignInPromos);

  SetSyncAccountState(SyncPrefs::SyncAccountState::kSyncing);
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());

  // Ensure OS types are enabled by default.
  ASSERT_TRUE(sync_user_settings->IsSyncAllOsTypesEnabled());
  // Ensure browser types are enabled by default.
  ASSERT_TRUE(sync_user_settings->GetPreferredDataTypes().HasAll(
      {NIGORI, DEVICE_INFO, BOOKMARKS, PASSWORDS}));

  sync_user_settings->SetSyncFeatureDisabledViaDashboard();

  EXPECT_TRUE(sync_user_settings->IsSyncAllOsTypesEnabled());
  // When the feature is disabled, the dashboard reset clears preferred browser
  // types.  Only a minimal set of datatypes should sync.
  EXPECT_TRUE(sync_user_settings->GetPreferredDataTypes().HasAll(
      {NIGORI, DEVICE_INFO}));
  EXPECT_FALSE(sync_user_settings->GetPreferredDataTypes().Has(BOOKMARKS));
  EXPECT_FALSE(sync_user_settings->GetPreferredDataTypes().Has(PASSWORDS));
}

TEST_F(SyncUserSettingsImplTest,
       PreferredDataTypesWhileSyncFeatureDisabledViaDashboard) {
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());

  ASSERT_FALSE(sync_user_settings->IsSyncFeatureDisabledViaDashboard());
  ASSERT_TRUE(sync_user_settings->GetPreferredDataTypes().HasAll(
      {NIGORI, DEVICE_INFO, BOOKMARKS}));

  sync_user_settings->SetSyncFeatureDisabledViaDashboard();

  ASSERT_TRUE(sync_user_settings->IsSyncFeatureDisabledViaDashboard());
  EXPECT_TRUE(sync_user_settings->GetPreferredDataTypes().HasAll(
      {NIGORI, DEVICE_INFO}));
  EXPECT_FALSE(sync_user_settings->GetPreferredDataTypes().Has(BOOKMARKS));
}
#else   // BUILDFLAG(IS_CHROMEOS)
TEST_F(SyncUserSettingsImplTest, SetInitialSyncFeatureSetupComplete) {
  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
      MakeSyncUserSettings(GetUserTypes());

  ASSERT_FALSE(sync_user_settings->IsInitialSyncFeatureSetupComplete());

  EXPECT_CALL(delegate_, OnInitialSyncFeatureSetupCompleted());
  sync_user_settings->SetInitialSyncFeatureSetupComplete();

  EXPECT_TRUE(sync_user_settings->IsInitialSyncFeatureSetupComplete());
}
#endif  // BUILDFLAG(IS_CHROMEOS)

}  // namespace

}  // namespace syncer
