blob: af0009ec895bcad017f48616b9f1f484c75caa4e [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/tpcd/experiment/experiment_manager_impl.h"
#include "base/strings/to_string.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/browser/tpcd/experiment/tpcd_experiment_features.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/prefs/pref_service.h"
#include "components/privacy_sandbox/tpcd_pref_names.h"
#include "components/privacy_sandbox/tpcd_utils.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/ash/components/browser_context_helper/browser_context_types.h"
#endif // BUILDFLAG(IS_CHROMEOS)
namespace tpcd::experiment {
namespace {
class TestingExperimentManagerImpl : public ExperimentManagerImpl {
public:
bool CanRegisterSyntheticTrialForTesting() const {
return CanRegisterSyntheticTrial();
}
};
using ::testing::InSequence;
using ::testing::Optional;
using Checkpoint = ::testing::MockFunction<void(int step)>;
} // namespace
class ExperimentManagerImplTestBase : public testing::Test {
public:
PrefService& prefs() {
return *TestingBrowserProcess::GetGlobal()->local_state();
}
void SetUp() override {
ASSERT_TRUE(profile_manager_.SetUp());
prefs().SetInteger(
prefs::kTPCDExperimentClientState,
static_cast<int>(utils::ExperimentState::kUnknownEligibility));
delay_time_ = kDecisionDelayTime.Get();
}
protected:
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
TestingProfileManager profile_manager_{TestingBrowserProcess::GetGlobal()};
base::MockCallback<ExperimentManager::EligibilityDecisionCallback>
mock_callback_;
base::TimeDelta delay_time_;
};
TEST_F(ExperimentManagerImplTestBase, Version) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeatureWithParameters(
features::kCookieDeprecationFacilitatedTesting, {{"version", "2"}});
const struct {
const char* desc;
std::optional<int> initial_version;
std::optional<utils::ExperimentState> initial_state;
int expected_version;
utils::ExperimentState expected_state;
} kTestCases[] = {
{
.desc = "first-run",
.expected_version = 2,
.expected_state = utils::ExperimentState::kUnknownEligibility,
},
{
.desc = "new-version",
.initial_version = 1,
.initial_state = utils::ExperimentState::kEligible,
.expected_version = 2,
.expected_state = utils::ExperimentState::kUnknownEligibility,
},
{
.desc = "same-version",
.initial_version = 2,
.initial_state = utils::ExperimentState::kEligible,
.expected_version = 2,
.expected_state = utils::ExperimentState::kEligible,
},
{
.desc = "old-version",
.initial_version = 3,
.initial_state = utils::ExperimentState::kIneligible,
.expected_version = 2,
.expected_state = utils::ExperimentState::kUnknownEligibility,
},
};
for (const auto& test_case : kTestCases) {
SCOPED_TRACE(test_case.desc);
if (test_case.initial_version.has_value()) {
prefs().SetInteger(prefs::kTPCDExperimentClientStateVersion,
*test_case.initial_version);
} else {
prefs().ClearPref(prefs::kTPCDExperimentClientStateVersion);
}
if (test_case.initial_state.has_value()) {
prefs().SetInteger(prefs::kTPCDExperimentClientState,
static_cast<int>(*test_case.initial_state));
} else {
prefs().ClearPref(prefs::kTPCDExperimentClientState);
}
TestingExperimentManagerImpl experiment_manager;
EXPECT_EQ(prefs().GetInteger(prefs::kTPCDExperimentClientStateVersion),
test_case.expected_version);
EXPECT_EQ(prefs().GetInteger(prefs::kTPCDExperimentClientState),
static_cast<int>(test_case.expected_state));
EXPECT_EQ(experiment_manager.DidVersionChange(),
test_case.initial_version != 2);
}
}
TEST_F(ExperimentManagerImplTestBase, ForceEligibleForTesting) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeatureWithParameters(
features::kCookieDeprecationFacilitatedTesting,
{{"force_eligible", "true"}});
EXPECT_CALL(mock_callback_, Run(true)).Times(1);
TestingExperimentManagerImpl test_manager;
EXPECT_THAT(test_manager.IsClientEligible(), testing::Optional(true));
// This should do nothing.
test_manager.SetClientEligibility(/*is_eligible=*/false,
mock_callback_.Get());
task_environment_.FastForwardBy(delay_time_);
EXPECT_THAT(test_manager.IsClientEligible(), testing::Optional(true));
}
class ExperimentManagerImplTest : public ExperimentManagerImplTestBase {
public:
ExperimentManagerImplTest() {
feature_list_.InitAndEnableFeature(
features::kCookieDeprecationFacilitatedTesting);
}
private:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(ExperimentManagerImplTest,
ExperimentManager_OneEligibleProfileCallSetsPrefEligible) {
TestingExperimentManagerImpl test_manager;
test_manager.SetClientEligibility(/*is_eligible=*/true, mock_callback_.Get());
EXPECT_CALL(mock_callback_, Run(true)).Times(1);
task_environment_.FastForwardBy(delay_time_);
EXPECT_EQ(prefs().GetInteger(prefs::kTPCDExperimentClientState),
static_cast<int>(utils::ExperimentState::kEligible));
}
TEST_F(
ExperimentManagerImplTest,
ExperimentManager_OneIneligibleProfileCallSetsPrefIneligibleAndReturnsEarly) {
TestingExperimentManagerImpl test_manager;
test_manager.SetClientEligibility(/*is_eligible=*/false,
mock_callback_.Get());
EXPECT_CALL(mock_callback_, Run(false)).Times(1);
task_environment_.FastForwardBy(delay_time_);
EXPECT_EQ(prefs().GetInteger(prefs::kTPCDExperimentClientState),
static_cast<int>(utils::ExperimentState::kIneligible));
}
TEST_F(
ExperimentManagerImplTest,
ExperimentManager_OneEligibleOneIneligibleProfileCallSetsPrefIneligible) {
TestingExperimentManagerImpl test_manager;
test_manager.SetClientEligibility(/*is_eligible=*/true, mock_callback_.Get());
test_manager.SetClientEligibility(/*is_eligible=*/false,
mock_callback_.Get());
EXPECT_CALL(mock_callback_, Run(false)).Times(2);
task_environment_.FastForwardBy(delay_time_);
EXPECT_EQ(prefs().GetInteger(prefs::kTPCDExperimentClientState),
static_cast<int>(utils::ExperimentState::kIneligible));
}
TEST_F(
ExperimentManagerImplTest,
ExperimentManager_OneIneligibleOneEligibleProfileCallSetsPrefIneligible) {
TestingExperimentManagerImpl test_manager;
test_manager.SetClientEligibility(/*is_eligible=*/false,
mock_callback_.Get());
test_manager.SetClientEligibility(/*is_eligible=*/true, mock_callback_.Get());
EXPECT_CALL(mock_callback_, Run(false)).Times(2);
task_environment_.FastForwardBy(delay_time_);
EXPECT_EQ(prefs().GetInteger(prefs::kTPCDExperimentClientState),
static_cast<int>(utils::ExperimentState::kIneligible));
}
TEST_F(ExperimentManagerImplTest,
ExperimentManager_SetIneligibleAfterDecisionCallDoesNothing) {
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(mock_callback_, Run).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(mock_callback_, Run(true)).Times(1);
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(mock_callback_, Run(true)).Times(1);
}
TestingExperimentManagerImpl test_manager;
test_manager.SetClientEligibility(/*is_eligible=*/true, mock_callback_.Get());
checkpoint.Call(1);
task_environment_.FastForwardBy(delay_time_);
checkpoint.Call(2);
test_manager.SetClientEligibility(/*is_eligible=*/false,
mock_callback_.Get());
EXPECT_EQ(prefs().GetInteger(prefs::kTPCDExperimentClientState),
static_cast<int>(utils::ExperimentState::kEligible));
}
TEST_F(ExperimentManagerImplTest,
ExperimentManager_SetEligibleAfterDecisionCallDoesNothing) {
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(mock_callback_, Run).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(mock_callback_, Run(false)).Times(1);
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(mock_callback_, Run(false)).Times(1);
}
TestingExperimentManagerImpl test_manager;
test_manager.SetClientEligibility(/*is_eligible=*/false,
mock_callback_.Get());
checkpoint.Call(1);
task_environment_.FastForwardBy(delay_time_);
checkpoint.Call(2);
test_manager.SetClientEligibility(/*is_eligible=*/true, mock_callback_.Get());
EXPECT_EQ(prefs().GetInteger(prefs::kTPCDExperimentClientState),
static_cast<int>(utils::ExperimentState::kIneligible));
}
TEST_F(ExperimentManagerImplTest,
ExperimentManager_PrefUnsetBeforeFinalDecisionIsMade) {
TestingExperimentManagerImpl test_manager;
test_manager.SetClientEligibility(/*is_eligible=*/false,
mock_callback_.Get());
// No callbacks run before the delay_time_ time completes.
EXPECT_CALL(mock_callback_, Run).Times(0);
// fastforward less than the full delay_time_.
task_environment_.FastForwardBy(delay_time_ - base::Milliseconds(1));
// pref value should still be "kUnknownEligibility" before delay_time
// completes
EXPECT_EQ(prefs().GetInteger(prefs::kTPCDExperimentClientState),
static_cast<int>(utils::ExperimentState::kUnknownEligibility));
}
TEST_F(ExperimentManagerImplTest, PrefIneligibleReturnsEarly) {
prefs().SetInteger(prefs::kTPCDExperimentClientState,
static_cast<int>(utils::ExperimentState::kIneligible));
EXPECT_CALL(mock_callback_, Run(false)).Times(1);
TestingExperimentManagerImpl().SetClientEligibility(/*is_eligible=*/true,
mock_callback_.Get());
EXPECT_EQ(prefs().GetInteger(prefs::kTPCDExperimentClientState),
static_cast<int>(utils::ExperimentState::kIneligible));
}
TEST_F(ExperimentManagerImplTest, IsClientEligible_PrefIsEligibleReturnsTrue) {
prefs().SetInteger(prefs::kTPCDExperimentClientState,
static_cast<int>(utils::ExperimentState::kEligible));
EXPECT_THAT(TestingExperimentManagerImpl().IsClientEligible(),
Optional(true));
}
TEST_F(ExperimentManagerImplTest,
IsClientEligible_PrefIsIneligibleReturnsFalse) {
prefs().SetInteger(prefs::kTPCDExperimentClientState,
static_cast<int>(utils::ExperimentState::kIneligible));
EXPECT_THAT(TestingExperimentManagerImpl().IsClientEligible(),
Optional(false));
}
TEST_F(ExperimentManagerImplTest, IsClientEligible_PrefIsUnknownReturnsEmpty) {
prefs().SetInteger(
prefs::kTPCDExperimentClientState,
static_cast<int>(utils::ExperimentState::kUnknownEligibility));
EXPECT_EQ(TestingExperimentManagerImpl().IsClientEligible(), std::nullopt);
}
#if BUILDFLAG(IS_CHROMEOS)
TEST_F(ExperimentManagerImplTest, AshInternalProfile_NotCreated) {
auto* internal_profile =
profile_manager_.CreateTestingProfile(ash::kSigninBrowserContextBaseName);
EXPECT_FALSE(ExperimentManagerImpl::GetForProfile(internal_profile));
}
#endif // BUILDFLAG(IS_CHROMEOS)
// The parameter indicates whether to disable 3pcs.
class ExperimentManagerImplSyntheticTrialTest
: public ExperimentManagerImplTestBase,
public testing::WithParamInterface<bool> {};
TEST_P(ExperimentManagerImplSyntheticTrialTest, ProfileOnboardedSetsPref) {
const bool disable_3p_cookies = GetParam();
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeatureWithParameters(
features::kCookieDeprecationFacilitatedTesting,
{{kDisable3PCookiesName, base::ToString(disable_3p_cookies)},
{kNeedOnboardingForSyntheticTrialName, "true"},
{kEnableSilentOnboardingName, "true"}});
TestingExperimentManagerImpl test_manager;
test_manager.SetClientEligibility(/*is_eligible=*/true, mock_callback_.Get());
EXPECT_CALL(mock_callback_, Run(true)).Times(1);
task_environment_.FastForwardBy(delay_time_);
EXPECT_EQ(prefs().GetInteger(prefs::kTPCDExperimentClientState),
static_cast<int>(utils::ExperimentState::kEligible));
test_manager.NotifyProfileTrackingProtectionOnboarded();
EXPECT_EQ(prefs().GetInteger(prefs::kTPCDExperimentClientState),
static_cast<int>(utils::ExperimentState::kOnboarded));
}
TEST_P(ExperimentManagerImplSyntheticTrialTest, CanRegister) {
const bool disable_3p_cookies = GetParam();
const struct {
utils::ExperimentState experiment_state;
bool expected;
bool need_onboarding = false;
bool enable_silent_onboarding = false;
} kTestCases[] = {
{
.experiment_state = utils::ExperimentState::kUnknownEligibility,
.expected = false,
},
{
.experiment_state = utils::ExperimentState::kIneligible,
.expected = true,
},
{
.experiment_state = utils::ExperimentState::kEligible,
.expected = true,
.need_onboarding = false,
},
{
.experiment_state = utils::ExperimentState::kEligible,
.expected = !disable_3p_cookies,
.need_onboarding = true,
},
{
.experiment_state = utils::ExperimentState::kEligible,
.expected = false,
.need_onboarding = true,
.enable_silent_onboarding = true,
},
{
.experiment_state = utils::ExperimentState::kOnboarded,
.expected = true,
},
};
for (const auto& test_case : kTestCases) {
base::test::ScopedFeatureList scope_feature_list;
scope_feature_list.InitAndEnableFeatureWithParameters(
features::kCookieDeprecationFacilitatedTesting,
{{kDisable3PCookiesName, base::ToString(disable_3p_cookies)},
{kNeedOnboardingForSyntheticTrialName,
base::ToString(test_case.need_onboarding)},
{kEnableSilentOnboardingName,
base::ToString(test_case.enable_silent_onboarding)}});
prefs().SetInteger(prefs::kTPCDExperimentClientState,
static_cast<int>(test_case.experiment_state));
EXPECT_EQ(
TestingExperimentManagerImpl().CanRegisterSyntheticTrialForTesting(),
test_case.expected);
}
}
INSTANTIATE_TEST_SUITE_P(All,
ExperimentManagerImplSyntheticTrialTest,
testing::Bool());
} // namespace tpcd::experiment