blob: 88bf56bc469c9b0e5ce9c43cfb63bfc8bc22a5c5 [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 <stddef.h>
#include <memory>
#include <set>
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/account_tracker_service_factory.h"
#include "chrome/browser/signin/chrome_signin_client_factory.h"
#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
#include "chrome/browser/signin/signin_error_controller_factory.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/sync/profile_sync_test_util.h"
#include "chrome/browser/sync/sync_ui_util.h"
#include "chrome/test/base/testing_profile.h"
#include "components/browser_sync/profile_sync_service_mock.h"
#include "components/signin/core/browser/fake_signin_manager.h"
#include "components/signin/core/browser/profile_oauth2_token_service.h"
#include "components/signin/core/browser/signin_manager.h"
#include "content/public/test/test_browser_thread.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "google_apis/gaia/oauth2_token_service_delegate.h"
#include "testing/gmock/include/gmock/gmock-actions.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::AtMost;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::SetArgPointee;
using ::testing::_;
using browser_sync::ProfileSyncService;
using browser_sync::ProfileSyncServiceMock;
using content::BrowserThread;
// A number of distinct states of the ProfileSyncService can be generated for
// tests.
enum DistinctState {
STATUS_CASE_SETUP_IN_PROGRESS,
STATUS_CASE_SETUP_ERROR,
STATUS_CASE_AUTHENTICATING,
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
};
namespace {
const char kTestGaiaId[] = "gaia-id-test_user@test.com";
const char kTestUser[] = "test_user@test.com";
} // namespace
class SyncUIUtilTest : public testing::Test {
private:
content::TestBrowserThreadBundle thread_bundle_;
};
// TODO(tim): This shouldn't be required. r194857 removed the
// AuthInProgress override from FakeSigninManager, which meant this test started
// using the "real" SigninManager AuthInProgress logic. Without that override,
// it's no longer possible to test both chrome os + desktop flows as part of the
// same test, because AuthInProgress is always false on chrome os. Most of the
// tests are unaffected, but STATUS_CASE_AUTHENTICATING can't exist in both
// versions, so it we will require two separate tests, one using SigninManager
// and one using SigninManagerBase (which require different setup procedures.
class FakeSigninManagerForSyncUIUtilTest : public FakeSigninManagerBase {
public:
explicit FakeSigninManagerForSyncUIUtilTest(Profile* profile)
: FakeSigninManagerBase(
ChromeSigninClientFactory::GetForProfile(profile),
AccountTrackerServiceFactory::GetForProfile(profile),
SigninErrorControllerFactory::GetForProfile(profile)),
auth_in_progress_(false) {
Initialize(nullptr);
}
~FakeSigninManagerForSyncUIUtilTest() override {}
bool AuthInProgress() const override { return auth_in_progress_; }
void set_auth_in_progress() {
auth_in_progress_ = true;
}
private:
bool auth_in_progress_;
};
// Loads a ProfileSyncServiceMock to emulate one of a number of distinct cases
// in order to perform tests on the generated messages.
void GetDistinctCase(ProfileSyncServiceMock* service,
FakeSigninManagerForSyncUIUtilTest* signin,
ProfileOAuth2TokenService* token_service,
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: {
EXPECT_CALL(*service, IsFirstSetupComplete())
.WillRepeatedly(Return(false));
EXPECT_CALL(*service, IsSetupInProgress()).WillRepeatedly(Return(true));
syncer::SyncEngine::Status status;
EXPECT_CALL(*service, QueryDetailedSyncStatus(_))
.WillRepeatedly(DoAll(SetArgPointee<0>(status), Return(false)));
return;
}
case STATUS_CASE_SETUP_ERROR: {
EXPECT_CALL(*service, IsFirstSetupComplete())
.WillRepeatedly(Return(false));
EXPECT_CALL(*service, IsSetupInProgress()).WillRepeatedly(Return(false));
EXPECT_CALL(*service, GetDisableReasons())
.WillRepeatedly(
Return(syncer::SyncService::DISABLE_REASON_UNRECOVERABLE_ERROR));
syncer::SyncEngine::Status status;
EXPECT_CALL(*service, QueryDetailedSyncStatus(_))
.WillRepeatedly(DoAll(SetArgPointee<0>(status), Return(false)));
return;
}
case STATUS_CASE_AUTHENTICATING: {
EXPECT_CALL(*service, IsFirstSetupComplete())
.WillRepeatedly(Return(true));
EXPECT_CALL(*service, GetTransportState())
.WillRepeatedly(Return(syncer::SyncService::TransportState::ACTIVE));
EXPECT_CALL(*service, IsPassphraseRequired())
.WillRepeatedly(Return(false));
syncer::SyncEngine::Status status;
EXPECT_CALL(*service, QueryDetailedSyncStatus(_))
.WillRepeatedly(DoAll(SetArgPointee<0>(status), Return(false)));
EXPECT_CALL(*service, GetDisableReasons())
.WillRepeatedly(Return(syncer::SyncService::DISABLE_REASON_NONE));
signin->set_auth_in_progress();
return;
}
case STATUS_CASE_AUTH_ERROR: {
EXPECT_CALL(*service, IsFirstSetupComplete())
.WillRepeatedly(Return(true));
EXPECT_CALL(*service, GetTransportState())
.WillRepeatedly(Return(syncer::SyncService::TransportState::ACTIVE));
EXPECT_CALL(*service, IsPassphraseRequired())
.WillRepeatedly(Return(false));
syncer::SyncEngine::Status status;
EXPECT_CALL(*service, QueryDetailedSyncStatus(_))
.WillRepeatedly(DoAll(SetArgPointee<0>(status), Return(false)));
std::string account_id = signin->GetAuthenticatedAccountId();
token_service->UpdateCredentials(account_id, "refresh_token");
// TODO(https://crbug.com/836212): Do not use the delegate directly,
// because it is internal API.
token_service->GetDelegate()->UpdateAuthError(
account_id,
GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR));
EXPECT_CALL(*service, GetDisableReasons())
.WillRepeatedly(Return(syncer::SyncService::DISABLE_REASON_NONE));
return;
}
case STATUS_CASE_PROTOCOL_ERROR: {
EXPECT_CALL(*service, IsFirstSetupComplete())
.WillRepeatedly(Return(true));
EXPECT_CALL(*service, GetTransportState())
.WillRepeatedly(Return(syncer::SyncService::TransportState::ACTIVE));
EXPECT_CALL(*service, IsPassphraseRequired())
.WillRepeatedly(Return(false));
syncer::SyncProtocolError protocolError;
protocolError.action = syncer::UPGRADE_CLIENT;
syncer::SyncEngine::Status status;
status.sync_protocol_error = protocolError;
EXPECT_CALL(*service, QueryDetailedSyncStatus(_))
.WillRepeatedly(DoAll(SetArgPointee<0>(status), Return(false)));
EXPECT_CALL(*service, GetDisableReasons())
.WillRepeatedly(Return(syncer::SyncService::DISABLE_REASON_NONE));
return;
}
case STATUS_CASE_CONFIRM_SYNC_SETTINGS: {
EXPECT_CALL(*service, IsPassphraseRequired())
.WillRepeatedly(Return(false));
syncer::SyncEngine::Status status;
EXPECT_CALL(*service, QueryDetailedSyncStatus(_))
.WillRepeatedly(DoAll(SetArgPointee<0>(status), Return(false)));
return;
}
case STATUS_CASE_PASSPHRASE_ERROR: {
EXPECT_CALL(*service, IsFirstSetupComplete())
.WillRepeatedly(Return(true));
EXPECT_CALL(*service, GetTransportState())
.WillRepeatedly(Return(syncer::SyncService::TransportState::ACTIVE));
syncer::SyncEngine::Status status;
EXPECT_CALL(*service, QueryDetailedSyncStatus(_))
.WillRepeatedly(DoAll(SetArgPointee<0>(status), Return(false)));
EXPECT_CALL(*service, GetDisableReasons())
.WillRepeatedly(Return(syncer::SyncService::DISABLE_REASON_NONE));
EXPECT_CALL(*service, IsPassphraseRequired())
.WillRepeatedly(Return(true));
EXPECT_CALL(*service, IsPassphraseRequiredForDecryption())
.WillRepeatedly(Return(true));
return;
}
case STATUS_CASE_SYNCED: {
EXPECT_CALL(*service, IsFirstSetupComplete())
.WillRepeatedly(Return(true));
EXPECT_CALL(*service, GetTransportState())
.WillRepeatedly(Return(syncer::SyncService::TransportState::ACTIVE));
EXPECT_CALL(*service, IsPassphraseRequired())
.WillRepeatedly(Return(false));
syncer::SyncEngine::Status status;
EXPECT_CALL(*service, QueryDetailedSyncStatus(_))
.WillRepeatedly(DoAll(SetArgPointee<0>(status), Return(false)));
EXPECT_CALL(*service, GetDisableReasons())
.WillRepeatedly(Return(syncer::SyncService::DISABLE_REASON_NONE));
EXPECT_CALL(*service, IsPassphraseRequired())
.WillRepeatedly(Return(false));
return;
}
case STATUS_CASE_SYNC_DISABLED_BY_POLICY: {
EXPECT_CALL(*service, GetDisableReasons())
.WillRepeatedly(
Return(syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY));
EXPECT_CALL(*service, IsFirstSetupComplete())
.WillRepeatedly(Return(false));
EXPECT_CALL(*service, GetTransportState())
.WillRepeatedly(
Return(syncer::SyncService::TransportState::DISABLED));
EXPECT_CALL(*service, IsPassphraseRequired())
.WillRepeatedly(Return(false));
syncer::SyncEngine::Status status;
EXPECT_CALL(*service, QueryDetailedSyncStatus(_))
.WillRepeatedly(DoAll(SetArgPointee<0>(status), Return(false)));
return;
}
default:
NOTREACHED();
}
}
// Returns the expected value for the output argument |action_type| for each
// of the distinct cases.
sync_ui_util::ActionType GetActionTypeforDistinctCase(int case_number) {
switch (case_number) {
case STATUS_CASE_SETUP_IN_PROGRESS:
return sync_ui_util::NO_ACTION;
case STATUS_CASE_SETUP_ERROR:
return sync_ui_util::REAUTHENTICATE;
case STATUS_CASE_AUTHENTICATING:
return sync_ui_util::NO_ACTION;
case STATUS_CASE_AUTH_ERROR:
return sync_ui_util::REAUTHENTICATE;
case STATUS_CASE_PROTOCOL_ERROR:
return sync_ui_util::UPGRADE_CLIENT;
case STATUS_CASE_PASSPHRASE_ERROR:
return sync_ui_util::ENTER_PASSPHRASE;
case STATUS_CASE_CONFIRM_SYNC_SETTINGS:
return sync_ui_util::CONFIRM_SYNC_SETTINGS;
case STATUS_CASE_SYNCED:
return sync_ui_util::NO_ACTION;
case STATUS_CASE_SYNC_DISABLED_BY_POLICY:
return sync_ui_util::NO_ACTION;
default:
NOTREACHED();
return sync_ui_util::NO_ACTION;
}
}
// This test ensures that a each distinctive ProfileSyncService statuses
// 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 = std::make_unique<TestingProfile>();
NiceMock<ProfileSyncServiceMock> service(
CreateProfileSyncServiceParamsForTest(profile.get()));
GoogleServiceAuthError error = GoogleServiceAuthError::AuthErrorNone();
EXPECT_CALL(service, GetAuthError()).WillRepeatedly(ReturnRef(error));
FakeSigninManagerForSyncUIUtilTest signin(profile.get());
signin.SetAuthenticatedAccountInfo(kTestGaiaId, kTestUser);
ProfileOAuth2TokenService* token_service =
ProfileOAuth2TokenServiceFactory::GetForProfile(profile.get());
GetDistinctCase(&service, &signin, token_service, 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(), &service, signin,
&status_label, &link_label, &action_type);
EXPECT_EQ(GetActionTypeforDistinctCase(idx), action_type);
// 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);
testing::Mock::VerifyAndClearExpectations(&service);
testing::Mock::VerifyAndClearExpectations(&signin);
EXPECT_CALL(service, GetAuthError()).WillRepeatedly(ReturnRef(error));
signin.Shutdown();
}
}
TEST_F(SyncUIUtilTest, UnrecoverableErrorWithActionableError) {
std::unique_ptr<Profile> profile(MakeSignedInTestingProfile());
SigninManagerBase* signin =
SigninManagerFactory::GetForProfile(profile.get());
ProfileSyncServiceMock service(
CreateProfileSyncServiceParamsForTest(profile.get()));
EXPECT_CALL(service, IsFirstSetupComplete()).WillRepeatedly(Return(true));
EXPECT_CALL(service, GetDisableReasons())
.WillRepeatedly(
Return(syncer::SyncService::DISABLE_REASON_UNRECOVERABLE_ERROR));
// First time action is not set. We should get unrecoverable error.
syncer::SyncStatus status;
EXPECT_CALL(service, QueryDetailedSyncStatus(_))
.WillOnce(DoAll(SetArgPointee<0>(status), Return(true)));
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(), &service, *signin,
&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.
status.sync_protocol_error.action = syncer::UPGRADE_CLIENT;
EXPECT_CALL(service, QueryDetailedSyncStatus(_))
.WillOnce(DoAll(SetArgPointee<0>(status), Return(true)));
base::string16 upgrade_client_status_label;
sync_ui_util::GetStatusLabels(profile.get(), &service, *signin,
&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(MakeSignedInTestingProfile());
SigninManagerBase* signin =
SigninManagerFactory::GetForProfile(profile.get());
ProfileSyncServiceMock service(
CreateProfileSyncServiceParamsForTest(profile.get()));
EXPECT_CALL(service, IsFirstSetupComplete()).WillRepeatedly(Return(true));
EXPECT_CALL(service, GetDisableReasons())
.WillRepeatedly(
Return(syncer::SyncService::DISABLE_REASON_UNRECOVERABLE_ERROR));
// Set action to UPGRADE_CLIENT.
syncer::SyncStatus status;
status.sync_protocol_error.action = syncer::UPGRADE_CLIENT;
EXPECT_CALL(service, QueryDetailedSyncStatus(_))
.WillOnce(DoAll(SetArgPointee<0>(status), Return(true)));
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(), &service, *signin,
&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;
EXPECT_CALL(service, QueryDetailedSyncStatus(_))
.WillOnce(DoAll(SetArgPointee<0>(status), Return(true)));
base::string16 second_actionable_error_status_label;
action_type = sync_ui_util::NO_ACTION;
sync_ui_util::GetStatusLabels(profile.get(), &service, *signin,
&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(MakeSignedInTestingProfile());
SigninManagerBase* signin =
SigninManagerFactory::GetForProfile(profile.get());
NiceMock<ProfileSyncServiceMock> service(
CreateProfileSyncServiceParamsForTest(profile.get()));
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(), &service, *signin,
&actionable_error_status_label, &link_label,
&action_type);
EXPECT_EQ(action_type, sync_ui_util::CONFIRM_SYNC_SETTINGS);
}