blob: d5cf83b0747d40e02db75c1402b4f5536d524f58 [file] [log] [blame]
// Copyright 2018 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/unified_consent/unified_consent_service.h"
#include <map>
#include <memory>
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/sync/base/features.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/service/sync_prefs.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 "testing/gtest/include/gtest/gtest.h"
namespace unified_consent {
namespace {
class TestSyncService : public syncer::TestSyncService {
public:
TestSyncService() = default;
TestSyncService(const TestSyncService&) = delete;
TestSyncService& operator=(const TestSyncService&) = delete;
void AddObserver(syncer::SyncServiceObserver* observer) override {
observer_ = observer;
}
void FireStateChanged() {
if (observer_)
observer_->OnStateChanged(this);
}
private:
raw_ptr<syncer::SyncServiceObserver, DanglingUntriaged> observer_ = nullptr;
};
} // namespace
class UnifiedConsentServiceTest : public testing::Test {
public:
using SyncState = UnifiedConsentService::SyncState;
static bool ShouldEnableUrlKeyedAnonymizedDataCollection(
SyncState old_sync_state,
SyncState new_sync_state) {
return UnifiedConsentService::ShouldEnableUrlKeyedAnonymizedDataCollection(
old_sync_state, new_sync_state);
}
static bool ShouldDisableUrlKeyedAnonymizedDataCollection(
SyncState old_sync_state,
SyncState new_sync_state) {
return UnifiedConsentService::ShouldDisableUrlKeyedAnonymizedDataCollection(
old_sync_state, new_sync_state);
}
static const char* SyncStateToString(SyncState state) {
switch (state) {
case SyncState::kSignedOut:
return "kSignedOut";
case SyncState::kSignedInWithoutHistory:
return "kSignedInWithoutHistory";
case SyncState::kSignedInWithHistoryWaitingForPassphrase:
return "kSignedInWithHistoryWaitingForPassphrase";
case SyncState::kSignedInWithHistoryAndExplicitPassphrase:
return "kSignedInWithHistoryAndExplicitPassphrase";
case SyncState::kSignedInWithHistoryAndNoPassphrase:
return "kSignedInWithHistoryAndNoPassphrase";
}
NOTREACHED_NORETURN();
}
UnifiedConsentServiceTest() {
UnifiedConsentService::RegisterPrefs(pref_service_.registry());
syncer::SyncPrefs::RegisterProfilePrefs(pref_service_.registry());
}
UnifiedConsentServiceTest(const UnifiedConsentServiceTest&) = delete;
UnifiedConsentServiceTest& operator=(const UnifiedConsentServiceTest&) =
delete;
~UnifiedConsentServiceTest() override {
if (consent_service_)
consent_service_->Shutdown();
}
void CreateConsentService() {
consent_service_ = std::make_unique<UnifiedConsentService>(
&pref_service_, identity_test_environment_.identity_manager(),
&sync_service_, std::vector<std::string>());
sync_service_.FireStateChanged();
// Run until idle so the migration can finish.
base::RunLoop().RunUntilIdle();
}
void ResetConsentService() {
if (consent_service_) {
consent_service_->Shutdown();
consent_service_.reset();
}
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
unified_consent::MigrationState GetMigrationState() {
int migration_state_int =
pref_service_.GetInteger(prefs::kUnifiedConsentMigrationState);
return static_cast<unified_consent::MigrationState>(migration_state_int);
}
#endif
protected:
base::test::SingleThreadTaskEnvironment task_environment_;
sync_preferences::TestingPrefServiceSyncable pref_service_;
signin::IdentityTestEnvironment identity_test_environment_;
TestSyncService sync_service_;
std::unique_ptr<UnifiedConsentService> consent_service_;
};
TEST_F(UnifiedConsentServiceTest, DefaultValuesWhenSignedOut) {
CreateConsentService();
EXPECT_FALSE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
}
TEST_F(UnifiedConsentServiceTest, EnableUrlKeyedAnonymizedDataCollection) {
CreateConsentService();
identity_test_environment_.SetPrimaryAccount("[email protected]",
signin::ConsentLevel::kSync);
EXPECT_FALSE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
// Enable services and check expectations.
consent_service_->SetUrlKeyedAnonymizedDataCollectionEnabled(true);
EXPECT_TRUE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
}
// Tests that in all cases, initializing the UnifiedConsentService does not
// affect the UrlKeyedAnonymizedDataCollectionEnabled state.
TEST_F(UnifiedConsentServiceTest,
ReplaceSync_InitializeNoChangeToUrlKeyedAnonymizedDataCollection) {
base::test::ScopedFeatureList scoped_feature_list(
syncer::kReplaceSyncPromosWithSignInPromos);
sync_service_.SetHasSyncConsent(true);
CreateConsentService();
EXPECT_FALSE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
ResetConsentService();
identity_test_environment_.SetPrimaryAccount("[email protected]",
signin::ConsentLevel::kSignin);
CreateConsentService();
EXPECT_FALSE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
ResetConsentService();
sync_service_.GetUserSettings()->SetSelectedType(
syncer::UserSelectableType::kHistory, true);
CreateConsentService();
EXPECT_FALSE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
ResetConsentService();
identity_test_environment_.SetPrimaryAccount("[email protected]",
signin::ConsentLevel::kSync);
CreateConsentService();
EXPECT_FALSE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
}
// Tests that `kUrlKeyedAnonymizedDataCollectionEnabled` does not change for
// sync users when history sync opt-in state changes.
TEST_F(UnifiedConsentServiceTest, ReplaceSync_HistorySyncIgnoredForSyncUsers) {
identity_test_environment_.SetPrimaryAccount("[email protected]",
signin::ConsentLevel::kSync);
CreateConsentService();
consent_service_->SetUrlKeyedAnonymizedDataCollectionEnabled(true);
ASSERT_TRUE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
ResetConsentService();
base::test::ScopedFeatureList scoped_feature_list(
syncer::kReplaceSyncPromosWithSignInPromos);
sync_service_.SetHasSyncConsent(true);
CreateConsentService();
ASSERT_TRUE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
sync_service_.GetUserSettings()->SetSelectedType(
syncer::UserSelectableType::kHistory, false);
sync_service_.FireStateChanged();
EXPECT_TRUE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
}
#if !BUILDFLAG(IS_CHROMEOS_ASH)
// Tests that kUrlKeyedAnonymizedDataCollectionEnabled is enabled after
// syncing user signs out, then in again and enabled history sync opt-in.
TEST_F(UnifiedConsentServiceTest,
ReplaceSync_SyncUserMovesToSigninWithHistorySyncEnabled) {
identity_test_environment_.SetPrimaryAccount("[email protected]",
signin::ConsentLevel::kSync);
CreateConsentService();
consent_service_->SetUrlKeyedAnonymizedDataCollectionEnabled(true);
ASSERT_TRUE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
ResetConsentService();
// Clearing the primary account and resetting history sync should disable
// `kUrlKeyedAnonymizedDataCollectionEnabled`.
base::test::ScopedFeatureList scoped_feature_list(
syncer::kReplaceSyncPromosWithSignInPromos);
CreateConsentService();
ASSERT_TRUE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
identity_test_environment_.ClearPrimaryAccount();
sync_service_.SetHasSyncConsent(false);
sync_service_.GetUserSettings()->SetSelectedType(
syncer::UserSelectableType::kHistory, false);
sync_service_.FireStateChanged();
EXPECT_FALSE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
// Enabling history sync should enable
// `kUrlKeyedAnonymizedDataCollectionEnabled`.
identity_test_environment_.SetPrimaryAccount("[email protected]",
signin::ConsentLevel::kSignin);
sync_service_.SetHasSyncConsent(false);
sync_service_.GetUserSettings()->SetSelectedType(
syncer::UserSelectableType::kHistory, true);
sync_service_.FireStateChanged();
EXPECT_TRUE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
// Disabling history sync should disable
// `kUrlKeyedAnonymizedDataCollectionEnabled`.
sync_service_.GetUserSettings()->SetSelectedType(
syncer::UserSelectableType::kHistory, false);
sync_service_.FireStateChanged();
EXPECT_FALSE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
}
#endif // !BUILDFLAG(IS_CHROMEOS_ASH)
// Tests that any change to history sync opt-in, is reflected in the state
// of `kUrlKeyedAnonymizedDataCollectionEnabled`.
TEST_F(UnifiedConsentServiceTest,
ReplaceSync_HistorySyncEnablesUrlKeyedAnonymizedDataCollection) {
base::test::ScopedFeatureList scoped_feature_list(
syncer::kReplaceSyncPromosWithSignInPromos);
sync_service_.SetHasSyncConsent(false);
identity_test_environment_.SetPrimaryAccount("[email protected]",
signin::ConsentLevel::kSignin);
sync_service_.GetUserSettings()->SetSelectedType(
syncer::UserSelectableType::kHistory, false);
CreateConsentService();
EXPECT_FALSE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
sync_service_.GetUserSettings()->SetSelectedType(
syncer::UserSelectableType::kHistory, true);
sync_service_.FireStateChanged();
EXPECT_TRUE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
sync_service_.GetUserSettings()->SetSelectedType(
syncer::UserSelectableType::kHistory, false);
sync_service_.FireStateChanged();
EXPECT_FALSE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
}
// This low-level unit test explicitly checks all state transitions. Most
// practical cases are (also) covered by browser tests, see
// UnifiedConsentSyncToSigninBrowserTest.
TEST_F(UnifiedConsentServiceTest, ReplaceSync_StateTransitions) {
base::test::ScopedFeatureList scoped_feature_list(
syncer::kReplaceSyncPromosWithSignInPromos);
enum Outcome {
kNoChange,
kEnable,
kDisable,
};
struct TestCase {
const char* description;
SyncState old_state;
SyncState new_state;
Outcome expected_outcome;
} test_cases[] = {
// From kSignedOut:
{"No-op", SyncState::kSignedOut, SyncState::kSignedOut, kNoChange},
{"Sign-in without history", SyncState::kSignedOut,
SyncState::kSignedInWithoutHistory, kNoChange},
{"Sign-in with history", SyncState::kSignedOut,
SyncState::kSignedInWithHistoryWaitingForPassphrase, kEnable},
{"Sign-in with passphrase", SyncState::kSignedOut,
SyncState::kSignedInWithHistoryAndExplicitPassphrase, kNoChange},
{"Sign-in with history", SyncState::kSignedOut,
SyncState::kSignedInWithHistoryAndNoPassphrase, kEnable},
// From kSignedInWithoutHistory:
{"Sign-out without history", SyncState::kSignedInWithoutHistory,
SyncState::kSignedOut, kNoChange},
{"No-op", SyncState::kSignedInWithoutHistory,
SyncState::kSignedInWithoutHistory, kNoChange},
{"History opt-in", SyncState::kSignedInWithoutHistory,
SyncState::kSignedInWithHistoryWaitingForPassphrase, kEnable},
{"History opt-in but also passphrase", SyncState::kSignedInWithoutHistory,
SyncState::kSignedInWithHistoryAndExplicitPassphrase, kNoChange},
{"History opt-in", SyncState::kSignedInWithoutHistory,
SyncState::kSignedInWithHistoryAndNoPassphrase, kEnable},
// From kSignedInWithHistoryWaitingForPassphrase:
{"Sign-out with history",
SyncState::kSignedInWithHistoryWaitingForPassphrase,
SyncState::kSignedOut, kDisable},
{"History opt-out", SyncState::kSignedInWithHistoryWaitingForPassphrase,
SyncState::kSignedInWithoutHistory, kDisable},
{"No-op", SyncState::kSignedInWithHistoryWaitingForPassphrase,
SyncState::kSignedInWithHistoryWaitingForPassphrase, kNoChange},
{"Explicit passphrase determined",
SyncState::kSignedInWithHistoryWaitingForPassphrase,
SyncState::kSignedInWithHistoryAndExplicitPassphrase, kDisable},
{"No passphrase determined",
SyncState::kSignedInWithHistoryWaitingForPassphrase,
SyncState::kSignedInWithHistoryAndNoPassphrase, kNoChange},
// From kSignedInWithHistoryAndExplicitPassphrase:
{"Sign-out with passphrase",
SyncState::kSignedInWithHistoryAndExplicitPassphrase,
SyncState::kSignedOut, kNoChange},
{"History opt-out with passphrase",
SyncState::kSignedInWithHistoryAndExplicitPassphrase,
SyncState::kSignedInWithoutHistory, kNoChange},
{"Passphrase became unknown (e.g. dashboard reset)",
SyncState::kSignedInWithHistoryAndExplicitPassphrase,
SyncState::kSignedInWithHistoryWaitingForPassphrase, kEnable},
{"No-op", SyncState::kSignedInWithHistoryAndExplicitPassphrase,
SyncState::kSignedInWithHistoryAndExplicitPassphrase, kNoChange},
{"Passphrase turned off (e.g. dashboard reset)",
SyncState::kSignedInWithHistoryAndExplicitPassphrase,
SyncState::kSignedInWithHistoryAndNoPassphrase, kEnable},
// From kSignedInWithHistoryAndNoPassphrase:
{"Sign-out with history", SyncState::kSignedInWithHistoryAndNoPassphrase,
SyncState::kSignedOut, kDisable},
{"History opt-out", SyncState::kSignedInWithHistoryAndNoPassphrase,
SyncState::kSignedInWithoutHistory, kDisable},
{"Passphrase became unknown (e.g. dashboard reset)",
SyncState::kSignedInWithHistoryAndNoPassphrase,
SyncState::kSignedInWithHistoryWaitingForPassphrase, kNoChange},
{"Passphrase turned on", SyncState::kSignedInWithHistoryAndNoPassphrase,
SyncState::kSignedInWithHistoryAndExplicitPassphrase, kDisable},
{"No-op", SyncState::kSignedInWithHistoryAndNoPassphrase,
SyncState::kSignedInWithHistoryAndNoPassphrase, kNoChange},
};
for (const TestCase& test_case : test_cases) {
SCOPED_TRACE(base::StringPrintf(
"Test case: %s -> %s, %s", SyncStateToString(test_case.old_state),
SyncStateToString(test_case.new_state), test_case.description));
EXPECT_EQ(ShouldEnableUrlKeyedAnonymizedDataCollection(test_case.old_state,
test_case.new_state),
test_case.expected_outcome == kEnable);
EXPECT_EQ(ShouldDisableUrlKeyedAnonymizedDataCollection(
test_case.old_state, test_case.new_state),
test_case.expected_outcome == kDisable);
}
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(UnifiedConsentServiceTest, Migration_UpdateSettings) {
// Create user that syncs history and has no custom passphrase.
identity_test_environment_.SetPrimaryAccount("[email protected]",
signin::ConsentLevel::kSync);
sync_service_.GetUserSettings()->SetSelectedTypes(
false, {syncer::UserSelectableType::kHistory});
EXPECT_TRUE(sync_service_.IsSyncFeatureActive());
// Url keyed data collection is off before the migration.
EXPECT_FALSE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
CreateConsentService();
EXPECT_EQ(GetMigrationState(), unified_consent::MigrationState::kCompleted);
// During the migration Url keyed data collection is enabled.
EXPECT_TRUE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
}
TEST_F(UnifiedConsentServiceTest, Migration_NotSignedIn) {
base::HistogramTester histogram_tester;
CreateConsentService();
// The user is signed out, so the migration is completed after the
// creation of the consent service.
EXPECT_EQ(GetMigrationState(), unified_consent::MigrationState::kCompleted);
}
#else
TEST_F(UnifiedConsentServiceTest, ClearPrimaryAccountDisablesSomeServices) {
base::HistogramTester histogram_tester;
CreateConsentService();
identity_test_environment_.SetPrimaryAccount("[email protected]",
signin::ConsentLevel::kSync);
// Precondition: Enable unified consent.
consent_service_->SetUrlKeyedAnonymizedDataCollectionEnabled(true);
EXPECT_TRUE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
// Clearing primary account revokes unfied consent and a couple of other
// non-personalized services.
identity_test_environment_.ClearPrimaryAccount();
EXPECT_FALSE(pref_service_.GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
} // namespace unified_consent