blob: 8dbdc1ef8cfefe7dbf443590e6b82fef13bc4ed0 [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/ui/signin/dice_migration_service.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_mock_time_task_runner.h"
#include "chrome/browser/enterprise/util/managed_browser_utils.h"
#include "chrome/browser/profiles/profile_test_util.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/sync/test/integration/preferences_helper.h"
#include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/ui/signin/dice_migration_service_factory.h"
#include "chrome/browser/ui/toasts/api/toast_id.h"
#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
#include "chrome/browser/ui/views/profiles/avatar_toolbar_button.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_test_helper.h"
#include "chrome/browser/web_applications/test/os_integration_test_override_impl.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/account_managed_status_finder.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/signin/public/identity_manager/identity_utils.h"
#include "components/signin/public/identity_manager/primary_account_mutator.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_launcher.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/window/dialog_client_view.h"
namespace {
constexpr char kTestEmail[] = "test@gmail.com";
constexpr char kEnterpriseTestEmail[] = "test@google.com";
constexpr char kIndeterminableTestEmail[] = "test@indeterminable.com";
constexpr char kDialogCloseReasonHistogram[] =
"Signin.DiceMigrationDialog.CloseReason";
constexpr char kDialogTimerStartedHistogram[] =
"Signin.DiceMigrationDialog.TimerStarted";
constexpr char kDialogPreviouslyShownCountHistogram[] =
"Signin.DiceMigrationDialog.PreviouslyShownCount";
constexpr char kDialogDaysSinceLastShownHistogram[] =
"Signin.DiceMigrationDialog.DaysSinceLastShown";
constexpr char kDialogShownHistogram[] = "Signin.DiceMigrationDialog.Shown";
constexpr char kAccountManagedStatusHistogram[] =
"Signin.DiceMigrationDialog.AccountManagedStatus";
constexpr char kUserMigratedHistogram[] = "Signin.DiceMigrationDialog.Migrated";
constexpr char kDialogNotShownReasonHistogram[] =
"Signin.DiceMigrationDialog.NotShownReason";
constexpr char kRestoredFromBackupHistogram[] =
"Signin.DiceMigration.RestoredFromBackup";
constexpr char kForceMigratedHistogram[] = "Signin.DiceMigration.ForceMigrated";
constexpr char kSignoutReasonHistogram[] = "Signin.SignOut.Completed";
constexpr char kToastTriggerToShowHistogram[] = "Toast.TriggeredToShow";
constexpr char kForcedMigrationAccountManagedHistogram[] =
"Signin.ForcedDiceMigration.HasAcceptedAccountManagement";
// Utility macro to implicitly sign in the user in a PRE test.
// NOTE: `test_suite` must be a subclass of `DiceMigrationServiceBrowserTest`.
#define DICE_MIGRATION_TEST_F(test_suite, test_name) \
IN_PROC_BROWSER_TEST_F(test_suite, PRE_##test_name) { \
ImplicitlySignIn(kTestEmail); \
} \
IN_PROC_BROWSER_TEST_F(test_suite, test_name)
bool ContainsViewWithId(const views::View* view, ui::ElementIdentifier id) {
for (const views::View* child : view->children()) {
if (child->GetVisible() &&
(child->GetProperty(views::kElementIdentifierKey) == id ||
// Recurse into the child.
ContainsViewWithId(child, id))) {
return true;
}
}
return false;
}
class DiceMigrationServiceBrowserTest : public InProcessBrowserTest {
public:
DiceMigrationServiceBrowserTest() {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{switches::kOfferMigrationToDiceUsers},
// DICe migration dialog is not shown when forced migration flag is
// enabled.
/*disabled_features=*/{switches::kForcedDiceMigration});
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
disclaimer_service_resetter_ =
enterprise_util::DisableAutomaticManagementDisclaimerUntilReset(
GetProfile());
}
void ImplicitlySignIn(const std::string& email) {
AccountInfo account_info = signin::MakeAccountAvailable(
GetIdentityManager(),
signin::AccountAvailabilityOptionsBuilder()
.AsPrimary(signin::ConsentLevel::kSignin)
.WithAccessPoint(signin_metrics::AccessPoint::kWebSignin)
.Build(email));
}
bool IsImplicitlySignedIn() {
return GetIdentityManager()->HasPrimaryAccount(
signin::ConsentLevel::kSignin) &&
signin::IsImplicitBrowserSigninOrExplicitDisabled(
GetIdentityManager(), GetPrefs());
}
bool IsExplicitlySignedIn() {
return GetIdentityManager()->HasPrimaryAccount(
signin::ConsentLevel::kSignin) &&
!signin::IsImplicitBrowserSigninOrExplicitDisabled(
GetIdentityManager(), GetPrefs());
}
void FireDialogTriggerTimer() {
base::OneShotTimer& timer =
GetDiceMigrationService()->GetDialogTriggerTimerForTesting();
ASSERT_TRUE(timer.IsRunning());
timer.FireNow();
}
Profile* GetProfile() { return browser()->profile(); }
PrefService* GetPrefs() { return GetProfile()->GetPrefs(); }
DiceMigrationService* GetDiceMigrationService() {
DiceMigrationService* service =
DiceMigrationServiceFactory::GetForProfileIfExists(GetProfile());
EXPECT_TRUE(service);
return service;
}
signin::IdentityManager* GetIdentityManager() {
return IdentityManagerFactory::GetForProfile(GetProfile());
}
syncer::SyncService* GetSyncService() {
return SyncServiceFactory::GetForProfile(GetProfile());
}
AvatarToolbarButton* GetAvatarToolbarButton() {
return BrowserView::GetBrowserViewForBrowser(browser())
->toolbar_button_provider()
->GetAvatarToolbarButton();
}
protected:
base::test::ScopedFeatureList scoped_feature_list_;
base::ScopedClosureRunner disclaimer_service_resetter_;
base::HistogramTester histogram_tester_;
};
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest, NotSignedIn) {
// The user is not signed in.
ASSERT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
// The timer to trigger the dialog is not started.
EXPECT_FALSE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
EXPECT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(kDialogTimerStartedHistogram, false, 1);
histogram_tester_.ExpectUniqueSample(
kDialogNotShownReasonHistogram,
DiceMigrationService::DialogNotShownReason::kNotEligible, 1);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest, PRE_Syncing) {
signin::MakePrimaryAccountAvailable(GetIdentityManager(), kTestEmail,
signin::ConsentLevel::kSync);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest, Syncing) {
// The user is syncing.
ASSERT_TRUE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSync));
EXPECT_FALSE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
EXPECT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(kDialogTimerStartedHistogram, false, 1);
histogram_tester_.ExpectUniqueSample(
kDialogNotShownReasonHistogram,
DiceMigrationService::DialogNotShownReason::kNotEligible, 1);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest,
PRE_ExplicitlySignedIn) {
signin::MakePrimaryAccountAvailable(GetIdentityManager(), kTestEmail,
signin::ConsentLevel::kSignin);
ASSERT_TRUE(IsExplicitlySignedIn());
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest, ExplicitlySignedIn) {
// The user is explicitly signed in.
ASSERT_TRUE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
ASSERT_FALSE(signin::IsImplicitBrowserSigninOrExplicitDisabled(
GetIdentityManager(), GetPrefs()));
EXPECT_FALSE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
EXPECT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(kDialogTimerStartedHistogram, false, 1);
histogram_tester_.ExpectUniqueSample(
kDialogNotShownReasonHistogram,
DiceMigrationService::DialogNotShownReason::kNotEligible, 1);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest,
PRE_ImplicitlySignedIn) {
ImplicitlySignIn(kTestEmail);
ASSERT_TRUE(IsImplicitlySignedIn());
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest, ImplicitlySignedIn) {
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
EXPECT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
ASSERT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(kDialogTimerStartedHistogram, true, 1);
// Trigger the timer.
FireDialogTriggerTimer();
EXPECT_TRUE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(kDialogShownHistogram, true, 1);
histogram_tester_.ExpectTotalCount(kDialogNotShownReasonHistogram, 0);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
ShouldNotShowDialogIfNotEligibleAnymore) {
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
// Set the explicit sign-in pref to true. This should make the user ineligible
// for the migration, but the timer still runs. This is a test-only scenario
// and should not happen in production.
GetPrefs()->SetBoolean(prefs::kExplicitBrowserSignin, true);
ASSERT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
// Show the migration bubble.
FireDialogTriggerTimer();
// The dialog is not shown.
EXPECT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(kDialogShownHistogram, false, 1);
histogram_tester_.ExpectUniqueSample(
kDialogNotShownReasonHistogram,
DiceMigrationService::DialogNotShownReason::kNotEligible, 1);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest, MigrateUser) {
constexpr syncer::UserSelectableTypeSet new_selected_types = {
syncer::UserSelectableType::kPreferences,
syncer::UserSelectableType::kThemes,
syncer::UserSelectableType::kPasswords,
syncer::UserSelectableType::kAutofill,
};
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
// These types are only enabled upon explicitly signing in.
ASSERT_FALSE(GetSyncService()->GetUserSettings()->GetSelectedTypes().HasAny(
new_selected_types));
// Show migration bubble.
FireDialogTriggerTimer();
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
views::test::WidgetDestroyedWaiter waiter(dialog_widget);
// Simulate clicking on accept button.
dialog_widget->CloseWithReason(
views::Widget::ClosedReason::kAcceptButtonClicked);
waiter.Wait();
EXPECT_TRUE(GetPrefs()->GetBoolean(kDiceMigrationMigrated));
// The explicit sign-in pref is set, this marks the user as explicitly
// signed in.
EXPECT_TRUE(GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
EXPECT_FALSE(IsImplicitlySignedIn());
EXPECT_TRUE(IsExplicitlySignedIn());
// This should set the relevant user selected types.
EXPECT_TRUE(GetSyncService()->GetUserSettings()->GetSelectedTypes().HasAll(
new_selected_types))
<< GetSyncService()->GetUserSettings()->GetSelectedTypes();
histogram_tester_.ExpectUniqueSample(kUserMigratedHistogram, true, 1);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
ShouldNotMigrateUserIfIneligible) {
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
// Show the migration bubble.
FireDialogTriggerTimer();
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
// Set the explicit sign-in pref to true. This should make the user
// ineligible for the migration. The dialog is still shown but it's okay since
// this is a test-only scenario and should not happen in production.
GetPrefs()->SetBoolean(prefs::kExplicitBrowserSignin, true);
views::test::WidgetDestroyedWaiter waiter(dialog_widget);
// Simulate clicking on the accept button.
dialog_widget->CloseWithReason(
views::Widget::ClosedReason::kAcceptButtonClicked);
waiter.Wait();
EXPECT_FALSE(GetPrefs()->GetBoolean(kDiceMigrationMigrated));
// The prefs are not updated.
EXPECT_EQ(GetPrefs()->GetBoolean(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled),
false);
histogram_tester_.ExpectUniqueSample(kUserMigratedHistogram, false, 1);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
IncrementDialogShownCount) {
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
// Set the current dialog shown count to 1.
GetPrefs()->SetInteger(kDiceMigrationDialogShownCount, 1);
// Show the migration bubble.
FireDialogTriggerTimer();
views::Widget* widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(widget);
// The dialog shown count is now incremented.
EXPECT_EQ(GetPrefs()->GetInteger(kDiceMigrationDialogShownCount), 2);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
UpdateDialogLastShownTime) {
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
base::Time time_now = base::Time::Now();
ASSERT_LT(GetPrefs()->GetTime(kDiceMigrationDialogLastShownTime), time_now);
// Not logged since the dialog was never shown before.
histogram_tester_.ExpectTotalCount(kDialogDaysSinceLastShownHistogram, 0);
// Show the migration bubble.
FireDialogTriggerTimer();
ASSERT_TRUE(GetDiceMigrationService()->GetDialogWidgetForTesting());
// The dialog last shown time is now updated.
EXPECT_GE(GetPrefs()->GetTime(kDiceMigrationDialogLastShownTime), time_now);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
DoNotUpdateDialogShownCountAndTimeUponInteraction) {
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
// Set the current dialog shown count to 1.
GetPrefs()->SetInteger(kDiceMigrationDialogShownCount, 1);
base::Time time_now = base::Time::Now();
ASSERT_LT(GetPrefs()->GetTime(kDiceMigrationDialogLastShownTime), time_now);
// Show the migration bubble.
base::OneShotTimer& timer =
GetDiceMigrationService()->GetDialogTriggerTimerForTesting();
ASSERT_TRUE(timer.IsRunning());
timer.FireNow();
views::Widget* widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(widget);
// The dialog shown count is incremented.
ASSERT_EQ(GetPrefs()->GetInteger(kDiceMigrationDialogShownCount), 2);
// The dialog last shown time is updated.
ASSERT_GE(GetPrefs()->GetTime(kDiceMigrationDialogLastShownTime), time_now);
time_now = GetPrefs()->GetTime(kDiceMigrationDialogLastShownTime);
views::test::WidgetDestroyedWaiter waiter(widget);
// Simulate clicking on accept button.
widget->CloseWithReason(views::Widget::ClosedReason::kAcceptButtonClicked);
waiter.Wait();
// The dialog shown count and time are not updated anymore.
EXPECT_EQ(GetPrefs()->GetInteger(kDiceMigrationDialogShownCount), 2);
EXPECT_EQ(GetPrefs()->GetTime(kDiceMigrationDialogLastShownTime), time_now);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest,
PRE_DoNotShowDialogIfShownLessThanWeekAgo) {
ImplicitlySignIn(kTestEmail);
// Set the dialog last shown time to
// (`kOfferMigrationToDiceUsersMinTimeBetweenDialogs` - 1) days ago.
GetPrefs()->SetTime(
kDiceMigrationDialogLastShownTime,
base::Time::Now() -
(switches::kOfferMigrationToDiceUsersMinTimeBetweenDialogs.Get() -
base::Days(1)));
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest,
DoNotShowDialogIfShownLessThanWeekAgo) {
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
EXPECT_FALSE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
histogram_tester_.ExpectUniqueSample(kDialogTimerStartedHistogram, false, 1);
histogram_tester_.ExpectUniqueSample(
kDialogDaysSinceLastShownHistogram,
(switches::kOfferMigrationToDiceUsersMinTimeBetweenDialogs.Get() -
base::Days(1))
.InDays(),
1);
histogram_tester_.ExpectUniqueSample(
kDialogNotShownReasonHistogram,
DiceMigrationService::DialogNotShownReason::
kMinTimeBetweenDialogsNotPassed,
1);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest,
PRE_ShowDialogIfShownMoreThanAWeekAgo) {
ImplicitlySignIn(kTestEmail);
// Set the dialog last shown time to
// (`kOfferMigrationToDiceUsersMinTimeBetweenDialogs` + 1) days ago.
GetPrefs()->SetTime(
kDiceMigrationDialogLastShownTime,
base::Time::Now() -
(switches::kOfferMigrationToDiceUsersMinTimeBetweenDialogs.Get() +
base::Days(1)));
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest,
ShowDialogIfShownMoreThanAWeekAgo) {
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
EXPECT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
histogram_tester_.ExpectUniqueSample(kDialogTimerStartedHistogram, true, 1);
histogram_tester_.ExpectUniqueSample(
kDialogDaysSinceLastShownHistogram,
(switches::kOfferMigrationToDiceUsersMinTimeBetweenDialogs.Get() +
base::Days(1))
.InDays(),
1);
histogram_tester_.ExpectTotalCount(kDialogNotShownReasonHistogram, 0);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest, ConsumerAccount) {
// The account managed status is known.
signin::AccountManagedStatusFinder account_managed_status_finder(
GetIdentityManager(),
GetIdentityManager()->GetPrimaryAccountInfo(
signin::ConsentLevel::kSignin),
base::DoNothing());
ASSERT_EQ(account_managed_status_finder.GetOutcome(),
signin::AccountManagedStatusFinderOutcome::kConsumerGmail);
// Simulate the timer firing.
FireDialogTriggerTimer();
histogram_tester_.ExpectUniqueSample(
kAccountManagedStatusHistogram,
signin::AccountManagedStatusFinderOutcome::kConsumerGmail, 1);
// The dialog is shown.
EXPECT_TRUE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(kDialogShownHistogram, true, 1);
histogram_tester_.ExpectTotalCount(kDialogNotShownReasonHistogram, 0);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest, PRE_EnterpriseAccount) {
// Implicitly sign in with a known enterprise test account.
ImplicitlySignIn(kEnterpriseTestEmail);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest, EnterpriseAccount) {
// The account managed status is known.
signin::AccountManagedStatusFinder account_managed_status_finder(
GetIdentityManager(),
GetIdentityManager()->GetPrimaryAccountInfo(
signin::ConsentLevel::kSignin),
base::DoNothing());
ASSERT_EQ(account_managed_status_finder.GetOutcome(),
signin::AccountManagedStatusFinderOutcome::kEnterpriseGoogleDotCom);
// Simulate the timer firing.
FireDialogTriggerTimer();
histogram_tester_.ExpectUniqueSample(
kAccountManagedStatusHistogram,
signin::AccountManagedStatusFinderOutcome::kEnterpriseGoogleDotCom, 1);
// The dialog is not shown.
EXPECT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectTotalCount(kDialogShownHistogram, 0);
histogram_tester_.ExpectUniqueSample(
kDialogNotShownReasonHistogram,
DiceMigrationService::DialogNotShownReason::kManagedAccount, 1);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest,
PRE_TimerFinishedButAccountManagedStatusNotKnown) {
// Implicitly sign in with a test account whose managed status is not known.
ImplicitlySignIn(kIndeterminableTestEmail);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest,
TimerFinishedButAccountManagedStatusNotKnown) {
// The account managed status is not known yet.
signin::AccountManagedStatusFinder account_managed_status_finder(
GetIdentityManager(),
GetIdentityManager()->GetPrimaryAccountInfo(
signin::ConsentLevel::kSignin),
base::DoNothing());
ASSERT_EQ(account_managed_status_finder.GetOutcome(),
signin::AccountManagedStatusFinderOutcome::kPending);
FireDialogTriggerTimer();
histogram_tester_.ExpectUniqueSample(
kAccountManagedStatusHistogram,
signin::AccountManagedStatusFinderOutcome::kPending, 1);
// The dialog is not shown.
EXPECT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
// Simulate the account managed status becoming known when refresh tokens are
// loaded.
signin::AccountManagedStatusFinder::SetNonEnterpriseDomainForTesting(
"indeterminable.com");
signin::SetRefreshTokenForPrimaryAccount(GetIdentityManager());
// The dialog is now shown.
EXPECT_TRUE(GetDiceMigrationService()->GetDialogWidgetForTesting());
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
AccountManagedStatusKnownButTimerPending) {
// The account managed status is known.
signin::AccountManagedStatusFinder account_managed_status_finder(
GetIdentityManager(),
GetIdentityManager()->GetPrimaryAccountInfo(
signin::ConsentLevel::kSignin),
base::DoNothing());
ASSERT_EQ(account_managed_status_finder.GetOutcome(),
signin::AccountManagedStatusFinderOutcome::kConsumerGmail);
// The dialog trigger timer is running.
ASSERT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
histogram_tester_.ExpectTotalCount(kAccountManagedStatusHistogram, 0);
// The dialog is not shown.
EXPECT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
// Simulate the timer firing.
FireDialogTriggerTimer();
// The dialog is now shown.
EXPECT_TRUE(GetDiceMigrationService()->GetDialogWidgetForTesting());
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
StopTimerUponPersistentAuthError) {
// The timer has started.
ASSERT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
// Simulate a persistent auth error.
signin::SetInvalidRefreshTokenForPrimaryAccount(GetIdentityManager());
// The timer is stopped.
EXPECT_FALSE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
ASSERT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(
kDialogNotShownReasonHistogram,
DiceMigrationService::DialogNotShownReason::kPrimaryAccountCleared, 1);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
CloseDialogUponPersistentAuthError) {
// Show the migration bubble.
FireDialogTriggerTimer();
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
views::test::WidgetDestroyedWaiter waiter(dialog_widget);
// Simulate a persistent auth error. This should cause the implicitly
// signed-in account to be removed, thereby becoming similar to the case of
// the user signing out.
signin::SetInvalidRefreshTokenForPrimaryAccount(GetIdentityManager());
waiter.Wait();
ASSERT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(
kDialogCloseReasonHistogram,
DiceMigrationService::DialogCloseReason::kPrimaryAccountCleared, 1);
}
// This can happen due to race condition between the timer firing and the dialog
// being closed.
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
AcceptDialogAfterPersistentAuthError) {
// Show the migration bubble.
FireDialogTriggerTimer();
ASSERT_TRUE(GetDiceMigrationService()->GetDialogWidgetForTesting());
// Simulate a persistent auth error.
signin::SetInvalidRefreshTokenForPrimaryAccount(GetIdentityManager());
// The dialog is not destroyed yet due to the race condition.
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
views::test::WidgetDestroyedWaiter waiter(dialog_widget);
// Simulate clicking on accept button.
dialog_widget->CloseWithReason(
views::Widget::ClosedReason::kAcceptButtonClicked);
waiter.Wait();
// No migration is performed.
EXPECT_FALSE(GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest, StopTimerUponSignout) {
ASSERT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
// Sign out.
signin::ClearPrimaryAccount(GetIdentityManager());
// The timer is stopped.
EXPECT_FALSE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
ASSERT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(
kDialogNotShownReasonHistogram,
DiceMigrationService::DialogNotShownReason::kPrimaryAccountCleared, 1);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest, CloseDialogUponSignout) {
// Show the migration bubble.
FireDialogTriggerTimer();
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
views::test::WidgetDestroyedWaiter waiter(dialog_widget);
// Sign out.
signin::ClearPrimaryAccount(GetIdentityManager());
waiter.Wait();
ASSERT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(
kDialogCloseReasonHistogram,
DiceMigrationService::DialogCloseReason::kPrimaryAccountCleared, 1);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
StopTimerUponPrimaryAccountChange) {
ASSERT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
// Change the primary account.
ImplicitlySignIn("test2@gmail.com");
// The timer is stopped.
EXPECT_FALSE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
ASSERT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(
kDialogNotShownReasonHistogram,
DiceMigrationService::DialogNotShownReason::kPrimaryAccountChanged, 1);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
CloseDialogUponPrimaryAccountChange) {
// Show the migration bubble.
FireDialogTriggerTimer();
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
views::test::WidgetDestroyedWaiter waiter(dialog_widget);
// Change the primary account.
ImplicitlySignIn("test2@gmail.com");
waiter.Wait();
ASSERT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(
kDialogCloseReasonHistogram,
DiceMigrationService::DialogCloseReason::kPrimaryAccountChanged, 1);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
StopTimerIfSyncTurnedOn) {
ASSERT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
// Turn sync on.
GetIdentityManager()->GetPrimaryAccountMutator()->SetPrimaryAccount(
GetIdentityManager()
->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
.account_id,
signin::ConsentLevel::kSync);
// The timer is stopped.
EXPECT_FALSE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
ASSERT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(
kDialogNotShownReasonHistogram,
DiceMigrationService::DialogNotShownReason::kSyncTurnedOn, 1);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
CloseDialogIfSyncTurnedOn) {
// Show the migration bubble.
FireDialogTriggerTimer();
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
views::test::WidgetDestroyedWaiter waiter(dialog_widget);
// Turn sync on.
GetIdentityManager()->GetPrimaryAccountMutator()->SetPrimaryAccount(
GetIdentityManager()
->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
.account_id,
signin::ConsentLevel::kSync);
waiter.Wait();
ASSERT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(
kDialogCloseReasonHistogram,
DiceMigrationService::DialogCloseReason::kSyncTurnedOn, 1);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
CloseDialogUponAvatarButtonPress) {
// Show the migration bubble.
FireDialogTriggerTimer();
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
views::test::WidgetDestroyedWaiter waiter(dialog_widget);
// Press the avatar button.
GetAvatarToolbarButton()->ButtonPressed();
waiter.Wait();
ASSERT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(
kDialogCloseReasonHistogram,
DiceMigrationService::DialogCloseReason::kAvatarButtonClicked, 1);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
PressingAvatarButtonBeforeDialogIsShown) {
// Press the avatar button.
GetAvatarToolbarButton()->ButtonPressed();
// Show the migration bubble.
FireDialogTriggerTimer();
// The dialog is shown.
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
histogram_tester_.ExpectTotalCount(kDialogCloseReasonHistogram, 0);
}
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTest,
CloseDialogUponBrowserClose) {
// Show the migration bubble.
FireDialogTriggerTimer();
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
views::test::WidgetDestroyedWaiter waiter(dialog_widget);
// Browser is closed.
CloseBrowserAsynchronously(browser());
waiter.Wait();
histogram_tester_.ExpectUniqueSample(
kDialogCloseReasonHistogram,
DiceMigrationService::DialogCloseReason::kUnspecified, 1);
}
class DiceMigrationServiceSyncTest : public SyncTest {
public:
DiceMigrationServiceSyncTest() : SyncTest(SINGLE_CLIENT) {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{switches::kOfferMigrationToDiceUsers},
/*disabled_features=*/{switches::kForcedDiceMigration});
}
signin::IdentityManager* GetIdentityManager() {
return IdentityManagerFactory::GetForProfile(GetProfile(0));
}
DiceMigrationService* GetDiceMigrationService() {
DiceMigrationService* service =
DiceMigrationServiceFactory::GetForProfileIfExists(GetProfile(0));
EXPECT_TRUE(service);
return service;
}
void TriggerDialog() {
// This should allow account managed status to be known.
signin::WaitForRefreshTokensLoaded(GetIdentityManager());
// The account managed status is known.
signin::AccountManagedStatusFinder account_managed_status_finder(
GetIdentityManager(),
GetIdentityManager()->GetPrimaryAccountInfo(
signin::ConsentLevel::kSignin),
base::DoNothing());
ASSERT_EQ(account_managed_status_finder.GetOutcome(),
signin::AccountManagedStatusFinderOutcome::kConsumerGmail);
base::OneShotTimer& timer =
GetDiceMigrationService()->GetDialogTriggerTimerForTesting();
ASSERT_TRUE(timer.IsRunning());
timer.FireNow();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceSyncTest, PRE_MigrateUser) {
ASSERT_TRUE(SetupClients());
// Implicitly sign in.
AccountInfo account_info = signin::MakeAccountAvailable(
GetIdentityManager(),
signin::AccountAvailabilityOptionsBuilder()
.AsPrimary(signin::ConsentLevel::kSignin)
.WithAccessPoint(signin_metrics::AccessPoint::kWebSignin)
.Build(kTestEmail));
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceSyncTest, MigrateUser) {
constexpr syncer::UserSelectableTypeSet new_selected_types = {
syncer::UserSelectableType::kPreferences,
syncer::UserSelectableType::kThemes,
syncer::UserSelectableType::kPasswords,
syncer::UserSelectableType::kAutofill,
};
ASSERT_TRUE(SetupClients());
// The user is implicitly signed in.
ASSERT_TRUE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
ASSERT_FALSE(preferences_helper::GetPrefs(0)->GetBoolean(
prefs::kExplicitBrowserSignin));
// These types are only enabled upon explicitly signing in.
ASSERT_FALSE(GetSyncService(0)->GetUserSettings()->GetSelectedTypes().HasAny(
new_selected_types));
ASSERT_FALSE(GetSyncService(0)->GetActiveDataTypes().HasAny({
syncer::PREFERENCES,
syncer::THEMES,
syncer::PASSWORDS,
syncer::CONTACT_INFO,
}));
// Show migration bubble.
TriggerDialog();
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
// Simulate clicking on accept button.
dialog_widget->CloseWithReason(
views::Widget::ClosedReason::kAcceptButtonClicked);
EXPECT_TRUE(PrefValueChecker(preferences_helper::GetPrefs(0),
prefs::kExplicitBrowserSignin, base::Value(true))
.Wait());
ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
// This should set the relevant user selected types.
EXPECT_TRUE(GetSyncService(0)->GetUserSettings()->GetSelectedTypes().HasAll(
new_selected_types))
<< GetSyncService(0)->GetUserSettings()->GetSelectedTypes();
EXPECT_TRUE(GetSyncService(0)->GetActiveDataTypes().HasAll(
{syncer::PREFERENCES, syncer::THEMES, syncer::PASSWORDS,
syncer::CONTACT_INFO}))
<< GetSyncService(0)->GetActiveDataTypes();
}
class DiceMigrationServiceBrowserTestWithParameterizedDialogShownCount
: public DiceMigrationServiceBrowserTest,
public testing::WithParamInterface<int> {};
INSTANTIATE_TEST_SUITE_P(
,
DiceMigrationServiceBrowserTestWithParameterizedDialogShownCount,
testing::Range(0, DiceMigrationService::kMaxDialogShownCount + 1));
IN_PROC_BROWSER_TEST_P(
DiceMigrationServiceBrowserTestWithParameterizedDialogShownCount,
PRE_LimitDialogShownCount) {
ImplicitlySignIn(kTestEmail);
GetPrefs()->SetInteger(kDiceMigrationDialogShownCount, GetParam());
}
IN_PROC_BROWSER_TEST_P(
DiceMigrationServiceBrowserTestWithParameterizedDialogShownCount,
LimitDialogShownCount) {
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
ASSERT_EQ(GetPrefs()->GetInteger(kDiceMigrationDialogShownCount), GetParam());
// The timer is started only if the preconditions are met, i.e. the dialog
// shown count is below the limit.
const bool should_timer_be_running =
GetParam() < DiceMigrationService::kMaxDialogShownCount;
EXPECT_EQ(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning(),
should_timer_be_running);
ASSERT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(kDialogTimerStartedHistogram,
should_timer_be_running, 1);
}
IN_PROC_BROWSER_TEST_P(
DiceMigrationServiceBrowserTestWithParameterizedDialogShownCount,
PRE_DialogVariants) {
ImplicitlySignIn(kTestEmail);
GetPrefs()->SetInteger(kDiceMigrationDialogShownCount, GetParam());
}
IN_PROC_BROWSER_TEST_P(
DiceMigrationServiceBrowserTestWithParameterizedDialogShownCount,
DialogVariants) {
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
ASSERT_EQ(GetPrefs()->GetInteger(kDiceMigrationDialogShownCount), GetParam());
histogram_tester_.ExpectUniqueSample(kDialogPreviouslyShownCountHistogram,
GetParam(), 1);
// Show the migration bubble.
base::OneShotTimer& timer =
GetDiceMigrationService()->GetDialogTriggerTimerForTesting();
// Skip this test for `kMaxDialogShownCount` since no dialog is shown in this
// case.
if (GetParam() == DiceMigrationService::kMaxDialogShownCount) {
ASSERT_FALSE(timer.IsRunning());
return;
}
ASSERT_TRUE(timer.IsRunning());
timer.FireNow();
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
// Both the variants have the accept button.
ASSERT_TRUE(ContainsViewWithId(dialog_widget->GetContentsView(),
DiceMigrationService::kAcceptButtonElementId));
if (GetParam() < DiceMigrationService::kMaxDialogShownCount - 1) {
// Non-"final" variant has the cancel button but not the close-x button.
EXPECT_TRUE(
ContainsViewWithId(dialog_widget->GetRootView(),
DiceMigrationService::kCancelButtonElementId));
EXPECT_FALSE(
ContainsViewWithId(dialog_widget->GetRootView(),
views::BubbleFrameView::kCloseButtonElementId));
} else {
// "Final" variant has the close-x button but not the cancel button.
EXPECT_FALSE(
ContainsViewWithId(dialog_widget->GetRootView(),
DiceMigrationService::kCancelButtonElementId));
EXPECT_TRUE(
ContainsViewWithId(dialog_widget->GetRootView(),
views::BubbleFrameView::kCloseButtonElementId));
}
}
class DiceMigrationServiceBrowserTestWithMockedTime
: public DiceMigrationServiceBrowserTest {
protected:
void SetUpBrowserContextKeyedServices(
content::BrowserContext* context) override {
DiceMigrationServiceBrowserTest::SetUpBrowserContextKeyedServices(context);
DiceMigrationServiceFactory::GetInstance()->SetTestingFactory(
context,
base::BindRepeating(&DiceMigrationServiceBrowserTestWithMockedTime::
CreateDiceMigrationServiceWithTaskRunner,
base::Unretained(this)));
}
std::unique_ptr<KeyedService> CreateDiceMigrationServiceWithTaskRunner(
content::BrowserContext* context) {
return std::make_unique<DiceMigrationService>(
Profile::FromBrowserContext(context), task_runner_);
}
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_ =
base::MakeRefCounted<base::TestMockTimeTaskRunner>();
};
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTestWithMockedTime,
ShowDialogBetweenRange) {
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
// The timer is running, the dialog is not shown.
ASSERT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
ASSERT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
// Fast forward to the minimum delay - 1 second. The timer is still running
// and the dialog is not shown.
task_runner_->FastForwardBy(
switches::kOfferMigrationToDiceUsersMinDelay.Get() - base::Seconds(1));
EXPECT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
EXPECT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
// Fast forward to the maximum delay. The timer is stopped and the dialog is
// shown.
task_runner_->FastForwardBy(
switches::kOfferMigrationToDiceUsersMaxDelay.Get() -
switches::kOfferMigrationToDiceUsersMinDelay.Get() + base::Seconds(1));
EXPECT_FALSE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
EXPECT_TRUE(GetDiceMigrationService()->GetDialogWidgetForTesting());
}
class DiceMigrationServiceRestoreFromBackupBrowserTestFlagDisabled
: public DiceMigrationServiceBrowserTest {
public:
DiceMigrationServiceRestoreFromBackupBrowserTestFlagDisabled() {
scoped_feature_list_.InitAndDisableFeature(
switches::kRollbackDiceMigration);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
DICE_MIGRATION_TEST_F(
DiceMigrationServiceRestoreFromBackupBrowserTestFlagDisabled,
SavePrefsForBackup) {
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
// Pre-migration prefs.
ASSERT_FALSE(GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
ASSERT_FALSE(GetPrefs()->GetBoolean(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled));
ASSERT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
FireDialogTriggerTimer();
// The dialog is shown.
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
views::test::WidgetDestroyedWaiter waiter(dialog_widget);
// Simulate clicking on accept button.
dialog_widget->CloseWithReason(
views::Widget::ClosedReason::kAcceptButtonClicked);
waiter.Wait();
ASSERT_TRUE(GetPrefs()->GetBoolean(kDiceMigrationMigrated));
// The explicit sign-in pref is set, this marks the user as explicitly
// signed in.
ASSERT_TRUE(GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
ASSERT_FALSE(IsImplicitlySignedIn());
ASSERT_TRUE(GetPrefs()->GetBoolean(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled));
// The prefs are saved for backup.
const base::Value* value = GetPrefs()->GetUserPrefValue(kDiceMigrationBackup);
ASSERT_TRUE(value);
ASSERT_TRUE(value->is_dict());
EXPECT_EQ(
value->GetDict(),
base::Value::Dict()
.SetByDottedPath(prefs::kExplicitBrowserSignin, false)
.SetByDottedPath(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled, false));
}
class DiceMigrationServiceRestoreFromBackupBrowserTestFlagEnabled
: public DiceMigrationServiceBrowserTest {
public:
DiceMigrationServiceRestoreFromBackupBrowserTestFlagEnabled() {
// Only enable the rollback feature in the main test to allow testing the
// rollback flow.
scoped_feature_list_.InitWithFeatureState(switches::kRollbackDiceMigration,
!content::IsPreTest());
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(
DiceMigrationServiceRestoreFromBackupBrowserTestFlagEnabled,
PRE_PRE_Restore) {
ImplicitlySignIn(kTestEmail);
}
IN_PROC_BROWSER_TEST_F(
DiceMigrationServiceRestoreFromBackupBrowserTestFlagEnabled,
PRE_Restore) {
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
// Pre-migration prefs.
ASSERT_FALSE(GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
ASSERT_FALSE(GetPrefs()->GetBoolean(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled));
// Only payments is selected for implicitly signed-in users, till the
// kReplaceSyncPromosWithSignInPromos flag is enabled.
if (!base::FeatureList::IsEnabled(
syncer::kReplaceSyncPromosWithSignInPromos)) {
ASSERT_EQ(
GetSyncService()->GetUserSettings()->GetSelectedTypes(),
syncer::UserSelectableTypeSet({syncer::UserSelectableType::kPayments}));
}
ASSERT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
FireDialogTriggerTimer();
// The dialog is shown.
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
views::test::WidgetDestroyedWaiter waiter(dialog_widget);
// Simulate clicking on accept button.
dialog_widget->CloseWithReason(
views::Widget::ClosedReason::kAcceptButtonClicked);
waiter.Wait();
ASSERT_TRUE(GetPrefs()->GetBoolean(kDiceMigrationMigrated));
histogram_tester_.ExpectUniqueSample(kUserMigratedHistogram, true, 1);
// The explicit sign-in pref is set, this marks the user as explicitly
// signed in.
ASSERT_TRUE(GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
ASSERT_TRUE(GetPrefs()->GetBoolean(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled));
ASSERT_TRUE(IsExplicitlySignedIn());
histogram_tester_.ExpectUniqueSample(kUserMigratedHistogram, true, 1);
// This should enable additional user selected types.
ASSERT_TRUE(GetSyncService()->GetUserSettings()->GetSelectedTypes().HasAll({
syncer::UserSelectableType::kPayments,
syncer::UserSelectableType::kPreferences,
syncer::UserSelectableType::kThemes,
syncer::UserSelectableType::kPasswords,
syncer::UserSelectableType::kAutofill,
}));
// The prefs are saved for backup.
const base::Value* value = GetPrefs()->GetUserPrefValue(kDiceMigrationBackup);
ASSERT_TRUE(value);
ASSERT_TRUE(value->is_dict());
ASSERT_EQ(
value->GetDict(),
base::Value::Dict()
.SetByDottedPath(prefs::kExplicitBrowserSignin, false)
.SetByDottedPath(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled, false));
EXPECT_FALSE(GetPrefs()->GetBoolean(kDiceMigrationRestoredFromBackup));
}
IN_PROC_BROWSER_TEST_F(
DiceMigrationServiceRestoreFromBackupBrowserTestFlagEnabled,
Restore) {
histogram_tester_.ExpectUniqueSample(kRestoredFromBackupHistogram, true, 1);
// The user is implicitly signed in.
EXPECT_TRUE(IsImplicitlySignedIn());
// Prefs restored from backup.
EXPECT_FALSE(GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
EXPECT_FALSE(GetPrefs()->GetBoolean(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled));
// Only payments is selected for implicitly signed-in users. None of the other
// user selectable types are selected, unless the
// kReplaceSyncPromosWithSignInPromos flag is enabled.
if (!base::FeatureList::IsEnabled(
syncer::kReplaceSyncPromosWithSignInPromos)) {
EXPECT_EQ(
GetSyncService()->GetUserSettings()->GetSelectedTypes(),
syncer::UserSelectableTypeSet({syncer::UserSelectableType::kPayments}));
}
// The timer is not running, the dialog is not shown.
EXPECT_FALSE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
EXPECT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
// Restoration pref is set.
EXPECT_TRUE(GetPrefs()->GetBoolean(kDiceMigrationRestoredFromBackup));
// The migration pref is reset.
EXPECT_FALSE(GetPrefs()->GetBoolean(kDiceMigrationMigrated));
// The dialog shown count is reset.
EXPECT_EQ(0, GetPrefs()->GetInteger(kDiceMigrationDialogShownCount));
// The dialog last shown time is not cleared.
EXPECT_FALSE(
GetPrefs()->GetTime(kDiceMigrationDialogLastShownTime).is_null());
}
IN_PROC_BROWSER_TEST_F(
DiceMigrationServiceRestoreFromBackupBrowserTestFlagEnabled,
PRE_PRE_RestoreFailed) {
ImplicitlySignIn(kTestEmail);
}
IN_PROC_BROWSER_TEST_F(
DiceMigrationServiceRestoreFromBackupBrowserTestFlagEnabled,
PRE_RestoreFailed) {
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
// Pre-migration prefs.
ASSERT_FALSE(GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
ASSERT_FALSE(GetPrefs()->GetBoolean(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled));
// Only payments is selected for implicitly signed-in users, unless
// kReplaceSyncPromosWithSignInPromos flag is enabled.
if (!base::FeatureList::IsEnabled(
syncer::kReplaceSyncPromosWithSignInPromos)) {
ASSERT_EQ(
GetSyncService()->GetUserSettings()->GetSelectedTypes(),
syncer::UserSelectableTypeSet({syncer::UserSelectableType::kPayments}));
}
ASSERT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
FireDialogTriggerTimer();
// The dialog is shown.
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
views::test::WidgetDestroyedWaiter waiter(dialog_widget);
// Simulate clicking on accept button.
dialog_widget->CloseWithReason(
views::Widget::ClosedReason::kAcceptButtonClicked);
waiter.Wait();
ASSERT_TRUE(GetPrefs()->GetBoolean(kDiceMigrationMigrated));
histogram_tester_.ExpectUniqueSample(kUserMigratedHistogram, true, 1);
// The explicit sign-in pref is set, this marks the user as explicitly
// signed in.
ASSERT_TRUE(GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
ASSERT_TRUE(GetPrefs()->GetBoolean(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled));
ASSERT_TRUE(IsExplicitlySignedIn());
histogram_tester_.ExpectUniqueSample(kUserMigratedHistogram, true, 1);
// This should enable additional user selected types.
ASSERT_TRUE(GetSyncService()->GetUserSettings()->GetSelectedTypes().HasAll({
syncer::UserSelectableType::kPayments,
syncer::UserSelectableType::kPreferences,
syncer::UserSelectableType::kThemes,
syncer::UserSelectableType::kPasswords,
syncer::UserSelectableType::kAutofill,
}));
// The prefs are saved for backup.
const base::Value* value = GetPrefs()->GetUserPrefValue(kDiceMigrationBackup);
ASSERT_TRUE(value);
ASSERT_TRUE(value->is_dict());
ASSERT_EQ(
value->GetDict(),
base::Value::Dict()
.SetByDottedPath(prefs::kExplicitBrowserSignin, false)
.SetByDottedPath(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled, false));
// Simulate backup pref missing.
GetPrefs()->ClearPref(kDiceMigrationBackup);
}
IN_PROC_BROWSER_TEST_F(
DiceMigrationServiceRestoreFromBackupBrowserTestFlagEnabled,
RestoreFailed) {
histogram_tester_.ExpectUniqueSample(kRestoredFromBackupHistogram, false, 1);
// The user is implicitly signed in.
EXPECT_TRUE(IsExplicitlySignedIn());
// Prefs are not restored from backup.
EXPECT_TRUE(GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
EXPECT_TRUE(GetPrefs()->GetBoolean(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled));
// The user selected types are unchanged.
EXPECT_TRUE(GetSyncService()->GetUserSettings()->GetSelectedTypes().HasAll({
syncer::UserSelectableType::kPayments,
syncer::UserSelectableType::kPreferences,
syncer::UserSelectableType::kThemes,
syncer::UserSelectableType::kPasswords,
syncer::UserSelectableType::kAutofill,
}));
// The timer is not running, the dialog is not shown.
ASSERT_FALSE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
ASSERT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
// Restoration pref is not set.
EXPECT_FALSE(GetPrefs()->GetBoolean(kDiceMigrationRestoredFromBackup));
// The migration pref is not reset.
EXPECT_TRUE(GetPrefs()->GetBoolean(kDiceMigrationMigrated));
}
class DiceMigrationServiceRestoreFromBackupBrowserTestReMigration
: public DiceMigrationServiceBrowserTest {
public:
DiceMigrationServiceRestoreFromBackupBrowserTestReMigration() {
// Only enable the rollback feature for the last pre-test to allow DICe
// migration to be tested in the main test.
std::string_view test_name =
testing::UnitTest::GetInstance()->current_test_info()->name();
// This calculates the number of times "PRE_" appears in the test name.
int pre_count = 0;
while (test_name.starts_with("PRE_")) {
pre_count++;
test_name.remove_prefix(4);
}
scoped_feature_list_.InitWithFeatureState(switches::kRollbackDiceMigration,
pre_count == 1);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(
DiceMigrationServiceRestoreFromBackupBrowserTestReMigration,
PRE_PRE_PRE_PostRestore) {
ImplicitlySignIn(kTestEmail);
}
IN_PROC_BROWSER_TEST_F(
DiceMigrationServiceRestoreFromBackupBrowserTestReMigration,
PRE_PRE_PostRestore) {
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
// Pre-migration prefs.
ASSERT_FALSE(GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
ASSERT_FALSE(GetPrefs()->GetBoolean(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled));
ASSERT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
FireDialogTriggerTimer();
// The dialog is shown.
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
views::test::WidgetDestroyedWaiter waiter(dialog_widget);
// Simulate clicking on accept button.
dialog_widget->CloseWithReason(
views::Widget::ClosedReason::kAcceptButtonClicked);
waiter.Wait();
ASSERT_TRUE(GetPrefs()->GetBoolean(kDiceMigrationMigrated));
ASSERT_FALSE(GetPrefs()->GetBoolean(kDiceMigrationRestoredFromBackup));
// The explicit sign-in pref is set, this marks the user as explicitly
// signed in.
ASSERT_TRUE(GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
ASSERT_TRUE(GetPrefs()->GetBoolean(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled));
ASSERT_TRUE(IsExplicitlySignedIn());
histogram_tester_.ExpectUniqueSample(kUserMigratedHistogram, true, 1);
}
IN_PROC_BROWSER_TEST_F(
DiceMigrationServiceRestoreFromBackupBrowserTestReMigration,
PRE_PostRestore) {
histogram_tester_.ExpectUniqueSample(kRestoredFromBackupHistogram, true, 1);
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
// Prefs restored from backup.
ASSERT_FALSE(GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
ASSERT_FALSE(GetPrefs()->GetBoolean(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled));
ASSERT_TRUE(GetPrefs()->GetBoolean(kDiceMigrationRestoredFromBackup));
ASSERT_FALSE(GetPrefs()->GetBoolean(kDiceMigrationMigrated));
// Set the dialog last shown time to
// (`kOfferMigrationToDiceUsersMinTimeBetweenDialogs` + 1) days ago to allow
// the dialog to be shown again.
GetProfile()->GetPrefs()->SetTime(
kDiceMigrationDialogLastShownTime,
base::Time::Now() -
(switches::kOfferMigrationToDiceUsersMinTimeBetweenDialogs.Get() +
base::Days(1)));
}
IN_PROC_BROWSER_TEST_F(
DiceMigrationServiceRestoreFromBackupBrowserTestReMigration,
PostRestore) {
histogram_tester_.ExpectTotalCount(kRestoredFromBackupHistogram, 0);
// The user is implicitly signed in.
ASSERT_TRUE(IsImplicitlySignedIn());
// Prefs restored from backup.
ASSERT_FALSE(GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
ASSERT_FALSE(GetPrefs()->GetBoolean(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled));
ASSERT_TRUE(GetPrefs()->GetBoolean(kDiceMigrationRestoredFromBackup));
ASSERT_FALSE(GetPrefs()->GetBoolean(kDiceMigrationMigrated));
ASSERT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
FireDialogTriggerTimer();
// The dialog is shown.
views::Widget* dialog_widget =
GetDiceMigrationService()->GetDialogWidgetForTesting();
ASSERT_TRUE(dialog_widget);
views::test::WidgetDestroyedWaiter waiter(dialog_widget);
// Simulate clicking on accept button.
dialog_widget->CloseWithReason(
views::Widget::ClosedReason::kAcceptButtonClicked);
waiter.Wait();
ASSERT_TRUE(GetPrefs()->GetBoolean(kDiceMigrationMigrated));
ASSERT_FALSE(GetPrefs()->GetBoolean(kDiceMigrationRestoredFromBackup));
// The explicit sign-in pref is set, this marks the user as explicitly
// signed in.
ASSERT_TRUE(GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
ASSERT_TRUE(GetPrefs()->GetBoolean(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled));
ASSERT_TRUE(IsExplicitlySignedIn());
histogram_tester_.ExpectUniqueSample(kUserMigratedHistogram, true, 1);
histogram_tester_.ExpectTotalCount(kRestoredFromBackupHistogram, 0);
}
// Regression test for crbug.com/437344538. The dialog should not be shown if
// the avatar button is not unavailable. This test reproduces it with a web
// app window, which has no avatar button.
class DiceMigrationServiceBrowserTestWithWebApps
: public DiceMigrationServiceBrowserTest {
public:
void InstallAndLaunchWebApp() {
web_app_frame_toolbar_helper_.InstallAndLaunchWebApp(
browser(), GURL("https://test.org"));
}
private:
WebAppFrameToolbarTestHelper web_app_frame_toolbar_helper_;
web_app::OsIntegrationTestOverrideBlockingRegistration faked_os_integration_;
};
DICE_MIGRATION_TEST_F(DiceMigrationServiceBrowserTestWithWebApps,
NoDialogShown) {
// The user is implicitly signed in.
ASSERT_TRUE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
ASSERT_FALSE(
GetProfile()->GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
InstallAndLaunchWebApp();
ASSERT_TRUE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
// Show the migration dialog.
FireDialogTriggerTimer();
// The dialog is not shown.
EXPECT_FALSE(GetDiceMigrationService()->GetDialogWidgetForTesting());
histogram_tester_.ExpectUniqueSample(kDialogShownHistogram, false, 1);
histogram_tester_.ExpectUniqueSample(
kDialogNotShownReasonHistogram,
DiceMigrationService::DialogNotShownReason::kAvatarButtonUnavailable, 1);
}
class DiceMigrationServiceForcedMigrationBrowserTest
: public DiceMigrationServiceBrowserTest {
base::test::ScopedFeatureList scoped_feature_list_{
switches::kForcedDiceMigration};
};
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceForcedMigrationBrowserTest,
PRE_ForceMigrateImplicitlySignedInUser) {
ImplicitlySignIn(kTestEmail);
ASSERT_TRUE(IsImplicitlySignedIn());
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceForcedMigrationBrowserTest,
ForceMigrateImplicitlySignedInUser) {
EXPECT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
// The user is signed in to the web only.
signin::WaitForRefreshTokensLoaded(GetIdentityManager());
EXPECT_THAT(
GetIdentityManager()->GetAccountsWithRefreshTokens(),
testing::ElementsAre(testing::Field(&AccountInfo::email, kTestEmail)));
histogram_tester_.ExpectUniqueSample(kForceMigratedHistogram, true, 1);
histogram_tester_.ExpectUniqueSample(
kSignoutReasonHistogram,
signin_metrics::ProfileSignout::kForcedDiceMigration, 1);
// No toast is shown.
histogram_tester_.ExpectTotalCount(kToastTriggerToShowHistogram, 0);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceForcedMigrationBrowserTest,
DoesNotMigrateSignedOutUser) {
EXPECT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
histogram_tester_.ExpectUniqueSample(kForceMigratedHistogram, false, 1);
histogram_tester_.ExpectTotalCount(kSignoutReasonHistogram, 0);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceForcedMigrationBrowserTest,
PRE_DoesNotMigrateExplicitlySignedInUser) {
signin::MakePrimaryAccountAvailable(GetIdentityManager(), kTestEmail,
signin::ConsentLevel::kSignin);
ASSERT_TRUE(IsExplicitlySignedIn());
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceForcedMigrationBrowserTest,
DoesNotMigrateExplicitlySignedInUser) {
EXPECT_TRUE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
histogram_tester_.ExpectUniqueSample(kForceMigratedHistogram, false, 1);
histogram_tester_.ExpectTotalCount(kSignoutReasonHistogram, 0);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceForcedMigrationBrowserTest,
PRE_DoesNotMigrateSyncingUser) {
signin::MakePrimaryAccountAvailable(GetIdentityManager(), kTestEmail,
signin::ConsentLevel::kSync);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceForcedMigrationBrowserTest,
DoesNotMigrateSyncingUser) {
EXPECT_TRUE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSync));
histogram_tester_.ExpectUniqueSample(kForceMigratedHistogram, false, 1);
histogram_tester_.ExpectTotalCount(kSignoutReasonHistogram, 0);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceForcedMigrationBrowserTest,
PRE_EnterpriseAccountWithoutAccountManagement) {
ImplicitlySignIn(kEnterpriseTestEmail);
ASSERT_TRUE(IsImplicitlySignedIn());
// The account managed status is known.
signin::AccountManagedStatusFinder account_managed_status_finder(
GetIdentityManager(),
GetIdentityManager()->GetPrimaryAccountInfo(
signin::ConsentLevel::kSignin),
base::DoNothing());
ASSERT_EQ(account_managed_status_finder.GetOutcome(),
signin::AccountManagedStatusFinderOutcome::kEnterpriseGoogleDotCom);
// The user has not accepted account management.
ASSERT_FALSE(enterprise_util::UserAcceptedAccountManagement(GetProfile()));
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceForcedMigrationBrowserTest,
EnterpriseAccountWithoutAccountManagement) {
EXPECT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
// The user is signed in to the web only.
signin::WaitForRefreshTokensLoaded(GetIdentityManager());
EXPECT_THAT(GetIdentityManager()->GetAccountsWithRefreshTokens(),
testing::ElementsAre(
testing::Field(&AccountInfo::email, kEnterpriseTestEmail)));
histogram_tester_.ExpectUniqueSample(kForceMigratedHistogram, true, 1);
histogram_tester_.ExpectUniqueSample(kForcedMigrationAccountManagedHistogram,
false, 1);
histogram_tester_.ExpectUniqueSample(
kSignoutReasonHistogram,
signin_metrics::ProfileSignout::kForcedDiceMigration, 1);
// No toast is shown.
ASSERT_FALSE(
GetDiceMigrationService()->GetDialogTriggerTimerForTesting().IsRunning());
histogram_tester_.ExpectTotalCount(kToastTriggerToShowHistogram, 0);
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceForcedMigrationBrowserTest,
PRE_EnterpriseAccountWithAccountManagement) {
ImplicitlySignIn(kEnterpriseTestEmail);
ASSERT_TRUE(IsImplicitlySignedIn());
// The account managed status is known.
signin::AccountManagedStatusFinder account_managed_status_finder(
GetIdentityManager(),
GetIdentityManager()->GetPrimaryAccountInfo(
signin::ConsentLevel::kSignin),
base::DoNothing());
ASSERT_EQ(account_managed_status_finder.GetOutcome(),
signin::AccountManagedStatusFinderOutcome::kEnterpriseGoogleDotCom);
// The user has accepted account management.
enterprise_util::SetUserAcceptedAccountManagement(GetProfile(), true);
ASSERT_TRUE(enterprise_util::UserAcceptedAccountManagement(GetProfile()));
}
IN_PROC_BROWSER_TEST_F(DiceMigrationServiceForcedMigrationBrowserTest,
EnterpriseAccountWithAccountManagement) {
// The user is explicitly signed in.
EXPECT_TRUE(IsExplicitlySignedIn());
// The user is signed in to the web.
signin::WaitForRefreshTokensLoaded(GetIdentityManager());
EXPECT_THAT(GetIdentityManager()->GetAccountsWithRefreshTokens(),
testing::ElementsAre(
testing::Field(&AccountInfo::email, kEnterpriseTestEmail)));
histogram_tester_.ExpectUniqueSample(kForceMigratedHistogram, true, 1);
histogram_tester_.ExpectUniqueSample(kForcedMigrationAccountManagedHistogram,
true, 1);
histogram_tester_.ExpectTotalCount(kSignoutReasonHistogram, 0);
// The toast is shown after the timer finishes. However, it is possible that
// the timer has already finished and the toast is shown before the
// expectation below is checked.
if (GetDiceMigrationService()
->GetDialogTriggerTimerForTesting()
.IsRunning()) {
histogram_tester_.ExpectUniqueSample(kToastTriggerToShowHistogram,
ToastId::kDiceUserMigrated, 0);
FireDialogTriggerTimer();
}
histogram_tester_.ExpectUniqueSample(kToastTriggerToShowHistogram,
ToastId::kDiceUserMigrated, 1);
}
} // namespace