blob: fab58708597798236496f66972b3f90d0a9f9e33 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <set>
#include <string>
#include "base/bind.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/sync/sync_ui_util.h"
#include "chrome/test/base/testing_profile.h"
#include "components/sync/driver/test_sync_service.h"
#include "components/sync/engine/sync_engine.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "services/identity/public/cpp/identity_manager.h"
#include "services/identity/public/cpp/identity_test_environment.h"
#include "services/identity/public/cpp/identity_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using syncer::TestSyncService;
namespace {
// 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_PASSPHRASE_ERROR,
STATUS_CASE_CONFIRM_SYNC_SETTINGS,
STATUS_CASE_SYNCED,
STATUS_CASE_SYNC_DISABLED_BY_POLICY,
NUMBER_OF_STATUS_CASES
};
const char kTestUser[] = "test_user@test.com";
} // namespace
class SyncUIUtilTest : public testing::Test {
private:
content::TestBrowserThreadBundle thread_bundle_;
};
// 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 for the output argument |action_type| for each
// of the distinct cases.
sync_ui_util::ActionType GetDistinctCase(
TestSyncService* service,
identity::IdentityManager* identity_manager,
int case_number) {
// Auth Error object is returned by reference in mock and needs to stay in
// scope throughout test, so it is owned by calling method. However it is
// immutable so can only be allocated in this method.
switch (case_number) {
case STATUS_CASE_SETUP_IN_PROGRESS: {
service->SetFirstSetupComplete(false);
service->SetSetupInProgress(true);
service->SetDetailedSyncStatus(false, syncer::SyncEngine::Status());
return sync_ui_util::NO_ACTION;
}
case STATUS_CASE_SETUP_ERROR: {
service->SetFirstSetupComplete(false);
service->SetSetupInProgress(false);
service->SetDisableReasons(
syncer::SyncService::DISABLE_REASON_UNRECOVERABLE_ERROR);
service->SetDetailedSyncStatus(false, syncer::SyncEngine::Status());
return sync_ui_util::REAUTHENTICATE;
}
case STATUS_CASE_AUTH_ERROR: {
service->SetFirstSetupComplete(true);
service->SetTransportState(syncer::SyncService::TransportState::ACTIVE);
service->SetPassphraseRequired(false);
service->SetDetailedSyncStatus(false, syncer::SyncEngine::Status());
// Make sure to fail authentication with an error in this case.
std::string account_id = identity_manager->GetPrimaryAccountId();
identity::SetRefreshTokenForPrimaryAccount(identity_manager);
identity::UpdatePersistentErrorOfRefreshTokenForAccount(
identity_manager, account_id,
GoogleServiceAuthError(GoogleServiceAuthError::State::SERVICE_ERROR));
service->SetDisableReasons(syncer::SyncService::DISABLE_REASON_NONE);
return sync_ui_util::REAUTHENTICATE;
}
case STATUS_CASE_PROTOCOL_ERROR: {
service->SetFirstSetupComplete(true);
service->SetTransportState(syncer::SyncService::TransportState::ACTIVE);
service->SetPassphraseRequired(false);
syncer::SyncProtocolError protocol_error;
protocol_error.action = syncer::UPGRADE_CLIENT;
syncer::SyncEngine::Status status;
status.sync_protocol_error = protocol_error;
service->SetDetailedSyncStatus(false, status);
service->SetDisableReasons(syncer::SyncService::DISABLE_REASON_NONE);
return sync_ui_util::UPGRADE_CLIENT;
}
case STATUS_CASE_CONFIRM_SYNC_SETTINGS: {
service->SetFirstSetupComplete(false);
service->SetPassphraseRequired(false);
service->SetDetailedSyncStatus(false, syncer::SyncEngine::Status());
return sync_ui_util::CONFIRM_SYNC_SETTINGS;
}
case STATUS_CASE_PASSPHRASE_ERROR: {
service->SetFirstSetupComplete(true);
service->SetTransportState(syncer::SyncService::TransportState::ACTIVE);
service->SetDetailedSyncStatus(false, syncer::SyncEngine::Status());
service->SetDisableReasons(syncer::SyncService::DISABLE_REASON_NONE);
service->SetPassphraseRequired(true);
service->SetPassphraseRequiredForDecryption(true);
return sync_ui_util::ENTER_PASSPHRASE;
}
case STATUS_CASE_SYNCED: {
service->SetFirstSetupComplete(true);
service->SetTransportState(syncer::SyncService::TransportState::ACTIVE);
service->SetDetailedSyncStatus(false, syncer::SyncEngine::Status());
service->SetDisableReasons(syncer::SyncService::DISABLE_REASON_NONE);
service->SetPassphraseRequired(false);
return sync_ui_util::NO_ACTION;
}
case STATUS_CASE_SYNC_DISABLED_BY_POLICY: {
service->SetDisableReasons(
syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY);
service->SetFirstSetupComplete(false);
service->SetTransportState(syncer::SyncService::TransportState::DISABLED);
service->SetPassphraseRequired(false);
service->SetDetailedSyncStatus(false, syncer::SyncEngine::Status());
return sync_ui_util::NO_ACTION;
}
case NUMBER_OF_STATUS_CASES:
NOTREACHED();
}
return sync_ui_util::NO_ACTION;
}
std::unique_ptr<KeyedService> BuildTestSyncService(
content::BrowserContext* context) {
return std::make_unique<TestSyncService>();
}
std::unique_ptr<TestingProfile> BuildTestingProfile() {
return IdentityTestEnvironmentProfileAdaptor::
CreateProfileForIdentityTestEnvironment(
{{ProfileSyncServiceFactory::GetInstance(),
base::BindRepeating(&BuildTestSyncService)}});
}
std::unique_ptr<TestingProfile> BuildSignedInTestingProfile() {
std::unique_ptr<TestingProfile> profile = BuildTestingProfile();
IdentityTestEnvironmentProfileAdaptor identity_adaptor(profile.get());
identity_adaptor.identity_test_env()->SetPrimaryAccount(kTestUser);
return profile;
}
// This test ensures that each distinctive SyncService status will return a
// unique combination of status and link messages from GetStatusLabels().
TEST_F(SyncUIUtilTest, DistinctCasesReportUniqueMessageSets) {
std::set<base::string16> messages;
for (int idx = 0; idx != NUMBER_OF_STATUS_CASES; idx++) {
std::unique_ptr<Profile> profile = BuildTestingProfile();
IdentityTestEnvironmentProfileAdaptor env_adaptor(profile.get());
identity::IdentityTestEnvironment* environment =
env_adaptor.identity_test_env();
identity::IdentityManager* identity_manager =
environment->identity_manager();
// Need a primary account signed in before calling GetDistinctCase().
environment->MakePrimaryAccountAvailable(kTestUser);
TestSyncService* service = static_cast<TestSyncService*>(
ProfileSyncServiceFactory::GetSyncServiceForProfile(profile.get()));
sync_ui_util::ActionType expected_action_type =
GetDistinctCase(service, identity_manager, idx);
base::string16 status_label;
base::string16 link_label;
sync_ui_util::ActionType action_type = sync_ui_util::NO_ACTION;
sync_ui_util::GetStatusLabels(profile.get(), &status_label, &link_label,
&action_type);
EXPECT_EQ(expected_action_type, action_type)
<< "Wrong action returned for case #" << idx;
// If the status and link message combination is already present in the set
// of messages already seen, this is a duplicate rather than a unique
// message, and the test has failed.
EXPECT_FALSE(status_label.empty())
<< "Empty status label returned for case #" << idx;
// Ensures a search for string 'href' (found in links, not a string to be
// found in an English language message) fails, since links are excluded
// from the status label.
EXPECT_EQ(status_label.find(base::ASCIIToUTF16("href")),
base::string16::npos);
base::string16 combined_label =
status_label + base::ASCIIToUTF16("#") + link_label;
EXPECT_TRUE(messages.find(combined_label) == messages.end()) <<
"Duplicate message for case #" << idx << ": " << combined_label;
messages.insert(combined_label);
}
}
TEST_F(SyncUIUtilTest, UnrecoverableErrorWithActionableError) {
std::unique_ptr<Profile> profile = BuildSignedInTestingProfile();
TestSyncService* service = static_cast<TestSyncService*>(
ProfileSyncServiceFactory::GetSyncServiceForProfile(profile.get()));
service->SetFirstSetupComplete(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());
base::string16 link_label;
base::string16 unrecoverable_error_status_label;
sync_ui_util::ActionType action_type = sync_ui_util::NO_ACTION;
sync_ui_util::GetStatusLabels(profile.get(),
&unrecoverable_error_status_label, &link_label,
&action_type);
// Expect the generic unrecoverable error action which is to reauthenticate.
EXPECT_EQ(sync_ui_util::REAUTHENTICATE, action_type);
// This time set action to UPGRADE_CLIENT. Ensure that status label differs
// from previous one.
syncer::SyncStatus status;
status.sync_protocol_error.action = syncer::UPGRADE_CLIENT;
service->SetDetailedSyncStatus(true, status);
base::string16 upgrade_client_status_label;
sync_ui_util::GetStatusLabels(profile.get(), &upgrade_client_status_label,
&link_label, &action_type);
// Expect an explicit 'client upgrade' action.
EXPECT_EQ(sync_ui_util::UPGRADE_CLIENT, action_type);
EXPECT_NE(unrecoverable_error_status_label, upgrade_client_status_label);
}
TEST_F(SyncUIUtilTest, ActionableErrorWithPassiveMessage) {
std::unique_ptr<Profile> profile = BuildSignedInTestingProfile();
TestSyncService* service = static_cast<TestSyncService*>(
ProfileSyncServiceFactory::GetSyncServiceForProfile(profile.get()));
service->SetFirstSetupComplete(true);
service->SetDisableReasons(
syncer::SyncService::DISABLE_REASON_UNRECOVERABLE_ERROR);
// Set action to UPGRADE_CLIENT.
syncer::SyncStatus status;
status.sync_protocol_error.action = syncer::UPGRADE_CLIENT;
service->SetDetailedSyncStatus(true, status);
base::string16 first_actionable_error_status_label;
base::string16 link_label;
sync_ui_util::ActionType action_type = sync_ui_util::NO_ACTION;
sync_ui_util::GetStatusLabels(profile.get(),
&first_actionable_error_status_label,
&link_label, &action_type);
// Expect a 'client upgrade' call to action.
EXPECT_EQ(sync_ui_util::UPGRADE_CLIENT, action_type);
// This time set action to ENABLE_SYNC_ON_ACCOUNT.
status.sync_protocol_error.action = syncer::ENABLE_SYNC_ON_ACCOUNT;
service->SetDetailedSyncStatus(true, status);
base::string16 second_actionable_error_status_label;
action_type = sync_ui_util::NO_ACTION;
sync_ui_util::GetStatusLabels(profile.get(),
&second_actionable_error_status_label,
&link_label, &action_type);
// Expect a passive message instead of a call to action.
EXPECT_EQ(sync_ui_util::NO_ACTION, action_type);
EXPECT_NE(first_actionable_error_status_label,
second_actionable_error_status_label);
}
TEST_F(SyncUIUtilTest, SyncSettingsConfirmationNeededTest) {
std::unique_ptr<Profile> profile = BuildSignedInTestingProfile();
TestSyncService* service = static_cast<TestSyncService*>(
ProfileSyncServiceFactory::GetSyncServiceForProfile(profile.get()));
service->SetFirstSetupComplete(false);
ASSERT_TRUE(sync_ui_util::ShouldRequestSyncConfirmation(service));
base::string16 actionable_error_status_label;
base::string16 link_label;
sync_ui_util::ActionType action_type = sync_ui_util::NO_ACTION;
sync_ui_util::GetStatusLabels(profile.get(), &actionable_error_status_label,
&link_label, &action_type);
EXPECT_EQ(action_type, sync_ui_util::CONFIRM_SYNC_SETTINGS);
}