// 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/run_until.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/browser_sync/browser_sync_switches.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"

using bookmarks_helper::GetBookmarkBarNode;
using bookmarks_helper::StoreType;

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_.InitWithFeatures(
          {syncer::kReplaceSyncPromosWithSignInPromos,
           switches::kSyncEnableBookmarksInTransportMode,
           switches::kEnablePreferencesAccountStorage,
           syncer::kSeparateLocalAndAccountSearchEngines,
           syncer::kReadingListEnableSyncTransportModeUponSignIn},
          {});
    }
  }

  SyncAuthTestBase(const SyncAuthTestBase&) = delete;
  SyncAuthTestBase& operator=(const SyncAuthTestBase&) = delete;

  ~SyncAuthTestBase() override = default;

  void AddReadingListEntry() {
    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);
  }

  // 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() {
    AddReadingListEntry();

    // 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)));
  }

  StoreType GetStoreType() {
    return GetSetupSyncMode() == SyncTest::SetupSyncMode::kSyncTransportOnly
               ? StoreType::kAccountStore
               : StoreType::kLocalOrSyncableStore;
  }

 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<SyncTest::SetupSyncMode> {
 public:
  SyncAuthTokenFetcherDependentTest() : SyncAuthTestBase(GetParam()) {}

  SyncTest::SetupSyncMode GetSetupSyncMode() const override {
    return GetParam();
  }

  bool IsIssueTokenEnabled() const {
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
    return true;
#else
    return false;
#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,
    GetSyncTestModes(),
    [](const testing::TestParamInfo<SyncTest::SetupSyncMode>& info) {
      return SetupSyncModeAsString(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());
  SetOAuth2TokenResponse(gaia::FakeOAuth2TokenResponse::Success(
      "short_lived_access_token", base::Seconds(5)));
  ASSERT_TRUE(GetClient(0)->SetupSync());
  ASSERT_EQ(GetSyncService(0)->GetAccessTokenForTest(),
            "short_lived_access_token");

  SetOAuth2TokenResponse(
      gaia::FakeOAuth2TokenResponse::Success("new_access_token"));

  // Wait until the token has expired.
  base::PlatformThread::Sleep(base::Seconds(5));

  // Trigger an auth error and wait until a new token is fetched. Note that the
  // server error is not necessary when token expiry is checked on the client,
  // but still set it here in case there is a request made to the server for
  // consistency.
  GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);

  // Trigger a sync cycle to trigger an auth error.
  AddReadingListEntry();

  // Wait until a new token is fetched.
  ASSERT_TRUE(base::test::RunUntil([this]() {
    return GetSyncService(0)->GetAccessTokenForTest() == "new_access_token";
  }));

  // Verify that the next sync cycle is successful.
  GetFakeServer()->ClearHttpError();
  ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());
}

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...
  const bookmarks::BookmarkNode* bar = GetBookmarkBarNode(0, GetStoreType());
  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
