blob: e06e117edf8d03d8effd94ff11b1f45ca4482a3e [file] [log] [blame]
// Copyright 2012 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/sync/sync_ui_util.h"
#include <set>
#include <string>
#include <utility>
#include "base/test/task_environment.h"
#include "build/chromeos_buildflags.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/sync/engine/sync_engine.h"
#include "components/sync/test/test_sync_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_features.h"
#include "base/test/scoped_feature_list.h"
#endif
namespace {
MATCHER_P4(SyncStatusLabelsMatch,
message_type,
status_label_string_id,
button_string_id,
action_type,
"") {
if (arg.message_type != message_type) {
*result_listener << "Wrong message type";
return false;
}
if (arg.status_label_string_id != status_label_string_id) {
*result_listener << "Wrong status label";
return false;
}
if (arg.button_string_id != button_string_id) {
*result_listener << "Wrong button string";
return false;
}
if (arg.action_type != action_type) {
*result_listener << "Wrong action type";
return false;
}
return true;
}
// A number of distinct states of the SyncService can be generated for tests.
enum DistinctState {
STATUS_CASE_SETUP_IN_PROGRESS,
STATUS_CASE_SETUP_ERROR,
STATUS_CASE_AUTH_ERROR,
STATUS_CASE_PROTOCOL_ERROR,
STATUS_CASE_CONFIRM_SYNC_SETTINGS,
STATUS_CASE_PASSPHRASE_ERROR,
STATUS_CASE_TRUSTED_VAULT_KEYS_ERROR,
STATUS_CASE_TRUSTED_VAULT_RECOVERABILITY_ERROR,
STATUS_CASE_SYNCED,
STATUS_CASE_SYNC_DISABLED_BY_POLICY,
#if BUILDFLAG(IS_CHROMEOS_ASH)
STATUS_CASE_SYNC_RESET_FROM_DASHBOARD,
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
NUMBER_OF_STATUS_CASES
};
const char kTestUser[] = "test_user@test.com";
// Sets up a TestSyncService to emulate one of a number of distinct cases in
// order to perform tests on the generated messages. Returns the expected value
// GetSyncStatusLabels should return.
// TODO(mastiz): Split the cases below to separate tests.
SyncStatusLabels SetUpDistinctCase(
syncer::TestSyncService* service,
signin::IdentityTestEnvironment* test_environment,
DistinctState case_number) {
switch (case_number) {
case STATUS_CASE_SETUP_IN_PROGRESS: {
service->SetInitialSyncFeatureSetupComplete(false);
service->SetSetupInProgress(true);
service->SetDetailedSyncStatus(false, syncer::SyncStatus());
return {SyncStatusMessageType::kPreSynced, IDS_SYNC_SETUP_IN_PROGRESS,
IDS_SETTINGS_EMPTY_STRING, SyncStatusActionType::kNoAction};
}
case STATUS_CASE_SETUP_ERROR: {
service->SetInitialSyncFeatureSetupComplete(false);
service->SetSetupInProgress(false);
service->SetDisableReasons(
{syncer::SyncService::DISABLE_REASON_UNRECOVERABLE_ERROR});
service->SetDetailedSyncStatus(false, syncer::SyncStatus());
return {
SyncStatusMessageType::kSyncError,
#if !BUILDFLAG(IS_CHROMEOS_ASH)
IDS_SYNC_STATUS_UNRECOVERABLE_ERROR,
#else
IDS_SYNC_STATUS_UNRECOVERABLE_ERROR_NEEDS_SIGNOUT,
#endif
IDS_SYNC_RELOGIN_BUTTON, SyncStatusActionType::kReauthenticate
};
}
case STATUS_CASE_AUTH_ERROR: {
service->SetInitialSyncFeatureSetupComplete(true);
service->SetTransportState(syncer::SyncService::TransportState::ACTIVE);
service->SetPassphraseRequired(false);
service->SetDetailedSyncStatus(false, syncer::SyncStatus());
// Make sure to fail authentication with an error in this case.
CoreAccountId account_id =
test_environment->identity_manager()->GetPrimaryAccountId(
signin::ConsentLevel::kSync);
test_environment->SetRefreshTokenForPrimaryAccount();
service->SetAccountInfo(
test_environment->identity_manager()->GetPrimaryAccountInfo(
signin::ConsentLevel::kSync));
test_environment->UpdatePersistentErrorOfRefreshTokenForAccount(
account_id,
GoogleServiceAuthError(GoogleServiceAuthError::State::SERVICE_ERROR));
service->SetDisableReasons(syncer::SyncService::DisableReasonSet());
return {SyncStatusMessageType::kSyncError, IDS_SYNC_RELOGIN_ERROR,
IDS_SYNC_RELOGIN_BUTTON, SyncStatusActionType::kReauthenticate};
}
case STATUS_CASE_PROTOCOL_ERROR: {
service->SetInitialSyncFeatureSetupComplete(true);
service->SetTransportState(syncer::SyncService::TransportState::ACTIVE);
service->SetPassphraseRequired(false);
syncer::SyncProtocolError protocol_error;
protocol_error.action = syncer::UPGRADE_CLIENT;
syncer::SyncStatus status;
status.sync_protocol_error = protocol_error;
service->SetDetailedSyncStatus(false, status);
service->SetDisableReasons(syncer::SyncService::DisableReasonSet());
return {SyncStatusMessageType::kSyncError, IDS_SYNC_UPGRADE_CLIENT,
IDS_SYNC_UPGRADE_CLIENT_BUTTON,
SyncStatusActionType::kUpgradeClient};
}
case STATUS_CASE_CONFIRM_SYNC_SETTINGS: {
service->SetInitialSyncFeatureSetupComplete(false);
service->SetPassphraseRequired(false);
service->SetDetailedSyncStatus(false, syncer::SyncStatus());
return {SyncStatusMessageType::kSyncError,
IDS_SYNC_SETTINGS_NOT_CONFIRMED,
IDS_SYNC_ERROR_USER_MENU_CONFIRM_SYNC_SETTINGS_BUTTON,
SyncStatusActionType::kConfirmSyncSettings};
}
case STATUS_CASE_PASSPHRASE_ERROR: {
service->SetInitialSyncFeatureSetupComplete(true);
service->SetTransportState(syncer::SyncService::TransportState::ACTIVE);
service->SetDetailedSyncStatus(false, syncer::SyncStatus());
service->SetDisableReasons(syncer::SyncService::DisableReasonSet());
service->SetPassphraseRequired(true);
service->SetPassphraseRequiredForPreferredDataTypes(true);
return {SyncStatusMessageType::kSyncError, IDS_SYNC_STATUS_NEEDS_PASSWORD,
IDS_SYNC_STATUS_NEEDS_PASSWORD_BUTTON,
SyncStatusActionType::kEnterPassphrase};
}
case STATUS_CASE_TRUSTED_VAULT_KEYS_ERROR:
service->SetInitialSyncFeatureSetupComplete(true);
service->SetTransportState(syncer::SyncService::TransportState::ACTIVE);
service->SetDetailedSyncStatus(false, syncer::SyncStatus());
service->SetDisableReasons(syncer::SyncService::DisableReasonSet());
service->SetPassphraseRequired(false);
service->SetTrustedVaultKeyRequiredForPreferredDataTypes(true);
return {SyncStatusMessageType::kPasswordsOnlySyncError,
IDS_SETTINGS_EMPTY_STRING, IDS_SYNC_STATUS_NEEDS_KEYS_BUTTON,
SyncStatusActionType::kRetrieveTrustedVaultKeys};
case STATUS_CASE_TRUSTED_VAULT_RECOVERABILITY_ERROR:
service->SetInitialSyncFeatureSetupComplete(true);
service->SetTransportState(syncer::SyncService::TransportState::ACTIVE);
service->SetDetailedSyncStatus(false, syncer::SyncStatus());
service->SetDisableReasons(syncer::SyncService::DisableReasonSet());
service->SetPassphraseRequired(false);
service->SetTrustedVaultRecoverabilityDegraded(true);
return {SyncStatusMessageType::kSynced, IDS_SYNC_ACCOUNT_SYNCING,
IDS_SETTINGS_EMPTY_STRING, SyncStatusActionType::kNoAction};
case STATUS_CASE_SYNCED: {
service->SetInitialSyncFeatureSetupComplete(true);
service->SetTransportState(syncer::SyncService::TransportState::ACTIVE);
service->SetDetailedSyncStatus(false, syncer::SyncStatus());
service->SetDisableReasons(syncer::SyncService::DisableReasonSet());
service->SetPassphraseRequired(false);
return {SyncStatusMessageType::kSynced, IDS_SYNC_ACCOUNT_SYNCING,
IDS_SETTINGS_EMPTY_STRING, SyncStatusActionType::kNoAction};
}
case STATUS_CASE_SYNC_DISABLED_BY_POLICY: {
service->SetDisableReasons(
{syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY});
service->SetInitialSyncFeatureSetupComplete(false);
service->SetTransportState(syncer::SyncService::TransportState::DISABLED);
service->SetPassphraseRequired(false);
service->SetDetailedSyncStatus(false, syncer::SyncStatus());
return {SyncStatusMessageType::kSynced,
IDS_SIGNED_IN_WITH_SYNC_DISABLED_BY_POLICY,
IDS_SETTINGS_EMPTY_STRING, SyncStatusActionType::kNoAction};
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
case STATUS_CASE_SYNC_RESET_FROM_DASHBOARD: {
service->SetSyncFeatureDisabledViaDashboard(true);
service->SetInitialSyncFeatureSetupComplete(true);
service->SetTransportState(syncer::SyncService::TransportState::ACTIVE);
service->SetPassphraseRequired(false);
service->SetDetailedSyncStatus(false, syncer::SyncStatus());
return {SyncStatusMessageType::kSyncError,
IDS_SIGNED_IN_WITH_SYNC_STOPPED_VIA_DASHBOARD,
IDS_SETTINGS_EMPTY_STRING, SyncStatusActionType::kNoAction};
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
case NUMBER_OF_STATUS_CASES:
NOTREACHED();
}
return {SyncStatusMessageType::kPreSynced, IDS_SETTINGS_EMPTY_STRING,
IDS_SETTINGS_EMPTY_STRING, SyncStatusActionType::kNoAction};
}
// This test ensures that each distinctive SyncService status will return a
// proper status and link messages from GetSyncStatusLabels().
TEST(SyncUIUtilTest, DistinctCasesReportProperMessages) {
base::test::TaskEnvironment task_environment;
for (int index = 0; index != NUMBER_OF_STATUS_CASES; index++) {
syncer::TestSyncService service;
signin::IdentityTestEnvironment environment;
// Need a primary account signed in before calling SetUpDistinctCase().
environment.MakePrimaryAccountAvailable(kTestUser,
signin::ConsentLevel::kSync);
SyncStatusLabels expected_labels = SetUpDistinctCase(
&service, &environment, static_cast<DistinctState>(index));
EXPECT_THAT(
GetSyncStatusLabels(&service, environment.identity_manager(),
/*is_user_clear_primary_account_allowed=*/true),
SyncStatusLabelsMatch(expected_labels.message_type,
expected_labels.status_label_string_id,
expected_labels.button_string_id,
expected_labels.action_type));
}
}
TEST(SyncUIUtilTest, UnrecoverableErrorWithActionableProtocolError) {
base::test::TaskEnvironment task_environment;
syncer::TestSyncService service;
signin::IdentityTestEnvironment environment;
environment.SetPrimaryAccount(kTestUser, signin::ConsentLevel::kSync);
service.SetInitialSyncFeatureSetupComplete(true);
service.SetDisableReasons(
{syncer::SyncService::DISABLE_REASON_UNRECOVERABLE_ERROR});
// First time action is not set. We should get unrecoverable error.
service.SetDetailedSyncStatus(true, syncer::SyncStatus());
// Expect the generic unrecoverable error action which is to reauthenticate.
int unrecoverable_error =
#if !BUILDFLAG(IS_CHROMEOS_ASH)
IDS_SYNC_STATUS_UNRECOVERABLE_ERROR;
#else
IDS_SYNC_STATUS_UNRECOVERABLE_ERROR_NEEDS_SIGNOUT;
#endif
EXPECT_THAT(
GetSyncStatusLabels(&service, environment.identity_manager(),
/*is_user_clear_primary_account_allowed=*/true),
SyncStatusLabelsMatch(SyncStatusMessageType::kSyncError,
unrecoverable_error, IDS_SYNC_RELOGIN_BUTTON,
SyncStatusActionType::kReauthenticate));
// This time set action to SyncStatusActionType::kUpgradeClient.
syncer::SyncStatus status;
status.sync_protocol_error.action = syncer::UPGRADE_CLIENT;
service.SetDetailedSyncStatus(true, status);
EXPECT_THAT(
GetSyncStatusLabels(&service, environment.identity_manager(),
/*is_user_clear_primary_account_allowed=*/true),
SyncStatusLabelsMatch(SyncStatusMessageType::kSyncError,
IDS_SYNC_UPGRADE_CLIENT,
IDS_SYNC_UPGRADE_CLIENT_BUTTON,
SyncStatusActionType::kUpgradeClient));
}
TEST(SyncUIUtilTest, ActionableProtocolErrorWithPassiveMessage) {
base::test::TaskEnvironment task_environment;
syncer::TestSyncService service;
signin::IdentityTestEnvironment environment;
environment.SetPrimaryAccount(kTestUser, signin::ConsentLevel::kSync);
service.SetInitialSyncFeatureSetupComplete(true);
service.SetDisableReasons(
{syncer::SyncService::DISABLE_REASON_UNRECOVERABLE_ERROR});
// Set action to SyncStatusActionType::kUpgradeClient.
syncer::SyncStatus status;
status.sync_protocol_error.action = syncer::UPGRADE_CLIENT;
service.SetDetailedSyncStatus(true, status);
// Expect a 'client upgrade' call to action.
EXPECT_THAT(
GetSyncStatusLabels(&service, environment.identity_manager(),
/*is_user_clear_primary_account_allowed=*/true),
SyncStatusLabelsMatch(SyncStatusMessageType::kSyncError,
IDS_SYNC_UPGRADE_CLIENT,
IDS_SYNC_UPGRADE_CLIENT_BUTTON,
SyncStatusActionType::kUpgradeClient));
}
TEST(SyncUIUtilTest, SyncSettingsConfirmationNeededTest) {
base::test::TaskEnvironment task_environment;
syncer::TestSyncService service;
signin::IdentityTestEnvironment environment;
environment.SetPrimaryAccount(kTestUser, signin::ConsentLevel::kSync);
service.SetInitialSyncFeatureSetupComplete(false);
ASSERT_TRUE(ShouldRequestSyncConfirmation(&service));
EXPECT_THAT(
GetSyncStatusLabels(&service, environment.identity_manager(),
/*is_user_clear_primary_account_allowed=*/true),
SyncStatusLabelsMatch(
SyncStatusMessageType::kSyncError, IDS_SYNC_SETTINGS_NOT_CONFIRMED,
IDS_SYNC_ERROR_USER_MENU_CONFIRM_SYNC_SETTINGS_BUTTON,
SyncStatusActionType::kConfirmSyncSettings));
}
// Errors in non-sync accounts should be ignored.
TEST(SyncUIUtilTest, IgnoreSyncErrorForNonSyncAccount) {
base::test::TaskEnvironment task_environment;
syncer::TestSyncService service;
signin::IdentityTestEnvironment environment;
const AccountInfo primary_account_info =
environment.MakePrimaryAccountAvailable(kTestUser,
signin::ConsentLevel::kSync);
service.SetAccountInfo(primary_account_info);
service.SetInitialSyncFeatureSetupComplete(true);
// Setup a secondary account.
const AccountInfo secondary_account_info =
environment.MakeAccountAvailable("secondary-user@example.com");
// Verify that we do not have any existing errors.
ASSERT_THAT(
GetSyncStatusLabels(&service, environment.identity_manager(),
/*is_user_clear_primary_account_allowed=*/true),
SyncStatusLabelsMatch(SyncStatusMessageType::kSynced,
IDS_SYNC_ACCOUNT_SYNCING, IDS_SETTINGS_EMPTY_STRING,
SyncStatusActionType::kNoAction));
// Add an error to the secondary account.
environment.UpdatePersistentErrorOfRefreshTokenForAccount(
secondary_account_info.account_id,
GoogleServiceAuthError(
GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS));
// Verify that we do not see any sign-in errors.
EXPECT_THAT(
GetSyncStatusLabels(&service, environment.identity_manager(),
/*is_user_clear_primary_account_allowed=*/true),
SyncStatusLabelsMatch(SyncStatusMessageType::kSynced,
IDS_SYNC_ACCOUNT_SYNCING, IDS_SETTINGS_EMPTY_STRING,
SyncStatusActionType::kNoAction));
}
TEST(SyncUIUtilTest, ShouldShowSyncPassphraseError) {
syncer::TestSyncService service;
service.SetInitialSyncFeatureSetupComplete(true);
service.SetPassphraseRequiredForPreferredDataTypes(true);
EXPECT_TRUE(ShouldShowSyncPassphraseError(&service));
}
TEST(SyncUIUtilTest, ShouldShowSyncPassphraseError_SyncDisabled) {
syncer::TestSyncService service;
service.SetInitialSyncFeatureSetupComplete(false);
service.SetPassphraseRequiredForPreferredDataTypes(true);
EXPECT_FALSE(ShouldShowSyncPassphraseError(&service));
}
TEST(SyncUIUtilTest, ShouldShowSyncPassphraseError_NotUsingPassphrase) {
syncer::TestSyncService service;
service.SetInitialSyncFeatureSetupComplete(true);
service.SetPassphraseRequiredForPreferredDataTypes(false);
EXPECT_FALSE(ShouldShowSyncPassphraseError(&service));
}
} // namespace