blob: a5753993d9ae4721efa84ee1fb8cda50601eb2be [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "build/buildflag.h"
#include "chrome/browser/reading_list/reading_list_model_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/sync/test/integration/bookmarks_helper.h"
#include "chrome/browser/sync/test/integration/preferences_helper.h"
#include "chrome/browser/sync/test/integration/single_client_status_change_checker.h"
#include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/sync/test/integration/updated_progress_marker_checker.h"
#include "chrome/common/pref_names.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/prefs/pref_service.h"
#include "components/reading_list/core/reading_list_model.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/sync/service/sync_service_impl.h"
#include "components/sync/service/sync_token_status.h"
#include "content/public/test/browser_test.h"
#include "google_apis/gaia/fake_oauth2_token_response.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "google_apis/gaia/oauth2_response.h"
#include "net/base/net_errors.h"
#include "net/http/http_status_code.h"
namespace {
bool HasUserPrefValue(const PrefService* pref_service,
const std::string& pref) {
return pref_service->GetUserPrefValue(pref) != nullptr;
}
// Waits until local changes are committed or an auth error is encountered.
class TestForAuthError : public UpdatedProgressMarkerChecker {
public:
static bool HasAuthError(syncer::SyncService* service) {
// Note that depending on the nature of the auth error, sync may become
// paused (for persistent auth errors) or in some other cases transient
// errors may be surfaced via GetSyncTokenStatusForDebugging() (e.g. 401
// HTTP status codes returned by the Sync server when the access token has
// expired).
return service->GetTransportState() ==
syncer::SyncService::TransportState::PAUSED ||
service->GetSyncTokenStatusForDebugging()
.last_get_token_error.state() !=
GoogleServiceAuthError::NONE;
}
explicit TestForAuthError(syncer::SyncServiceImpl* service)
: UpdatedProgressMarkerChecker(service) {}
// StatusChangeChecker implementation.
bool IsExitConditionSatisfied(std::ostream* os) override {
*os << "Waiting for auth error";
return HasAuthError(service()) ||
UpdatedProgressMarkerChecker::IsExitConditionSatisfied(os);
}
};
class SyncAuthTestBase : public SyncTest {
public:
explicit SyncAuthTestBase(SetupSyncMode setup_sync_mode)
: SyncTest(SINGLE_CLIENT) {
if (setup_sync_mode == SetupSyncMode::kSyncTransportOnly) {
scoped_feature_list_.InitAndEnableFeature(
syncer::kReplaceSyncPromosWithSignInPromos);
}
}
SyncAuthTestBase(const SyncAuthTestBase&) = delete;
SyncAuthTestBase& operator=(const SyncAuthTestBase&) = delete;
~SyncAuthTestBase() override = default;
// Helper function that adds a reading list entry and waits for either an auth
// error, or for the entry to be committed. Returns true if it detects an
// auth error, false if the entry is committed successfully.
bool AttemptToTriggerAuthError() {
int index = GetNextEntryIndex();
std::string title = base::StringPrintf("Entry %d", index);
GURL url = GURL(base::StringPrintf("http://www.foo%d.com", index));
ReadingListModel* model =
ReadingListModelFactory::GetForBrowserContext(GetProfile(0));
model->AddOrReplaceEntry(url, title, reading_list::ADDED_VIA_CURRENT_APP,
/*estimated_read_time=*/std::nullopt,
/*creation_time=*/std::nullopt);
// Run until the entry is committed or an auth error is encountered.
TestForAuthError(GetSyncService(0)).Wait();
return TestForAuthError::HasAuthError(GetSyncService(0));
}
void DisableTokenFetchRetries() {
// If SyncServiceImpl observes a transient error like SERVICE_UNAVAILABLE
// or CONNECTION_FAILED, this means the access token fetcher has given
// up trying to reach Gaia. In practice, the access token fetching code
// retries a fixed number of times, but the count is transparent to PSS.
// Disable retries so that we instantly trigger the case where
// SyncServiceImpl must pick up where the access token fetcher left off
// (in terms of retries).
signin::DisableAccessTokenFetchRetries(
IdentityManagerFactory::GetForProfile(GetProfile(0)));
}
private:
int GetNextEntryIndex() { return entry_index_++; }
base::test::ScopedFeatureList scoped_feature_list_;
int entry_index_ = 0;
};
class SyncAuthTest
: public SyncAuthTestBase,
public testing::WithParamInterface<SyncTest::SetupSyncMode> {
public:
SyncAuthTest() : SyncAuthTestBase(GetSetupSyncMode()) {}
SyncTest::SetupSyncMode GetSetupSyncMode() const override {
return GetParam();
}
};
INSTANTIATE_TEST_SUITE_P(,
SyncAuthTest,
GetSyncTestModes(),
testing::PrintToStringParamName());
// Verify that sync works with a valid OAuth2 token.
IN_PROC_BROWSER_TEST_P(SyncAuthTest, Sanity) {
ASSERT_TRUE(SetupSync());
GetFakeServer()->ClearHttpError();
DisableTokenFetchRetries();
SetOAuth2TokenResponse(
gaia::FakeOAuth2TokenResponse::Success("new_access_token"));
ASSERT_FALSE(AttemptToTriggerAuthError());
}
// Verify that SyncServiceImpl continues trying to fetch access tokens
// when the access token fetcher has encountered more than a fixed number of
// HTTP_INTERNAL_SERVER_ERROR (500) errors.
IN_PROC_BROWSER_TEST_P(SyncAuthTest, RetryOnInternalServerError500) {
ASSERT_TRUE(SetupSync());
ASSERT_FALSE(AttemptToTriggerAuthError());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(gaia::FakeOAuth2TokenResponse::OAuth2Error(
OAuth2Response::kInternalFailure));
ASSERT_TRUE(AttemptToTriggerAuthError());
EXPECT_EQ(GetSyncService(0)->GetTransportState(),
syncer::SyncService::TransportState::ACTIVE);
EXPECT_TRUE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
}
class SyncAuthTokenFetcherDependentTest
: public SyncAuthTestBase,
public testing::WithParamInterface<
std::tuple<bool, SyncTest::SetupSyncMode>> {
public:
SyncAuthTokenFetcherDependentTest() : SyncAuthTestBase(GetSetupSyncMode()) {
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
scoped_feature_list_.InitWithFeatureState(
switches::kUseIssueTokenToFetchAccessTokens, IsIssueTokenEnabled());
#else
CHECK(!IsIssueTokenEnabled());
#endif
}
SyncTest::SetupSyncMode GetSetupSyncMode() const override {
return std::get<1>(GetParam());
}
bool IsIssueTokenEnabled() const { return std::get<0>(GetParam()); }
private:
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
base::test::ScopedFeatureList scoped_feature_list_;
#endif
};
// Verifies the behavior when the access token fetcher encounters an
// HTTP_FORBIDDEN (403) error. With IssueToken this should result in a PAUSED
// state with SERVICE_ERROR auth error, whereas with GetToken it should keep
// retrying.
IN_PROC_BROWSER_TEST_P(SyncAuthTokenFetcherDependentTest, HttpForbidden403) {
ASSERT_TRUE(SetupSync());
ASSERT_FALSE(AttemptToTriggerAuthError());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(gaia::FakeOAuth2TokenResponse::OAuth2Error(
OAuth2Response::kErrorUnexpectedFormat, net::HTTP_FORBIDDEN));
ASSERT_TRUE(AttemptToTriggerAuthError());
EXPECT_EQ(GetSyncService(0)->GetTransportState(),
IsIssueTokenEnabled()
? syncer::SyncService::TransportState::PAUSED
: syncer::SyncService::TransportState::ACTIVE);
EXPECT_EQ(GetSyncService(0)->GetAuthError().state(),
IsIssueTokenEnabled() ? GoogleServiceAuthError::SERVICE_ERROR
: GoogleServiceAuthError::NONE);
EXPECT_EQ(GetSyncService(0)->IsRetryingAccessTokenFetchForTest(),
!IsIssueTokenEnabled());
}
// Verifies the behavior when the access token fetcher receives a malformed
// token. With IssueToken this should result in a PAUSED state with
// UNEXPECTED_SERVICE_RESPONSE auth error, whereas with GetToken it should keep
// retrying.
IN_PROC_BROWSER_TEST_P(SyncAuthTokenFetcherDependentTest, MalformedToken) {
ASSERT_TRUE(SetupSync());
ASSERT_FALSE(AttemptToTriggerAuthError());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(gaia::FakeOAuth2TokenResponse::OAuth2Error(
OAuth2Response::kOkUnexpectedFormat));
ASSERT_TRUE(AttemptToTriggerAuthError());
EXPECT_EQ(GetSyncService(0)->GetTransportState(),
IsIssueTokenEnabled()
? syncer::SyncService::TransportState::PAUSED
: syncer::SyncService::TransportState::ACTIVE);
EXPECT_EQ(GetSyncService(0)->GetAuthError().state(),
IsIssueTokenEnabled()
? GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE
: GoogleServiceAuthError::NONE);
EXPECT_EQ(GetSyncService(0)->IsRetryingAccessTokenFetchForTest(),
!IsIssueTokenEnabled());
}
INSTANTIATE_TEST_SUITE_P(
,
SyncAuthTokenFetcherDependentTest,
testing::Combine(testing::Values(false
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
,
true
#endif
),
GetSyncTestModes()),
[](const testing::TestParamInfo<std::tuple<bool, SyncTest::SetupSyncMode>>&
info) {
return (std::get<0>(info.param) ? "WithIssueToken_" : "WithGetToken_") +
(SetupSyncModeAsString(std::get<1>(info.param)));
});
// Verify that SyncServiceImpl continues trying to fetch access tokens
// when the access token fetcher has encountered a URLRequestStatus of FAILED.
IN_PROC_BROWSER_TEST_P(SyncAuthTest, RetryOnRequestFailed) {
ASSERT_TRUE(SetupSync());
ASSERT_FALSE(AttemptToTriggerAuthError());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(
gaia::FakeOAuth2TokenResponse::NetError(net::ERR_FAILED));
ASSERT_TRUE(AttemptToTriggerAuthError());
EXPECT_EQ(GetSyncService(0)->GetTransportState(),
syncer::SyncService::TransportState::ACTIVE);
EXPECT_TRUE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
}
// Verify that SyncServiceImpl continues trying to fetch access tokens
// when the access token fetcher has encountered more than a fixed number of
// rate limit exceeded errors.
IN_PROC_BROWSER_TEST_P(SyncAuthTest, RetryOnRateLimitExceeded) {
ASSERT_TRUE(SetupSync());
ASSERT_FALSE(AttemptToTriggerAuthError());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(gaia::FakeOAuth2TokenResponse::OAuth2Error(
OAuth2Response::kRateLimitExceeded));
ASSERT_TRUE(AttemptToTriggerAuthError());
EXPECT_EQ(GetSyncService(0)->GetTransportState(),
syncer::SyncService::TransportState::ACTIVE);
EXPECT_TRUE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
}
// Verify that SyncServiceImpl ends up with an INVALID_GAIA_CREDENTIALS auth
// error when an invalid_grant error is returned by the access token fetcher
// with an HTTP_BAD_REQUEST (400) response code.
IN_PROC_BROWSER_TEST_P(SyncAuthTest, InvalidGrant) {
ASSERT_TRUE(SetupSync());
ASSERT_FALSE(AttemptToTriggerAuthError());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(gaia::FakeOAuth2TokenResponse::OAuth2Error(
OAuth2Response::kInvalidGrant));
ASSERT_TRUE(AttemptToTriggerAuthError());
EXPECT_EQ(GetSyncService(0)->GetTransportState(),
syncer::SyncService::TransportState::PAUSED);
EXPECT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
GetSyncService(0)->GetAuthError().state());
EXPECT_FALSE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
}
// Verify that SyncServiceImpl does not retry after SERVICE_ERROR auth error
// when an invalid_client error is returned by the access token fetcher with an
// HTTP_BAD_REQUEST (400) response code.
IN_PROC_BROWSER_TEST_P(SyncAuthTest, InvalidClient) {
ASSERT_TRUE(SetupSync());
ASSERT_FALSE(AttemptToTriggerAuthError());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(gaia::FakeOAuth2TokenResponse::OAuth2Error(
OAuth2Response::kInvalidClient));
ASSERT_TRUE(AttemptToTriggerAuthError());
EXPECT_EQ(GetSyncService(0)->GetTransportState(),
syncer::SyncService::TransportState::PAUSED);
EXPECT_EQ(GoogleServiceAuthError::SERVICE_ERROR,
GetSyncService(0)->GetAuthError().state());
EXPECT_FALSE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
}
// Verify that SyncServiceImpl retries after REQUEST_CANCELED auth error
// when the access token fetcher has encountered a URLRequestStatus of
// CANCELED.
IN_PROC_BROWSER_TEST_P(SyncAuthTest, RetryRequestCanceled) {
ASSERT_TRUE(SetupSync());
ASSERT_FALSE(AttemptToTriggerAuthError());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(
gaia::FakeOAuth2TokenResponse::NetError(net::ERR_ABORTED));
ASSERT_TRUE(AttemptToTriggerAuthError());
EXPECT_EQ(GetSyncService(0)->GetTransportState(),
syncer::SyncService::TransportState::ACTIVE);
EXPECT_TRUE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
}
// Verify that SyncServiceImpl fails initial sync setup during backend
// initialization and ends up with an INVALID_GAIA_CREDENTIALS auth error when
// an invalid_grant error is returned by the access token fetcher with an
// HTTP_BAD_REQUEST (400) response code.
IN_PROC_BROWSER_TEST_P(SyncAuthTest, FailInitialSetupWithPersistentError) {
ASSERT_TRUE(SetupClients());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(gaia::FakeOAuth2TokenResponse::OAuth2Error(
OAuth2Response::kInvalidGrant));
ASSERT_FALSE(GetClient(0)->SetupSync());
EXPECT_FALSE(GetSyncService(0)->IsSyncFeatureActive());
EXPECT_EQ(GetSyncService(0)->GetTransportState(),
syncer::SyncService::TransportState::PAUSED);
EXPECT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
GetSyncService(0)->GetAuthError().state());
}
// Verify that SyncServiceImpl fails initial sync setup during backend
// initialization, but continues trying to fetch access tokens when
// the access token fetcher receives an HTTP_INTERNAL_SERVER_ERROR (500)
// response code.
IN_PROC_BROWSER_TEST_P(SyncAuthTest, RetryInitialSetupWithTransientError) {
ASSERT_TRUE(SetupClients());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(gaia::FakeOAuth2TokenResponse::OAuth2Error(
OAuth2Response::kInternalFailure));
ASSERT_FALSE(GetClient(0)->SetupSync());
ASSERT_FALSE(GetSyncService(0)->IsSyncFeatureActive());
EXPECT_TRUE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
}
// Verify that SyncServiceImpl fetches a new token when an old token expires.
IN_PROC_BROWSER_TEST_P(SyncAuthTest, TokenExpiry) {
// Initial sync succeeds with a short lived OAuth2 Token.
ASSERT_TRUE(SetupClients());
GetFakeServer()->ClearHttpError();
DisableTokenFetchRetries();
SetOAuth2TokenResponse(gaia::FakeOAuth2TokenResponse::Success(
"short_lived_access_token", base::Seconds(5)));
ASSERT_TRUE(GetClient(0)->SetupSync());
std::string old_token = GetSyncService(0)->GetAccessTokenForTest();
// Wait until the token has expired.
base::PlatformThread::Sleep(base::Seconds(5));
// Trigger an auth error on the server so PSS requests OA2TS for a new token
// during the next sync cycle.
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
SetOAuth2TokenResponse(gaia::FakeOAuth2TokenResponse::OAuth2Error(
OAuth2Response::kInternalFailure));
ASSERT_TRUE(AttemptToTriggerAuthError());
ASSERT_TRUE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
// Trigger an auth success state and set up a new valid OAuth2 token.
GetFakeServer()->ClearHttpError();
SetOAuth2TokenResponse(
gaia::FakeOAuth2TokenResponse::Success("new_access_token"));
// Verify that the next sync cycle is successful, and uses the new auth token.
ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());
std::string new_token = GetSyncService(0)->GetAccessTokenForTest();
EXPECT_NE(old_token, new_token);
}
class NoAuthErrorChecker : public SingleClientStatusChangeChecker {
public:
explicit NoAuthErrorChecker(syncer::SyncServiceImpl* service)
: SingleClientStatusChangeChecker(service) {}
// StatusChangeChecker implementation.
bool IsExitConditionSatisfied(std::ostream* os) override {
*os << "Waiting for auth error to be cleared";
return service()->GetAuthError().state() == GoogleServiceAuthError::NONE;
}
};
IN_PROC_BROWSER_TEST_P(SyncAuthTest, SyncPausedState) {
ASSERT_TRUE(SetupSync());
ASSERT_EQ(GetSyncService(0)->GetTransportState(),
syncer::SyncService::TransportState::ACTIVE);
const syncer::DataTypeSet active_types =
GetSyncService(0)->GetActiveDataTypes();
ASSERT_FALSE(active_types.empty());
// Enter the "Sync paused"/"sign-in pending" state.
if (GetSetupSyncMode() == SetupSyncMode::kSyncTheFeature) {
GetClient(0)->EnterSyncPausedStateForPrimaryAccount();
} else {
GetClient(0)->EnterSignInPendingStateForPrimaryAccount();
}
ASSERT_TRUE(GetSyncService(0)->GetAuthError().IsPersistentError());
// Sync should have shut itself down.
EXPECT_EQ(GetSyncService(0)->GetTransportState(),
syncer::SyncService::TransportState::PAUSED);
EXPECT_FALSE(GetSyncService(0)->IsEngineInitialized());
// The active data types should now be empty.
EXPECT_TRUE(GetSyncService(0)->GetActiveDataTypes().empty());
// Clear the "Sync paused"/"sign-in pending" state again.
if (GetSetupSyncMode() == SetupSyncMode::kSyncTheFeature) {
GetClient(0)->ExitSyncPausedStateForPrimaryAccount();
} else {
GetClient(0)->ExitSignInPendingStateForPrimaryAccount();
}
// SyncService will clear its auth error state only once it gets a valid
// access token again, so wait for that to happen.
NoAuthErrorChecker(GetSyncService(0)).Wait();
ASSERT_FALSE(GetSyncService(0)->GetAuthError().IsPersistentError());
// Once the auth error is gone, wait for Sync to start up again.
ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
// Now the active data types should be back.
EXPECT_EQ(GetSyncService(0)->GetActiveDataTypes(), active_types);
}
IN_PROC_BROWSER_TEST_P(SyncAuthTest, ShouldTrackDeletionsInSyncPausedState) {
ASSERT_TRUE(SetupSync());
ASSERT_EQ(GetSyncService(0)->GetTransportState(),
syncer::SyncService::TransportState::ACTIVE);
// USS type.
ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::BOOKMARKS));
// Pseudo-USS type.
ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::PREFERENCES));
const std::u16string kTestTitle = u"Title";
const GURL kTestURL("http://mail.google.com");
PrefService* pref_service = GetProfile(0)->GetPrefs();
// Create a bookmark...
bookmarks::BookmarkModel* bookmark_model =
bookmarks_helper::GetBookmarkModel(0);
const bookmarks::BookmarkNode* bar =
(GetSetupSyncMode() == SetupSyncMode::kSyncTheFeature)
? bookmark_model->bookmark_bar_node()
: bookmark_model->account_bookmark_bar_node();
ASSERT_FALSE(bookmarks_helper::HasNodeWithURL(0, kTestURL));
const bookmarks::BookmarkNode* bookmark = bookmarks_helper::AddURL(
0, bar, bar->children().size(), kTestTitle, kTestURL);
// ...set a pref...
ASSERT_FALSE(HasUserPrefValue(pref_service, prefs::kHomePageIsNewTabPage));
pref_service->SetBoolean(prefs::kHomePageIsNewTabPage, true);
// ...and wait for both to be synced up.
ASSERT_TRUE(bookmarks_helper::ServerBookmarksEqualityChecker(
{{kTestTitle, kTestURL}}, /*cryptographer=*/nullptr)
.Wait());
ASSERT_TRUE(FakeServerPrefMatchesValueChecker(
syncer::PREFERENCES, prefs::kHomePageIsNewTabPage, "true")
.Wait());
// Enter the "Sync paused"/"sign-in pending" state.
if (GetSetupSyncMode() == SetupSyncMode::kSyncTheFeature) {
GetClient(0)->EnterSyncPausedStateForPrimaryAccount();
} else {
GetClient(0)->EnterSignInPendingStateForPrimaryAccount();
}
ASSERT_TRUE(GetSyncService(0)->GetAuthError().IsPersistentError());
// Sync should have shut itself down.
EXPECT_EQ(GetSyncService(0)->GetTransportState(),
syncer::SyncService::TransportState::PAUSED);
EXPECT_FALSE(GetSyncService(0)->IsEngineInitialized());
ASSERT_FALSE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::BOOKMARKS));
ASSERT_FALSE(
GetSyncService(0)->GetActiveDataTypes().Has(syncer::PREFERENCES));
// Delete the bookmark and the pref.
// Note that AttemptToTriggerAuthError() also creates bookmarks, so the index
// of our test bookmark might have changed.
ASSERT_EQ(bar->children().back().get(), bookmark);
bookmarks_helper::Remove(0, bar, bar->children().size() - 1);
ASSERT_FALSE(bookmarks_helper::HasNodeWithURL(0, kTestURL));
pref_service->ClearPref(prefs::kHomePageIsNewTabPage);
// Clear the "Sync paused"/"sign-in pending" state again.
if (GetSetupSyncMode() == SetupSyncMode::kSyncTheFeature) {
GetClient(0)->ExitSyncPausedStateForPrimaryAccount();
} else {
GetClient(0)->ExitSignInPendingStateForPrimaryAccount();
}
// SyncService will clear its auth error state only once it gets a valid
// access token again, so wait for that to happen.
NoAuthErrorChecker(GetSyncService(0)).Wait();
ASSERT_FALSE(GetSyncService(0)->GetAuthError().IsPersistentError());
// Once the auth error is gone, wait for Sync to start up again.
ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
// Resuming sync should *not* have re-created the deleted items.
EXPECT_FALSE(bookmarks_helper::HasNodeWithURL(0, kTestURL));
EXPECT_FALSE(HasUserPrefValue(pref_service, prefs::kHomePageIsNewTabPage));
}
} // namespace