blob: ebc2406e245a89106fe52389d062dc62036b39fb [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/themes/theme_local_data_batch_uploader.h"
#include <string>
#include "base/files/file_path.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/protobuf_matchers.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/background/ntp_custom_background_service_constants.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_service_factory.h"
#include "chrome/browser/themes/theme_service_test_utils.h"
#include "chrome/browser/themes/theme_service_utils.h"
#include "chrome/browser/themes/theme_syncable_service.h"
#include "chrome/common/extensions/extension_test_util.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/features.h"
#include "components/sync/model/sync_change.h"
#include "components/sync/protocol/entity_specifics.pb.h"
#include "components/sync/protocol/theme_specifics.pb.h"
#include "components/sync/service/local_data_description.h"
#include "components/sync/test/fake_sync_change_processor.h"
#include "components/sync/test/sync_change_processor_wrapper_for_test.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/permissions/permission_set.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
using base::test::EqualsProto;
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::Property;
using ::testing::SizeIs;
using theme_service::test::MakeThemeChangeList;
using theme_service::test::MakeThemeDataList;
using theme_service::test::MakeThemeExtension;
static const char* kCustomThemeId = "abcdefghijklmnopabcdefghijklmnop";
static const char kCustomThemeName[] = "name";
static const char kCustomThemeUrl[] = "http://update.url/foo";
constexpr char kTestUrl[] = "https://www.foo.com";
#if BUILDFLAG(IS_WIN)
const base::FilePath::CharType kExtensionFilePath[] =
FILE_PATH_LITERAL("c:\\foo");
#elif BUILDFLAG(IS_POSIX)
const base::FilePath::CharType kExtensionFilePath[] = FILE_PATH_LITERAL("/oo");
#else
#error "Unknown platform"
#endif
// Returns a matcher that matches a SyncChangeList that contains a single
// ACTION_UPDATE change with the given ThemeSpecifics.
auto HasThemeSpecifics(const sync_pb::ThemeSpecifics& specifics) {
return AllOf(
Not(IsEmpty()),
Property(
&syncer::SyncChangeList::back,
AllOf(Property(&syncer::SyncChange::change_type,
syncer::SyncChange::ACTION_UPDATE),
Property(&syncer::SyncChange::sync_data,
Property(&syncer::SyncData::GetSpecifics,
Property(&sync_pb::EntitySpecifics::theme,
EqualsProto(specifics)))))));
}
class ThemeLocalDataBatchUploaderTestBase
: public extensions::ExtensionServiceTestBase {
protected:
void SetUp() override {
// Setting a matching update URL is necessary to make the test theme
// considered syncable.
extension_test_util::SetGalleryUpdateURL(GURL(kCustomThemeUrl));
// Prevent theme preprocessing in tests to avoid errors.
ThemeService::DisableThemePackForTesting();
extensions::ExtensionServiceTestBase::SetUp();
InitializeExtensionService(ExtensionServiceInitParams());
service()->Init();
theme_service_ = ThemeServiceFactory::GetForProfile(profile());
theme_sync_service_ = theme_service_->GetThemeSyncableService();
fake_change_processor_ =
std::make_unique<syncer::FakeSyncChangeProcessor>();
// Create and add custom theme extension so the ThemeSyncableService can
// find it.
theme_extension_ = MakeThemeExtension(
base::FilePath(kExtensionFilePath), kCustomThemeId, kCustomThemeName,
extensions::mojom::ManifestLocation::kInternal, kCustomThemeUrl);
extensions::ExtensionPrefs::Get(profile())->AddGrantedPermissions(
theme_extension_->id(), extensions::PermissionSet());
registrar()->AddExtension(theme_extension_);
ASSERT_EQ(1u, extensions::ExtensionRegistry::Get(profile())
->enabled_extensions()
.size());
batch_uploader_ = std::make_unique<ThemeLocalDataBatchUploader>(
theme_sync_service_.get());
}
ThemeService* theme_service() { return theme_service_; }
ThemeSyncableService* theme_sync_service() { return theme_sync_service_; }
syncer::FakeSyncChangeProcessor* fake_change_processor() {
return fake_change_processor_.get();
}
const extensions::Extension* theme_extension() const {
return theme_extension_.get();
}
syncer::LocalDataDescription GetLocalDataDescription() {
syncer::LocalDataDescription description;
base::test::TestFuture<syncer::LocalDataDescription> test_future_;
batch_uploader_->GetLocalDataDescription(test_future_.GetCallback());
return test_future_.Take();
}
void TriggerLocalDataMigration() {
batch_uploader_->TriggerLocalDataMigration();
}
void TriggerLocalDataMigrationForItems(
std::vector<syncer::LocalDataItemModel::DataId> items) {
batch_uploader_->TriggerLocalDataMigrationForItems(items);
}
void StartSyncing(const sync_pb::ThemeSpecifics& theme_specifics) {
theme_sync_service()->WillStartInitialSync();
ASSERT_FALSE(theme_sync_service()->MergeDataAndStartSyncing(
syncer::THEMES, MakeThemeDataList(theme_specifics),
std::unique_ptr<syncer::SyncChangeProcessor>(
new syncer::SyncChangeProcessorWrapperForTest(
fake_change_processor()))));
// Extension theme is applied asynchronously. Wait to detect a theme change
// before exiting.
if (theme_specifics.use_custom_theme()) {
ASSERT_TRUE(base::test::RunUntil(
[&]() { return theme_service()->UsingExtensionTheme(); }));
}
}
private:
raw_ptr<ThemeService> theme_service_;
raw_ptr<ThemeSyncableService> theme_sync_service_;
std::unique_ptr<syncer::FakeSyncChangeProcessor> fake_change_processor_;
scoped_refptr<extensions::Extension> theme_extension_;
std::unique_ptr<ThemeLocalDataBatchUploader> batch_uploader_;
};
class ThemeLocalDataBatchUploaderTestWithFlagDisabled
: public ThemeLocalDataBatchUploaderTestBase {
public:
ThemeLocalDataBatchUploaderTestWithFlagDisabled() {
feature_list_.InitWithFeatures(
/*enabled_features=*/{syncer::kMoveThemePrefsToSpecifics,
syncer::kSeparateLocalAndAccountThemes},
/*disabled_features=*/{syncer::kThemesBatchUpload});
}
private:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(ThemeLocalDataBatchUploaderTestWithFlagDisabled, ShouldReturnNoItems) {
// Local extension theme.
ASSERT_TRUE(base::test::RunUntil(
[&]() { return theme_service()->UsingExtensionTheme(); }));
const sync_pb::ThemeSpecifics local_theme_specifics =
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting();
const sync_pb::ThemeSpecifics remote_theme_specifics =
theme_service::test::CreateThemeSpecificsWithColorTheme();
StartSyncing(remote_theme_specifics);
syncer::LocalDataDescription desc = GetLocalDataDescription();
EXPECT_EQ(desc.type, syncer::THEMES);
EXPECT_THAT(desc.local_data_models, IsEmpty());
}
using ThemeLocalDataBatchUploaderDeathTestWithFlagDisabled =
ThemeLocalDataBatchUploaderTestWithFlagDisabled;
TEST_F(ThemeLocalDataBatchUploaderDeathTestWithFlagDisabled,
TriggerLocalDataMigration) {
// Local extension theme.
ASSERT_TRUE(base::test::RunUntil(
[&]() { return theme_service()->UsingExtensionTheme(); }));
const sync_pb::ThemeSpecifics local_theme_specifics =
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting();
const sync_pb::ThemeSpecifics remote_theme_specifics =
theme_service::test::CreateThemeSpecificsWithColorTheme();
StartSyncing(remote_theme_specifics);
EXPECT_DEATH(TriggerLocalDataMigration(), "");
}
class ThemeLocalDataBatchUploaderTest
: public ThemeLocalDataBatchUploaderTestBase,
public testing::WithParamInterface<sync_pb::ThemeSpecifics> {
public:
ThemeLocalDataBatchUploaderTest() {
feature_list_.InitWithFeatures(
/*enabled_features=*/{syncer::kMoveThemePrefsToSpecifics,
syncer::kSeparateLocalAndAccountThemes,
syncer::kThemesBatchUpload},
/*disabled_features=*/{});
}
private:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(ThemeLocalDataBatchUploaderTest,
ShouldReturnEmptyUponGetLocalDataDescriptionForNoTheme) {
ASSERT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(theme_service::test::EmptySpecifics()));
EXPECT_THAT(GetLocalDataDescription(),
AllOf(Field(&syncer::LocalDataDescription::type, syncer::THEMES),
Field(&syncer::LocalDataDescription::local_data_models,
IsEmpty())));
theme_sync_service()->WillStartInitialSync();
ASSERT_FALSE(theme_sync_service()->MergeDataAndStartSyncing(
syncer::THEMES, syncer::SyncDataList{},
std::make_unique<syncer::SyncChangeProcessorWrapperForTest>(
fake_change_processor())));
base::HistogramTester histogram_tester;
EXPECT_THAT(GetLocalDataDescription(),
AllOf(Field(&syncer::LocalDataDescription::type, syncer::THEMES),
Field(&syncer::LocalDataDescription::local_data_models,
IsEmpty())));
histogram_tester.ExpectUniqueSample("Theme.BatchUpload.HasLocalTheme", false,
1);
}
TEST_F(ThemeLocalDataBatchUploaderTest,
ShouldReturnEmptyUponGetLocalDataDescriptionForAccountTheme) {
// Set extension account theme.
sync_pb::ThemeSpecifics theme_specifics;
theme_specifics.set_use_custom_theme(true);
theme_specifics.set_custom_theme_id(theme_extension()->id());
theme_specifics.set_custom_theme_name(kCustomThemeName);
theme_specifics.set_custom_theme_update_url(kCustomThemeUrl);
theme_sync_service()->WillStartInitialSync();
ASSERT_FALSE(theme_sync_service()->MergeDataAndStartSyncing(
syncer::THEMES, MakeThemeDataList(theme_specifics),
std::unique_ptr<syncer::SyncChangeProcessor>(
new syncer::SyncChangeProcessorWrapperForTest(
fake_change_processor()))));
ASSERT_TRUE(base::test::RunUntil(
[&]() { return theme_service()->UsingExtensionTheme(); }));
EXPECT_THAT(GetLocalDataDescription(),
AllOf(Field(&syncer::LocalDataDescription::type, syncer::THEMES),
Field(&syncer::LocalDataDescription::local_data_models,
IsEmpty())));
// Set a non-extension account theme.
theme_specifics.Clear();
theme_specifics.set_use_custom_theme(false);
theme_specifics.set_browser_color_scheme(
::sync_pb::ThemeSpecifics_BrowserColorScheme_SYSTEM);
sync_pb::ThemeSpecifics::UserColorTheme* user_color_theme =
theme_specifics.mutable_user_color_theme();
user_color_theme->set_color(SK_ColorRED);
user_color_theme->set_browser_color_variant(
sync_pb::ThemeSpecifics_UserColorTheme_BrowserColorVariant_TONAL_SPOT);
ASSERT_FALSE(theme_sync_service()->ProcessSyncChanges(
FROM_HERE, MakeThemeChangeList(theme_specifics)));
ASSERT_FALSE(theme_service()->UsingExtensionTheme());
EXPECT_EQ(theme_service()->GetThemeID(), ThemeService::kUserColorThemeID);
base::HistogramTester histogram_tester;
EXPECT_THAT(GetLocalDataDescription(),
AllOf(Field(&syncer::LocalDataDescription::type, syncer::THEMES),
Field(&syncer::LocalDataDescription::local_data_models,
IsEmpty())));
histogram_tester.ExpectUniqueSample("Theme.BatchUpload.HasLocalTheme", false,
1);
}
TEST_F(ThemeLocalDataBatchUploaderTest, TriggerLocalDataMigrationForNoItem) {
// Local user color theme.
theme_service()->SetUserColorAndBrowserColorVariant(
SK_ColorBLUE, ui::mojom::BrowserColorVariant::kTonalSpot);
ASSERT_EQ(theme_service()->GetThemeID(), ThemeService::kUserColorThemeID);
const sync_pb::ThemeSpecifics local_theme_specifics =
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting();
const sync_pb::ThemeSpecifics remote_theme_specifics =
theme_service::test::CreateThemeSpecificsWithAutogeneratedColorTheme();
StartSyncing(remote_theme_specifics);
base::HistogramTester histogram_tester;
syncer::LocalDataDescription desc = GetLocalDataDescription();
EXPECT_EQ(desc.type, syncer::THEMES);
EXPECT_THAT(desc.local_data_models, SizeIs(1));
histogram_tester.ExpectUniqueSample("Theme.BatchUpload.HasLocalTheme", true,
1);
ASSERT_NE(theme_service()->GetUserColor(), SK_ColorBLUE);
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(remote_theme_specifics));
TriggerLocalDataMigrationForItems(/*items=*/{});
EXPECT_NE(theme_service()->GetThemeID(), ThemeService::kUserColorThemeID);
EXPECT_FALSE(theme_service()->GetUserColor());
// Nothing is committed.
EXPECT_THAT(fake_change_processor()->changes(), IsEmpty());
histogram_tester.ExpectTotalCount(
"Theme.BatchUpload.LocalThemeMigrationTriggered", 0);
theme_sync_service()->StopSyncing(syncer::THEMES);
EXPECT_EQ(theme_service()->GetThemeID(), ThemeService::kUserColorThemeID);
EXPECT_EQ(theme_service()->GetUserColor(), SK_ColorBLUE);
histogram_tester.ExpectUniqueSample("Theme.RestoredLocalThemeUponSignout",
true, 1);
}
TEST_F(ThemeLocalDataBatchUploaderTest,
TriggerLocalDataMigrationForItemsForCorrectItem) {
// Local user color theme.
theme_service()->SetUserColorAndBrowserColorVariant(
SK_ColorBLUE, ui::mojom::BrowserColorVariant::kTonalSpot);
ASSERT_EQ(theme_service()->GetThemeID(), ThemeService::kUserColorThemeID);
const sync_pb::ThemeSpecifics local_theme_specifics =
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting();
const sync_pb::ThemeSpecifics remote_theme_specifics =
theme_service::test::CreateThemeSpecificsWithAutogeneratedColorTheme();
StartSyncing(remote_theme_specifics);
base::HistogramTester histogram_tester;
TriggerLocalDataMigrationForItems(
/*items=*/{ThemeLocalDataBatchUploader::kThemesLocalDataItemModelId});
EXPECT_EQ(theme_service()->GetThemeID(), ThemeService::kUserColorThemeID);
EXPECT_EQ(theme_service()->GetUserColor(), SK_ColorBLUE);
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(local_theme_specifics));
// The local theme is committed.
EXPECT_THAT(fake_change_processor()->changes(),
HasThemeSpecifics(local_theme_specifics));
histogram_tester.ExpectUniqueSample(
"Theme.BatchUpload.LocalThemeMigrationTriggered", true, 1);
// GetLocalDataDescription should now return empty.
EXPECT_THAT(GetLocalDataDescription(),
AllOf(Field(&syncer::LocalDataDescription::type, syncer::THEMES),
Field(&syncer::LocalDataDescription::local_data_models,
IsEmpty())));
histogram_tester.ExpectUniqueSample("Theme.BatchUpload.HasLocalTheme", false,
1);
theme_sync_service()->StopSyncing(syncer::THEMES);
EXPECT_TRUE(theme_service()->UsingDefaultTheme());
histogram_tester.ExpectUniqueSample("Theme.RestoredLocalThemeUponSignout",
false, 1);
}
TEST_P(ThemeLocalDataBatchUploaderTest, LocalExtensionTheme) {
// Local extension theme.
ASSERT_TRUE(base::test::RunUntil(
[&]() { return theme_service()->UsingExtensionTheme(); }));
const sync_pb::ThemeSpecifics local_theme_specifics =
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting();
const sync_pb::ThemeSpecifics remote_theme_specifics = GetParam();
StartSyncing(remote_theme_specifics);
syncer::LocalDataDescription desc = GetLocalDataDescription();
EXPECT_EQ(desc.type, syncer::THEMES);
EXPECT_THAT(
desc.local_data_models,
ElementsAre(
AllOf(Field(&syncer::LocalDataItemModel::id,
ThemeLocalDataBatchUploader::kThemesLocalDataItemModelId),
Field(&syncer::LocalDataItemModel::title, kCustomThemeName))));
// Skip the rest of the test if remote theme is the same.
if (local_theme_specifics.SerializeAsString() ==
remote_theme_specifics.SerializeAsString()) {
return;
}
ASSERT_FALSE(theme_service()->UsingExtensionTheme());
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(remote_theme_specifics));
TriggerLocalDataMigration();
EXPECT_TRUE(base::test::RunUntil(
[&]() { return theme_service()->UsingExtensionTheme(); }));
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(local_theme_specifics));
// The local theme is committed.
EXPECT_THAT(fake_change_processor()->changes(),
HasThemeSpecifics(local_theme_specifics));
// GetLocalDataDescription should now return empty.
EXPECT_THAT(GetLocalDataDescription(),
AllOf(Field(&syncer::LocalDataDescription::type, syncer::THEMES),
Field(&syncer::LocalDataDescription::local_data_models,
IsEmpty())));
theme_sync_service()->StopSyncing(syncer::THEMES);
EXPECT_TRUE(theme_service()->UsingDefaultTheme());
}
TEST_P(ThemeLocalDataBatchUploaderTest, LocalAutogeneratedColorTheme) {
// Local autogenerated color theme.
theme_service()->BuildAutogeneratedThemeFromColor(SK_ColorBLUE);
ASSERT_TRUE(theme_service()->UsingAutogeneratedTheme());
const sync_pb::ThemeSpecifics local_theme_specifics =
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting();
const sync_pb::ThemeSpecifics remote_theme_specifics = GetParam();
StartSyncing(remote_theme_specifics);
syncer::LocalDataDescription desc = GetLocalDataDescription();
EXPECT_EQ(desc.type, syncer::THEMES);
EXPECT_THAT(
desc.local_data_models,
ElementsAre(
AllOf(Field(&syncer::LocalDataItemModel::id,
ThemeLocalDataBatchUploader::kThemesLocalDataItemModelId),
Field(&syncer::LocalDataItemModel::title, "Custom color"))));
ASSERT_NE(theme_service()->GetAutogeneratedThemeColor(), SK_ColorBLUE);
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(remote_theme_specifics));
TriggerLocalDataMigration();
EXPECT_TRUE(theme_service()->UsingAutogeneratedTheme());
EXPECT_EQ(theme_service()->GetAutogeneratedThemeColor(), SK_ColorBLUE);
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(local_theme_specifics));
// The local theme is committed.
EXPECT_THAT(fake_change_processor()->changes(),
HasThemeSpecifics(local_theme_specifics));
// GetLocalDataDescription should now return empty.
EXPECT_THAT(GetLocalDataDescription(),
AllOf(Field(&syncer::LocalDataDescription::type, syncer::THEMES),
Field(&syncer::LocalDataDescription::local_data_models,
IsEmpty())));
theme_sync_service()->StopSyncing(syncer::THEMES);
EXPECT_TRUE(theme_service()->UsingDefaultTheme());
}
TEST_P(ThemeLocalDataBatchUploaderTest, LocalUserColorTheme) {
// Local user color theme.
theme_service()->SetUserColorAndBrowserColorVariant(
SK_ColorGREEN, ui::mojom::BrowserColorVariant::kTonalSpot);
ASSERT_EQ(theme_service()->GetThemeID(), ThemeService::kUserColorThemeID);
const sync_pb::ThemeSpecifics local_theme_specifics =
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting();
const sync_pb::ThemeSpecifics remote_theme_specifics = GetParam();
StartSyncing(remote_theme_specifics);
syncer::LocalDataDescription desc = GetLocalDataDescription();
EXPECT_EQ(desc.type, syncer::THEMES);
EXPECT_THAT(
desc.local_data_models,
ElementsAre(
AllOf(Field(&syncer::LocalDataItemModel::id,
ThemeLocalDataBatchUploader::kThemesLocalDataItemModelId),
Field(&syncer::LocalDataItemModel::title, "Green color"))));
ASSERT_NE(theme_service()->GetUserColor(), SK_ColorGREEN);
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(remote_theme_specifics));
TriggerLocalDataMigration();
EXPECT_EQ(theme_service()->GetThemeID(), ThemeService::kUserColorThemeID);
EXPECT_EQ(theme_service()->GetUserColor(), SK_ColorGREEN);
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(local_theme_specifics));
// The local theme is committed.
EXPECT_THAT(fake_change_processor()->changes(),
HasThemeSpecifics(local_theme_specifics));
// GetLocalDataDescription should now return empty.
EXPECT_THAT(GetLocalDataDescription(),
AllOf(Field(&syncer::LocalDataDescription::type, syncer::THEMES),
Field(&syncer::LocalDataDescription::local_data_models,
IsEmpty())));
theme_sync_service()->StopSyncing(syncer::THEMES);
EXPECT_TRUE(theme_service()->UsingDefaultTheme());
}
TEST_P(ThemeLocalDataBatchUploaderTest, LocalGrayscaleTheme) {
// Local grayscale theme.
theme_service()->SetIsGrayscale(true);
ASSERT_TRUE(theme_service()->GetIsGrayscale());
const sync_pb::ThemeSpecifics local_theme_specifics =
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting();
const sync_pb::ThemeSpecifics remote_theme_specifics = GetParam();
StartSyncing(remote_theme_specifics);
syncer::LocalDataDescription desc = GetLocalDataDescription();
EXPECT_EQ(desc.type, syncer::THEMES);
EXPECT_THAT(
desc.local_data_models,
ElementsAre(AllOf(
Field(&syncer::LocalDataItemModel::id,
ThemeLocalDataBatchUploader::kThemesLocalDataItemModelId),
Field(&syncer::LocalDataItemModel::title, "Grey default color"))));
// Skip the rest of the test if remote theme is the same.
if (local_theme_specifics.SerializeAsString() ==
GetParam().SerializeAsString()) {
return;
}
ASSERT_FALSE(theme_service()->GetIsGrayscale());
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(remote_theme_specifics));
TriggerLocalDataMigration();
EXPECT_TRUE(theme_service()->GetIsGrayscale());
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(local_theme_specifics));
// The local theme is committed.
EXPECT_THAT(fake_change_processor()->changes(),
HasThemeSpecifics(local_theme_specifics));
// GetLocalDataDescription should now return empty.
EXPECT_THAT(GetLocalDataDescription(),
AllOf(Field(&syncer::LocalDataDescription::type, syncer::THEMES),
Field(&syncer::LocalDataDescription::local_data_models,
IsEmpty())));
theme_sync_service()->StopSyncing(syncer::THEMES);
EXPECT_TRUE(theme_service()->UsingDefaultTheme());
}
TEST_P(ThemeLocalDataBatchUploaderTest, LocalNtpBackground) {
// Set custom background via pref.
base::Value::Dict background_dict =
base::Value::Dict()
.Set(kNtpCustomBackgroundURL, kTestUrl)
.Set(kNtpCustomBackgroundAttributionLine1, "attribution_line_1")
.Set(kNtpCustomBackgroundAttributionLine2, "attribution_line_2")
.Set(kNtpCustomBackgroundAttributionActionURL,
"attribution_action_url")
.Set(kNtpCustomBackgroundCollectionId, "collection_id")
.Set(kNtpCustomBackgroundResumeToken, "resume_token")
.Set(kNtpCustomBackgroundRefreshTimestamp,
static_cast<int>(1234567890))
.Set(kNtpCustomBackgroundMainColor, static_cast<int>(SK_ColorRED));
profile()->GetPrefs()->Set(prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse,
base::Value(background_dict.Clone()));
const sync_pb::ThemeSpecifics local_theme_specifics =
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting();
const sync_pb::ThemeSpecifics remote_theme_specifics = GetParam();
StartSyncing(remote_theme_specifics);
syncer::LocalDataDescription desc = GetLocalDataDescription();
EXPECT_EQ(desc.type, syncer::THEMES);
EXPECT_THAT(
desc.local_data_models,
ElementsAre(AllOf(
Field(&syncer::LocalDataItemModel::id,
ThemeLocalDataBatchUploader::kThemesLocalDataItemModelId),
Field(&syncer::LocalDataItemModel::title, "attribution_line_1"))));
EXPECT_NE(profile()->GetPrefs()->GetDict(
prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse),
background_dict);
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(remote_theme_specifics));
TriggerLocalDataMigration();
EXPECT_EQ(profile()->GetPrefs()->GetDict(
prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse),
background_dict);
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(local_theme_specifics));
// The local theme is committed.
EXPECT_THAT(fake_change_processor()->changes(),
HasThemeSpecifics(local_theme_specifics));
// GetLocalDataDescription should now return empty.
EXPECT_THAT(GetLocalDataDescription(),
AllOf(Field(&syncer::LocalDataDescription::type, syncer::THEMES),
Field(&syncer::LocalDataDescription::local_data_models,
IsEmpty())));
theme_sync_service()->StopSyncing(syncer::THEMES);
EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
EXPECT_TRUE(theme_service()->UsingDefaultTheme());
}
// TODO(crbug.com/392074002): Re-think if it makes sense to offer batch upload
// if local theme is only the browser color scheme.
TEST_P(ThemeLocalDataBatchUploaderTest, LocalBrowserColorScheme) {
// Local browser color scheme.
theme_service()->SetBrowserColorScheme(
ThemeService::BrowserColorScheme::kLight);
const sync_pb::ThemeSpecifics local_theme_specifics =
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting();
sync_pb::ThemeSpecifics remote_theme_specifics = GetParam();
StartSyncing(remote_theme_specifics);
syncer::LocalDataDescription desc = GetLocalDataDescription();
EXPECT_EQ(desc.type, syncer::THEMES);
EXPECT_THAT(
desc.local_data_models,
ElementsAre(
AllOf(Field(&syncer::LocalDataItemModel::id,
ThemeLocalDataBatchUploader::kThemesLocalDataItemModelId),
Field(&syncer::LocalDataItemModel::title, "Custom color"))));
TriggerLocalDataMigration();
EXPECT_EQ(theme_service()->GetBrowserColorScheme(),
ThemeService::BrowserColorScheme::kLight);
EXPECT_TRUE(theme_service()->UsingDefaultTheme());
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(local_theme_specifics));
// The local theme is committed.
EXPECT_THAT(fake_change_processor()->changes(),
HasThemeSpecifics(local_theme_specifics));
// GetLocalDataDescription should now return empty.
EXPECT_THAT(GetLocalDataDescription(),
AllOf(Field(&syncer::LocalDataDescription::type, syncer::THEMES),
Field(&syncer::LocalDataDescription::local_data_models,
IsEmpty())));
theme_sync_service()->StopSyncing(syncer::THEMES);
ASSERT_EQ(theme_service()->GetBrowserColorScheme(),
ThemeService::BrowserColorScheme::kLight);
EXPECT_TRUE(theme_service()->UsingDefaultTheme());
}
TEST_P(ThemeLocalDataBatchUploaderTest, LocalSystemTheme) {
// Local system theme.
if (!theme_service()->IsSystemThemeDistinctFromDefaultTheme()) {
return;
}
theme_service()->UseSystemTheme();
ASSERT_TRUE(theme_service()->UsingSystemTheme());
const sync_pb::ThemeSpecifics local_theme_specifics =
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting();
const sync_pb::ThemeSpecifics remote_theme_specifics = GetParam();
StartSyncing(remote_theme_specifics);
syncer::LocalDataDescription desc = GetLocalDataDescription();
EXPECT_EQ(desc.type, syncer::THEMES);
EXPECT_THAT(desc.local_data_models, IsEmpty());
ASSERT_FALSE(theme_service()->UsingSystemTheme());
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(remote_theme_specifics));
TriggerLocalDataMigration();
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(remote_theme_specifics));
// The local theme is not committed.
EXPECT_THAT(fake_change_processor()->changes(),
Not(HasThemeSpecifics(local_theme_specifics)));
theme_sync_service()->StopSyncing(syncer::THEMES);
EXPECT_TRUE(theme_service()->UsingSystemTheme());
}
TEST_P(ThemeLocalDataBatchUploaderTest, LocalDefaultTheme) {
// Set local default theme.
theme_service()->UseDefaultTheme();
ASSERT_TRUE(theme_service()->UsingDefaultTheme());
const sync_pb::ThemeSpecifics local_theme_specifics =
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting();
const sync_pb::ThemeSpecifics remote_theme_specifics = GetParam();
StartSyncing(remote_theme_specifics);
syncer::LocalDataDescription desc = GetLocalDataDescription();
EXPECT_EQ(desc.type, syncer::THEMES);
EXPECT_THAT(desc.local_data_models, IsEmpty());
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(remote_theme_specifics));
TriggerLocalDataMigration();
EXPECT_THAT(
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
EqualsProto(remote_theme_specifics));
// The local theme is not committed.
EXPECT_THAT(fake_change_processor()->changes(),
Not(HasThemeSpecifics(local_theme_specifics)));
theme_sync_service()->StopSyncing(syncer::THEMES);
EXPECT_TRUE(theme_service()->UsingDefaultTheme());
}
INSTANTIATE_TEST_SUITE_P(
,
ThemeLocalDataBatchUploaderTest,
testing::Values(
theme_service::test::CreateThemeSpecificsWithExtensionTheme(
kCustomThemeId,
kCustomThemeName,
kCustomThemeUrl),
theme_service::test::CreateThemeSpecificsWithAutogeneratedColorTheme(),
theme_service::test::CreateThemeSpecificsWithColorTheme(),
theme_service::test::CreateThemeSpecificsWithGrayscaleTheme(),
theme_service::test::CreateThemeSpecificsWithCustomNtpBackground(
kTestUrl)));
using ThemeLocalDataBatchUploaderDeathTest = ThemeLocalDataBatchUploaderTest;
TEST_F(ThemeLocalDataBatchUploaderDeathTest,
TriggerLocalDataMigrationForItemsWithMoreThanOneItem) {
const std::string kExpectedErrorMessageHint =
"Only one local theme can exist";
// Local user color theme.
theme_service()->SetUserColorAndBrowserColorVariant(
SK_ColorBLUE, ui::mojom::BrowserColorVariant::kTonalSpot);
ASSERT_EQ(theme_service()->GetThemeID(), ThemeService::kUserColorThemeID);
const sync_pb::ThemeSpecifics local_theme_specifics =
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting();
const sync_pb::ThemeSpecifics remote_theme_specifics =
theme_service::test::CreateThemeSpecificsWithAutogeneratedColorTheme();
StartSyncing(remote_theme_specifics);
EXPECT_DEATH(
TriggerLocalDataMigrationForItems(
/*items=*/{ThemeLocalDataBatchUploader::kThemesLocalDataItemModelId,
"another-theme"}),
"");
}
TEST_F(ThemeLocalDataBatchUploaderDeathTest,
TriggerLocalDataMigrationForItemsWithInvalidItemIdType) {
const std::string kExpectedErrorMessageHint = "Invalid item ID type";
const syncer::LocalDataItemModel::DataId illegal_item = 1;
// Local user color theme.
theme_service()->SetUserColorAndBrowserColorVariant(
SK_ColorBLUE, ui::mojom::BrowserColorVariant::kTonalSpot);
ASSERT_EQ(theme_service()->GetThemeID(), ThemeService::kUserColorThemeID);
const sync_pb::ThemeSpecifics local_theme_specifics =
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting();
const sync_pb::ThemeSpecifics remote_theme_specifics =
theme_service::test::CreateThemeSpecificsWithAutogeneratedColorTheme();
StartSyncing(remote_theme_specifics);
EXPECT_DEATH(TriggerLocalDataMigrationForItems(/*items=*/{illegal_item}), "");
}
TEST_F(ThemeLocalDataBatchUploaderDeathTest,
TriggerLocalDataMigrationForItemsWithInvalidItemId) {
const std::string kExpectedErrorMessageHint = "Invalid item ID";
const syncer::LocalDataItemModel::DataId illegal_item = "another-theme";
// Local user color theme.
theme_service()->SetUserColorAndBrowserColorVariant(
SK_ColorBLUE, ui::mojom::BrowserColorVariant::kTonalSpot);
ASSERT_EQ(theme_service()->GetThemeID(), ThemeService::kUserColorThemeID);
const sync_pb::ThemeSpecifics local_theme_specifics =
theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting();
const sync_pb::ThemeSpecifics remote_theme_specifics =
theme_service::test::CreateThemeSpecificsWithAutogeneratedColorTheme();
StartSyncing(remote_theme_specifics);
EXPECT_DEATH(TriggerLocalDataMigrationForItems(/*items=*/{illegal_item}), "");
}
} // namespace