// 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/test/integration/sync_test.h"

#include <utility>

#include "base/auto_reset.h"
#include "base/check_deref.h"
#include "base/command_line.h"
#include "base/containers/to_vector.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/test/test_timeouts.h"
#include "build/build_config.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_browser_main.h"
#include "chrome/browser/chrome_browser_main_extra_parts.h"
#include "chrome/browser/gcm/gcm_profile_service_factory.h"
#include "chrome/browser/gcm/instance_id/instance_id_profile_service_factory.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_test_util.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/signin/chrome_signin_client_factory.h"
#include "chrome/browser/signin/chrome_signin_client_test_util.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/sync/test/integration/committed_all_nudged_changes_checker.h"
#include "chrome/browser/sync/test/integration/fake_sync_gcm_driver_for_instance_id.h"
#include "chrome/browser/sync/test/integration/session_hierarchy_match_checker.h"
#include "chrome/browser/sync/test/integration/sync_datatype_helper.h"
#include "chrome/browser/sync/test/integration/sync_disabled_checker.h"
#include "chrome/browser/sync/test/integration/sync_integration_test_util.h"
#include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/test/bookmark_test_helpers.h"
#include "components/browser_sync/browser_sync_switches.h"
#include "components/commerce/core/commerce_feature_list.h"
#include "components/data_sharing/public/features.h"
#include "components/gcm_driver/fake_gcm_profile_service.h"
#include "components/gcm_driver/gcm_profile_service.h"
#include "components/gcm_driver/instance_id/instance_id.h"
#include "components/gcm_driver/instance_id/instance_id_driver.h"
#include "components/gcm_driver/instance_id/instance_id_profile_service.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/signin/public/base/consent_level.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/skills/features.h"
#include "components/sync/base/command_line_switches.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/features.h"
#include "components/sync/engine/sync_protocol_error.h"
#include "components/sync/engine/sync_scheduler_impl.h"
#include "components/sync/invalidations/sync_invalidations_service_impl.h"
#include "components/sync/service/glue/sync_transport_data_prefs.h"
#include "components/sync/service/sync_service_impl.h"
#include "components/sync/service/sync_user_settings.h"
#include "components/sync/test/embedded_fake_server_adapter.h"
#include "components/sync/test/fake_server.h"
#include "content/public/browser/browser_main_parts.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/test_launcher.h"
#include "content/public/test/url_loader_interceptor.h"
#include "extensions/buildflags/buildflags.h"
#include "google_apis/gaia/fake_oauth2_token_response.h"
#include "google_apis/gaia/gaia_urls.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "net/base/port_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "url/gurl.h"
#include "url/url_constants.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "chrome/browser/ash/app_list/arc/arc_app_list_prefs_factory.h"
#include "chrome/browser/ash/app_list/test/fake_app_list_model_updater.h"
#include "chrome/browser/sync/test/integration/sync_arc_package_helper.h"
#include "chromeos/ash/components/account_manager/account_manager_factory.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "chromeos/ash/experiences/arc/test/arc_util_test_support.h"
#include "components/account_id/account_id.h"
#include "components/account_id/account_id_literal.h"  // nogncheck
#include "components/account_manager_core/chromeos/account_manager.h"
#include "components/session_manager/core/session_manager.h"
#include "components/user_manager/test_helper.h"
#include "components/user_manager/user_manager.h"
#endif  // BUILDFLAG(IS_CHROMEOS)

#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/sync/test/integration/sync_test_utils_android.h"
#else  // BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_collection_observer.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/create_browser_window.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/trusted_vault/command_line_switches.h"
#endif  // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
#include "chrome/browser/enterprise/util/managed_browser_utils.h"
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

using syncer::SyncServiceImpl;

namespace {

// A small ChromeBrowserMainExtraParts that invokes a callback when threads are
// ready.
class ChromeBrowserMainExtraPartsThreadNotifier final
    : public ChromeBrowserMainExtraParts {
 public:
  explicit ChromeBrowserMainExtraPartsThreadNotifier(
      base::OnceClosure threads_ready_closure)
      : threads_ready_closure_(std::move(threads_ready_closure)) {}

  // ChromeBrowserMainExtraParts:
  void PostCreateThreads() final { std::move(threads_ready_closure_).Run(); }

 private:
  base::OnceClosure threads_ready_closure_;
};

int GetNumClients(SyncTest::TestType test_type) {
  switch (test_type) {
    case SyncTest::SINGLE_CLIENT:
      return 1;
    case SyncTest::TWO_CLIENT:
      return 2;
  }
  NOTREACHED();
}

#if BUILDFLAG(IS_CHROMEOS)
constexpr auto kAccountId1 =
    AccountId::Literal::FromUserEmailGaiaId("user1@gmail.com",
                                            GaiaId::Literal("11111111"));
constexpr auto kAccountId2 =
    AccountId::Literal::FromUserEmailGaiaId("user2@gmail.com",
                                            GaiaId::Literal("22222222"));
#endif  // BUILDFLAG(IS_CHROMEOS)
}  // namespace

#if !BUILDFLAG(IS_ANDROID)
class SyncTest::ClosedBrowserObserver : public BrowserCollectionObserver {
 public:
  using OnBrowserRemovedCallback =
      base::RepeatingCallback<void(Browser* browser)>;

  explicit ClosedBrowserObserver(OnBrowserRemovedCallback callback)
      : browser_remove_callback_(std::move(callback)) {
    observation_.Observe(GlobalBrowserCollection::GetInstance());
  }

  ~ClosedBrowserObserver() override = default;

  void OnBrowserClosed(BrowserWindowInterface* browser) override {
    browser_remove_callback_.Run(browser->GetBrowserForMigrationOnly());
  }

 private:
  base::ScopedObservation<GlobalBrowserCollection, BrowserCollectionObserver>
      observation_{this};
  OnBrowserRemovedCallback browser_remove_callback_;
};
#endif

SyncTest::SyncClientState::SyncClientState() = default;
SyncTest::SyncClientState::~SyncClientState() = default;
SyncTest::SyncClientState::SyncClientState(SyncClientState&&) = default;
SyncTest::SyncClientState& SyncTest::SyncClientState::operator=(
    SyncClientState&&) = default;

SyncTest::SyncTest(TestType test_type)
    : test_type_(test_type),
      server_type_(base::CommandLine::ForCurrentProcess()->HasSwitch(
                       syncer::kSyncServiceURL)
                       ? EXTERNAL_LIVE_SERVER
                       : IN_PROCESS_FAKE_SERVER),
      test_construction_time_(base::Time::Now()),
      num_clients_(GetNumClients(test_type_)),
      sync_run_loop_timeout_(FROM_HERE, TestTimeouts::action_max_timeout()),
      previous_profile_(nullptr) {
  // Any RunLoop timeout will by default result in test failure.
  sync_run_loop_timeout_.SetAddGTestFailureOnTimeout();

  sync_datatype_helper::AssociateWithTest(this);
}

SyncTest::~SyncTest() = default;

void SyncTest::SetUp() {
  switch (server_type_) {
    case EXTERNAL_LIVE_SERVER: {
      break;
    }
    case IN_PROCESS_FAKE_SERVER: {
      ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
#if BUILDFLAG(IS_ANDROID)
      sync_test_utils_android::SetUpFakeAuthForTesting();
#endif
      break;
    }
  }

  // Yield control back to the PlatformBrowserTest framework.
  PlatformBrowserTest::SetUp();

  LOG(INFO) << "SyncTest::SetUp() completed; elapsed time since construction: "
            << (base::Time::Now() - test_construction_time_);
}

void SyncTest::TearDown() {
  // Clear any mock gaia responses that might have been set.
  ClearMockGaiaResponses();

  // Allow the PlatformBrowserTest framework to perform its tear down.
  PlatformBrowserTest::TearDown();
}

void SyncTest::PostRunTestOnMainThread() {
  // Needs to be destroyed before the task environment is shut down.
  url_loader_interceptor_.reset();

  PlatformBrowserTest::PostRunTestOnMainThread();

#if BUILDFLAG(IS_ANDROID)
  // TODO(crbug.com/368091420): Consider moving into SyncSigninDelegateAndroid.
  switch (server_type_) {
    case EXTERNAL_LIVE_SERVER:
      sync_test_utils_android::ShutdownLiveAuthForTesting();
      break;
    case IN_PROCESS_FAKE_SERVER:
      sync_test_utils_android::TearDownFakeAuthForTesting();
      break;
  }
#endif
}

void SyncTest::CreatedBrowserMainParts(content::BrowserMainParts* parts) {
  static_cast<ChromeBrowserMainParts*>(parts)->AddParts(
      std::make_unique<ChromeBrowserMainExtraPartsThreadNotifier>(
          base::BindOnce(&SyncTest::PostCreateThreads,
                         weak_ptr_factory_.GetWeakPtr())));
  PlatformBrowserTest::CreatedBrowserMainParts(parts);
}

void SyncTest::SetUpLocalStatePrefService(PrefService* local_state) {
  PlatformBrowserTest::SetUpLocalStatePrefService(local_state);
#if BUILDFLAG(IS_CHROMEOS)
  // For multi-user sign-in, users need to be pre registered on starting
  // Chrome. This class uses at most two profiles, so register them.
  user_manager::TestHelper::RegisterPersistedUser(*local_state, kAccountId1);
  user_manager::TestHelper::RegisterPersistedUser(*local_state, kAccountId2);
#endif  // BUILDFLAG(IS_CHROMEOS)
}

void SyncTest::SetUpCommandLine(base::CommandLine* cl) {
  // Disable non-essential access of external network resources.
  if (!cl->HasSwitch(switches::kDisableBackgroundNetworking)) {
    cl->AppendSwitch(switches::kDisableBackgroundNetworking);
  }

  if (!cl->HasSwitch(syncer::kSyncShortInitialRetryOverride)) {
    cl->AppendSwitch(syncer::kSyncShortInitialRetryOverride);
  }

  if (!cl->HasSwitch(syncer::kSyncShortNudgeDelayForTest)) {
    cl->AppendSwitch(syncer::kSyncShortNudgeDelayForTest);
  }

  if (!cl->HasSwitch(
          switches::kBypassAccountAlreadyUsedByAnotherProfileCheck)) {
    cl->AppendSwitch(switches::kBypassAccountAlreadyUsedByAnotherProfileCheck);
  }

  switch (server_type_) {
    case EXTERNAL_LIVE_SERVER: {
      if (!cl->HasSwitch(switches::kDisableSyncInvalidationOptimizations)) {
        // This flag is required because multiple devices in tests become active
        // at the same time, and they may populate a single client optimization
        // flag incorrectly resulting in missed invalidations.
        cl->AppendSwitch(switches::kDisableSyncInvalidationOptimizations);
      }
      break;
    }
    case IN_PROCESS_FAKE_SERVER: {
      CHECK(!cl->HasSwitch(syncer::kSyncServiceURL));
      cl->AppendSwitchASCII(
          syncer::kSyncServiceURL,
          embedded_test_server()
              ->GetURL(fake_server::EmbeddedFakeServerAdapter::kPath)
              .spec());
      break;
    }
  }

#if BUILDFLAG(IS_CHROMEOS)
  cl->AppendSwitch(ash::switches::kIgnoreUserProfileMappingForTests);
  cl->AppendSwitch(ash::switches::kDisableArcOptInVerification);
  arc::SetArcAvailableCommandLineForTesting(cl);
#endif
}

base::FilePath SyncTest::GetProfileBaseName(int index) {
#if BUILDFLAG(IS_CHROMEOS)
  // In ChromeOS platform, user profile path is expected to start with "u-",
  // followed by username hash. This follows the format.
  return base::FilePath::FromASCII("u-SyncIntegrationTestClient" +
                                   base::NumberToString(index));
#else
  return base::FilePath::FromASCII("SyncIntegrationTestClient" +
                                   base::NumberToString(index));
#endif
}

void SyncTest::PostCreateThreads() {
  CHECK(g_browser_process);
  CHECK(g_browser_process->profile_manager());
  profile_manager_observation_.Observe(g_browser_process->profile_manager());

  switch (server_type_) {
    case EXTERNAL_LIVE_SERVER: {
      // Allows google.com as well as country-specific TLDs.
      host_resolver()->AllowDirectLookup("*.google.com");
      host_resolver()->AllowDirectLookup("accounts.google.*");
      host_resolver()->AllowDirectLookup("*.googleusercontent.com");
      // Allow connection to googleapis.com for oauth token requests in E2E
      // tests.
      host_resolver()->AllowDirectLookup("*.googleapis.com");

      // On Linux, we use Chromium's NSS implementation which uses the following
      // hosts for certificate verification. Without these overrides, running
      // the integration tests on Linux causes error as we make external DNS
      // lookups.
      host_resolver()->AllowDirectLookup("*.thawte.com");
      host_resolver()->AllowDirectLookup("*.geotrust.com");
      host_resolver()->AllowDirectLookup("*.gstatic.com");
      break;
    }
    case IN_PROCESS_FAKE_SERVER: {
      // Start up a sync test server and setup mock gaia responses.
      // Note: This must be done prior to the call to SetUpOnMainThread()
      // because PlatformBrowserTest creates a default profile early, shortly
      // after the threadpool is initialized.
      base::FilePath user_data_dir;
      base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
      fake_server_ = std::make_unique<fake_server::FakeServer>(
          user_data_dir.AppendASCII("FakeServer"));
      embedded_fake_server_adapter_ =
          std::make_unique<fake_server::EmbeddedFakeServerAdapter>(
              fake_server_->AsWeakPtr());
      fake_server_sync_invalidation_sender_ =
          std::make_unique<fake_server::FakeServerSyncInvalidationSender>(
              fake_server_.get());

      SetupMockGaiaResponses();

      embedded_fake_server_adapter_->RegisterRequestHandler(
          *embedded_test_server());
      break;
    }
  }
}

bool SyncTest::CreateProfile(int index) {
  base::FilePath profile_path;

// For Android, we don't create profile because Clank doesn't support
// multiple profiles.
#if !BUILDFLAG(IS_ANDROID)
  base::ScopedAllowBlockingForTesting allow_blocking;

  base::FilePath user_data_dir;
  base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);

  // Instead of creating a new directory, use a deterministic name such that
  // PRE_ tests (i.e. tests that span browser restarts) can reuse the same
  // directory and carry over state.
  profile_path = user_data_dir.Append(GetProfileBaseName(index));
#endif

#if BUILDFLAG(IS_ANDROID)
  CHECK_EQ(index, 0);
  Profile* profile = ProfileManager::GetLastUsedProfile();
#else  // BUILDFLAG(IS_ANDROID)
  Profile* profile = nullptr;
#if BUILDFLAG(IS_CHROMEOS)
  if (use_primary_user_profile_) {
    CHECK_EQ(index, 0);
    profile = Profile::FromBrowserContext(
        ash::BrowserContextHelper::Get()->GetBrowserContextByUser(
            user_manager::UserManager::Get()->GetPrimaryUser()));
  } else {
    // Create a fake user session.
    CHECK(index == 0 || index == 1);
    AccountId account_id = index == 0 ? kAccountId1 : kAccountId2;
    session_manager::SessionManager::Get()->CreateSession(
        account_id,
        // Use profile path base for a fake username_hash here.
        // In production, the profile base name in ChromeOS is in "u-${hash}"
        // format, where ${hash} is actually user hash maintained in the
        // ChromeOS system side, and extracts the hash from the path on
        // initialization. This trick allows Chrome to tie the user and
        // a Profile being created just below.
        /*username_hash=*/
        ash::BrowserContextHelper::GetUsernameHashFromBrowserContextDirName(
            GetProfileBaseName(index)),
        /*new_user=*/false,
        /*has_active_session=*/false);
  }
#endif  // BUILDFLAG(IS_CHROMEOS)
  if (!profile) {
    profile = g_browser_process->profile_manager()->GetProfile(profile_path);
  }
#endif  // BUILDFLAG(IS_ANDROID)

  InitializeProfile(index, profile);

  // Once profile initialization has kicked off, wait for it to finish.
  WaitForDataModels(GetProfile(index));
  return true;
}

Profile* SyncTest::GetProfile(int index) const {
  CHECK(!clients_.empty()) << "SetupClients() has not yet been called.";
  CHECK(index >= 0 && index < std::ssize(clients_))
      << "GetProfile(): Index is out of bounds: " << index;

  Profile* profile = clients_[index].profile;
  CHECK(profile) << "No profile found at index: " << index;

  return profile;
}

std::vector<raw_ptr<Profile, VectorExperimental>> SyncTest::GetAllProfiles() {
  std::vector<raw_ptr<Profile, VectorExperimental>> profiles;
  for (const SyncClientState& client : clients_) {
    // Profile can be null if it was destroyed earlier (e.g. in
    // OnProfileWillBeDestroyed).
    if (client.profile) {
      profiles.push_back(client.profile);
    }
  }
  return profiles;
}

#if BUILDFLAG(IS_CHROMEOS)
void SyncTest::SetUsePrimaryUserProfile(bool value) {
  // Must be called early enough.
  CHECK(clients_.empty());
  use_primary_user_profile_ = true;
}
#endif

#if !BUILDFLAG(IS_ANDROID)
Browser* SyncTest::GetBrowser(int profile_index, int window_index) const {
  CHECK(!clients_.empty()) << "SetupClients() has not yet been called.";
  CHECK(profile_index >= 0 && profile_index < std::ssize(clients_))
      << "GetBrowser(): Profile index is out of bounds: " << profile_index;

  const std::vector<raw_ptr<Browser, AcrossTasksDanglingUntriaged>>& browsers =
      clients_[profile_index].browsers;
  CHECK(window_index >= 0 && window_index < std::ssize(browsers))
      << "GetBrowser(): Window index is out of bounds: " << window_index
      << " for profile " << profile_index
      << ". (Did you forget to call AddBrowser() to create additional "
         "windows?)";

  Browser* browser = browsers[window_index];
  CHECK(browser) << "GetBrowser(): Browser for profile " << profile_index
                 << " and window " << window_index
                 << " was not created. Call AddBrowser() explicitly if this "
                    "test requires a browser window.";

  return browser;
}

Browser* SyncTest::AddBrowser(int profile_index) {
  Profile* profile = GetProfile(profile_index);
  Browser* browser =
      CreateBrowserWindow(
          BrowserWindowCreateParams(profile, /*from_user_gesture=*/true))
          ->GetBrowserForMigrationOnly();
  clients_[profile_index].browsers.push_back(browser);

  chrome::AddSelectedTabWithURL(browser, GetInitialURL(),
                                ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
  // Show the browser window. Otherwise, the rendering pipeline might not
  // initialize or produce frames (e.g., on Wayland headless bots), which
  // can cause tests relying on hit test data or visual state to time out.
  browser->GetWindow()->Show();

  return browser;
}

void SyncTest::OnBrowserRemoved(Browser* browser) {
  for (SyncClientState& client : clients_) {
    std::erase(client.browsers, browser);
  }
}
#endif

SyncServiceImplHarness* SyncTest::GetClient(int index) {
  return const_cast<SyncServiceImplHarness*>(
      std::as_const(*this).GetClient(index));
}

const SyncServiceImplHarness* SyncTest::GetClient(int index) const {
  CHECK(!clients_.empty()) << "SetupClients() has not yet been called.";
  CHECK(index >= 0 && index < std::ssize(clients_))
      << "GetClient(): Index is out of bounds.";
  return clients_[index].harness.get();
}

std::vector<SyncServiceImplHarness*> SyncTest::GetSyncClients() {
  return base::ToVector(clients_, [](const SyncClientState& client) {
    return client.harness.get();
  });
}

SyncServiceImpl* SyncTest::GetSyncService(int index) const {
  return SyncServiceFactory::GetAsSyncServiceImplForProfileForTesting(
      GetProfile(index));
}

syncer::UserSelectableTypeSet SyncTest::GetRegisteredSelectableTypes(
    int index) {
  return GetSyncService(index)
      ->GetUserSettings()
      ->GetRegisteredSelectableTypes();
}

SyncTest::SetupSyncMode SyncTest::GetSetupSyncMode() const {
  return SetupSyncMode::kSyncTheFeature;
}

GURL SyncTest::GetInitialURL() const {
  return GURL(url::kAboutBlankURL);
}

std::vector<raw_ptr<SyncServiceImpl, VectorExperimental>>
SyncTest::GetSyncServices() {
  std::vector<raw_ptr<SyncServiceImpl, VectorExperimental>> services;
  for (int i = 0; i < num_clients(); ++i) {
    services.push_back(GetSyncService(i));
  }
  return services;
}

bool SyncTest::SetupClients() {
  CHECK(clients_.empty());
  CHECK_GT(num_clients_, 0) << "num_clients_ incorrectly initialized.";

  previous_profile_ =
      g_browser_process->profile_manager()->GetLastUsedProfile();

#if !BUILDFLAG(IS_ANDROID)
  // Create the browser observer now that GlobalBrowserCollection is available.
  // This cannot be done in the constructor because it runs before browser
  // process initialization.
  if (!browser_list_observer_) {
    browser_list_observer_ =
        std::make_unique<ClosedBrowserObserver>(base::BindRepeating(
            &SyncTest::OnBrowserRemoved, base::Unretained(this)));
  }
#endif

  // Create the required number of sync clients.
  clients_.resize(num_clients_);

  auto* cl = base::CommandLine::ForCurrentProcess();
  if (!cl->HasSwitch(syncer::kSyncDeferredStartupTimeoutSeconds)) {
    cl->AppendSwitchASCII(syncer::kSyncDeferredStartupTimeoutSeconds, "0");
  }

#if BUILDFLAG(IS_CHROMEOS)
  // Sets Arc flags, need to be called before create test profiles.
  ArcAppListPrefsFactory::SetFactoryForSyncTest();

  // Uses a fake app list model updater to avoid interacting with Ash.
  model_updater_factory_scope_ =
      app_list::AppListSyncableService::SetScopedModelUpdaterFactoryForTest(
          base::BindRepeating(
              [](app_list::reorder::AppListReorderDelegate* reorder_delegate)
                  -> std::unique_ptr<AppListModelUpdater> {
                return std::make_unique<FakeAppListModelUpdater>(
                    /*profile=*/nullptr, reorder_delegate);
              }));
#endif

  base::ScopedAllowBlockingForTesting allow_blocking;

  for (int i = 0; i < num_clients_; ++i) {
    if (!CreateProfile(i)) {
      return false;
    }

    LOG(INFO) << "SyncTest::SetupClients() created profile " << i
              << "; elapsed time since construction: "
              << (base::Time::Now() - test_construction_time_);
  }

#if BUILDFLAG(IS_CHROMEOS)
  if (ArcAppListPrefsFactory::IsFactorySetForSyncTest()) {
    // Init SyncArcPackageHelper to ensure that the arc services are initialized
    // for each Profile, only can be called after test profiles are created.
    if (!sync_arc_helper()) {
      return false;
    }
  }
#endif

  LOG(INFO)
      << "SyncTest::SetupClients() completed; elapsed time since construction: "
      << (base::Time::Now() - test_construction_time_);

  return true;
}

void SyncTest::InitializeProfile(int index, Profile* profile) {
  CHECK(profile);
  CHECK(index >= 0 && index < std::ssize(clients_));
  CHECK(!clients_[index].profile) << " for index " << index;

  clients_[index].profile = profile;
  profile->AddObserver(this);

#if !BUILDFLAG(IS_ANDROID)
  CHECK(clients_[index].browsers.empty());
  // Only the primary client (index 0) gets a browser window by default during
  // profile initialization. Secondary clients only create browser windows if
  // explicitly requested via AddBrowser().
  // This is needed for performance reasons, mostly to reduce flakiness due to
  // test timeouts during initialization and teardown.
  if (index == 0) {
    Browser* browser =
        CreateBrowserWindow(
            BrowserWindowCreateParams(profile, /*from_user_gesture=*/true))
            ->GetBrowserForMigrationOnly();
    clients_[index].browsers.push_back(browser);

    chrome::AddSelectedTabWithURL(browser, GetInitialURL(),
                                  ui::PAGE_TRANSITION_AUTO_TOPLEVEL);

    // Show the browser window. Otherwise, the rendering pipeline might not
    // initialize or produce frames (e.g., on Wayland headless bots), which
    // can cause tests relying on hit test data or visual state to time out.
    browser->GetWindow()->Show();
  }
#endif

  if (server_type_ == IN_PROCESS_FAKE_SERVER) {
    // Make sure that an instance of GCMProfileService has been created. This is
    // required for some tests which only call SetupClients().
    gcm::GCMProfileServiceFactory::GetForProfile(profile);
    CHECK(profile_to_fake_gcm_driver_.contains(profile));
    fake_server_sync_invalidation_sender_->AddFakeGCMDriver(
        profile_to_fake_gcm_driver_[profile]);
  }

  SyncServiceImplHarness::SigninType signin_type =
      server_type_ == EXTERNAL_LIVE_SERVER
          ? SyncServiceImplHarness::SigninType::UI_SIGNIN
          : SyncServiceImplHarness::SigninType::FAKE_SIGNIN;

  CHECK(!clients_[index].harness);
  clients_[index].harness =
      SyncServiceImplHarness::Create(GetProfile(index), signin_type);
  EXPECT_NE(nullptr, GetClient(index)) << "Could not create Client " << index;
}

bool SyncTest::SetupSyncInternal(SetupSyncMode setup_mode,
                                 SyncWaitCondition wait_condition,
                                 SyncTestAccount account,
                                 bool enable_history_sync_in_transport_mode) {
  // Create sync profiles and clients if they haven't already been created.
  if (clients_.empty()) {
    if (!SetupClients()) {
      ADD_FAILURE() << "SetupClients() failed.";
      return false;
    }
  }

  if (server_type_ == EXTERNAL_LIVE_SERVER) {
    LOG(ERROR) << "WARNING: Running against external servers with an existing "
                  "account. If there is any pre-existing data in the account, "
                  "things will likely break.";
  }

  // Sync each of the profiles.
  for (int client_index = 0; client_index < num_clients_; client_index++) {
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
    auto resetter =
        enterprise_util::DisableAutomaticManagementDisclaimerUntilReset(
            GetProfile(client_index));
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
    SyncServiceImplHarness* client = GetClient(client_index);
    DVLOG(1) << "Setting up " << client_index << " client";

    if (setup_mode == SetupSyncMode::kSyncTransportOnly) {
      if (!client->SignInNoWaitForCompletion(account) ||
          !client->AwaitEngineInitialization() ||
          (enable_history_sync_in_transport_mode &&
           !client->EnableHistorySyncNoWaitForCompletion())) {
        ADD_FAILURE() << "SetupSync() failed.";
        return false;
      }
    } else {
      if (!client->SetupSyncNoWaitForCompletion(account)) {
        ADD_FAILURE() << "SetupSync() failed.";
        return false;
      }
    }

    if (TestUsesSelfNotifications()) {
      // On Android, invalidations for Session data type are disabled by
      // default. This may result in test flakiness when using when using
      // AwaitQuiescence() because Android commits Session for "about:blank"
      // page, hence AwaitQuiescence() would wait for downloading updates
      // forever.
      // TODO(crbug.com/40173160): remove this workaround once SetupSync doesn't
      // rely on self-notifications.
      CHECK(GetSyncService(client_index)->IsEngineInitialized());
      GetSyncService(client_index)->SetInvalidationsForSessionsEnabled(true);
    }

    // It's important to wait for each client before setting up the next one,
    // otherwise multi-client tests get flaky. This may happen in some tests
    // which have local data before sync is enabled. In such tests it's
    // important (and this is closer to real behavior) that the initial merge is
    // happening sequentially in two clients, otherwise both clients can upload
    // their data simultaneously, e.g. resulting in duplicates (most prominent
    // for bookmarks).
    switch (wait_condition) {
      case NO_WAITING:
        break;
      case WAIT_FOR_SYNC_SETUP_TO_COMPLETE:
        if (!client->AwaitSyncTransportActive()) {
          ADD_FAILURE() << "AwaitSyncTransportActive() failed";
          return false;
        }
        if (!client->AwaitInvalidationsStatus(/*expected_status=*/true)) {
          ADD_FAILURE() << "AwaitInvalidationsStatus() failed";
          return false;
        }
        break;
      case WAIT_FOR_COMMITS_TO_COMPLETE:
        if (!client->AwaitSyncTransportActive()) {
          ADD_FAILURE() << "AwaitSyncTransportActive() failed";
          return false;
        }
        if (!client->AwaitInvalidationsStatus(/*expected_status=*/true)) {
          ADD_FAILURE() << "AwaitInvalidationsStatus() failed";
          return false;
        }
        if (!WaitForAsyncChangesToBeCommitted(client_index)) {
          ADD_FAILURE() << "WaitForAsyncChangesToBeCommitted() failed";
          return false;
        }
        break;
    }

    LOG(INFO) << "SetupSync for client " << client_index << " finished, "
              << "cache guid: " << GetCacheGuid(client_index);
  }

  // Because clients may modify sync data as part of startup (for example
  // local session-related data is rewritten), we need to ensure all
  // startup-based changes have propagated between the clients.
  //
  // Tests that don't use self-notifications or are allowlisted to run in E2E
  // mode can't await quiescence. They'll have to find their own way of waiting
  // for an initial state if they really need such guarantees.
  if (wait_condition != NO_WAITING && TestUsesSelfNotifications() &&
      !sync_integration_test_util::IsCurrentTestAllowlistedForE2EMode()) {
    // Bypass E2E check because all tests are calling SetupSync(), and in this
    // call site it's also verified that it's not called in E2E tests
    // (implicitly via TestUsesSelfNotifications()).
    if (!SyncServiceImplHarness::AwaitQuiescence(GetSyncClients())) {
      ADD_FAILURE() << "AwaitQuiescence() failed.";
      return false;
    }
  }

  return true;
}

bool SyncTest::SetupSync(SyncWaitCondition wait_condition) {
  return SetupSync(SyncTestAccount::kDefaultAccount, wait_condition);
}

bool SyncTest::SetupSync(SyncTestAccount account,
                         SyncWaitCondition wait_condition) {
  return SetupSyncWithMode(GetSetupSyncMode(), wait_condition, account);
}

bool SyncTest::SetupSyncWithMode(SetupSyncMode setup_mode,
                                 SyncWaitCondition wait_condition,
                                 SyncTestAccount account) {
#if BUILDFLAG(IS_ANDROID)
  // For Android, currently the framework only supports one client.
  // The client uses the default profile.
  CHECK(num_clients_ == 1)
      << "For Android, currently it only supports one client.";
#endif

  base::ScopedAllowBlockingForTesting allow_blocking;

  if (!SetupSyncInternal(setup_mode, wait_condition, account,
                         /*enable_history_sync_in_transport_mode=*/true)) {
    return false;
  }

  DLOG(INFO) << "SyncTest::SetupSync() completed.";
  return true;
}

bool SyncTest::SignIn(SyncTestAccount account) {
#if BUILDFLAG(IS_ANDROID)
  // For Android, currently the framework only supports one client.
  // The client uses the default profile.
  CHECK(num_clients_ == 1)
      << "For Android, currently it only supports one client.";
#endif

  base::ScopedAllowBlockingForTesting allow_blocking;

  if (!SetupSyncInternal(SetupSyncMode::kSyncTransportOnly,
                         WAIT_FOR_COMMITS_TO_COMPLETE, account,
                         /*enable_history_sync_in_transport_mode=*/false)) {
    return false;
  }

  DLOG(INFO) << "SyncTest::SignIn() completed.";
  return true;
}

void SyncTest::SetUpOnMainThread() {
  switch (server_type_) {
    case EXTERNAL_LIVE_SERVER:
      break;
    case IN_PROCESS_FAKE_SERVER:
      embedded_test_server_handle_ =
          embedded_test_server()->StartAcceptingConnectionsAndReturnHandle();
      break;
  }
}

void SyncTest::TearDownOnMainThread() {
  // Verify that there are no data type failures after the test.
  for (size_t client_index = 0; client_index < clients_.size();
       ++client_index) {
    if (!GetClient(client_index)) {
      // This may happen if the last tab and hence a browser has been closed.
      continue;
    }
    CheckForDataTypeFailures(client_index);
  }

  // Workaround for https://crbug.com/41364511: |prefs::kProfileLastUsed| stores
  // the profile path relative to the user dir, but our testing profiles are
  // outside the user dir (see CreateProfile). So code trying to access the last
  // used profile by path will fail. To work around that, set the last used
  // profile back to the originally created default profile (which does live in
  // the user data dir, and which we don't use otherwise).
  if (previous_profile_) {
    profiles::SetLastUsedProfile(previous_profile_->GetBaseName());
    previous_profile_ = nullptr;
  }

  if (fake_server_.get()) {
    fake_server_sync_invalidation_sender_.reset();
    embedded_fake_server_adapter_.reset();
    fake_server_.reset();
  }

  for (const SyncClientState& client : clients_) {
    Profile* profile = client.profile;
    // Profile could be removed earlier.
    if (profile) {
      profile->RemoveObserver(this);

#if BUILDFLAG(IS_ANDROID)
      // In Android browser tests, the Profile and thus the SyncService does not
      // get shut down in an orderly fashion. This can interfere with subsequent
      // tests. To work around that, produce an auth error here, which results
      // in the engine being shut down. (Note that auth errors are not
      // persisted, so this does not interfere with PRE_ tests.)
      signin::IdentityManager* identity_manager =
          IdentityManagerFactory::GetForProfile(profile);
      CoreAccountId primary_account =
          identity_manager->GetPrimaryAccountId(signin::ConsentLevel::kSignin);
      if (!primary_account.empty()) {
        signin::UpdatePersistentErrorOfRefreshTokenForAccount(
            identity_manager, primary_account,
            GoogleServiceAuthError::FromInvalidGaiaCredentialsReason(
                GoogleServiceAuthError::InvalidGaiaCredentialsReason::
                    CREDENTIALS_REJECTED_BY_CLIENT));
      }

      // On Android, the Profile does not get shut down in an orderly fashion.
      // In PRE_ tests, ensure that all relevant state is persisted to disk (in
      // non-PRE_ tests, it doesn't matter since nothing will use it again).
      if (content::IsPreTest()) {
        base::test::TestFuture<void> prefs_write_done;
        profile->GetPrefs()->CommitPendingWrite(prefs_write_done.GetCallback());
        ASSERT_TRUE(prefs_write_done.Wait());

        BookmarkModelFactory::GetForBrowserContext(profile)
            ->CommitPendingWriteForTest();

        fake_server::FakeServer* fake_server = GetFakeServer();
        if (fake_server) {
          fake_server->FlushToDisk();
        }
      }
#endif  // BUILDFLAG(IS_ANDROID)
    }
  }

#if !BUILDFLAG(IS_ANDROID)

  // Make a copy of browser pointers before calling CloseBrowserAsynchronously,
  // as closing a browser can synchronously notify observers and modify
  // clients_[...].browsers.
  std::vector<Browser*> browsers_to_close;
  for (const SyncClientState& client : clients_) {
    for (Browser* browser : client.browsers) {
      if (browser) {
        browsers_to_close.push_back(browser);
      }
    }
  }

  // Closing all browsers created by this test in parallel rather than
  // sequentially. Other browsers created outside SyncTest setup should be
  // closed by the creator of that browser.
  std::vector<std::unique_ptr<ui_test_utils::BrowserDestroyedObserver>>
      browser_observers;
  browser_observers.reserve(browsers_to_close.size());
  for (Browser* browser : browsers_to_close) {
    browser_observers.push_back(
        std::make_unique<ui_test_utils::BrowserDestroyedObserver>(browser));
    CloseBrowserAsynchronously(browser);
  }

  for (const std::unique_ptr<ui_test_utils::BrowserDestroyedObserver>&
           observer : browser_observers) {
    observer->Wait();
  }
#endif

  clients_.clear();
  profile_to_fake_gcm_driver_.clear();
  // TODO(crbug.com/40798524): There are various other Profile-related members
  // around like profile_to_*_map_ - those should probably be cleaned up too.

// Clean up the browser observer.
#if !BUILDFLAG(IS_ANDROID)
  browser_list_observer_.reset();
#endif

  PlatformBrowserTest::TearDownOnMainThread();
}

void SyncTest::OnProfileWillBeDestroyed(Profile* profile) {
  profile->RemoveObserver(this);

  if (server_type_ == IN_PROCESS_FAKE_SERVER) {
    CHECK(profile_to_fake_gcm_driver_.contains(profile));
    if (fake_server_sync_invalidation_sender_) {
      fake_server_sync_invalidation_sender_->RemoveFakeGCMDriver(
          profile_to_fake_gcm_driver_[profile]);
    }
    profile_to_fake_gcm_driver_.erase(profile);
  }

  for (size_t index = 0; index < clients_.size(); ++index) {
    if (clients_[index].profile != profile) {
      continue;
    }

    CheckForDataTypeFailures(/*client_index=*/index);
    clients_[index].profile = nullptr;
    clients_[index].harness.reset();
#if !BUILDFLAG(IS_ANDROID)
    CHECK(clients_[index].browsers.empty());
#endif  // !BUILDFLAG(IS_ANDROID)
  }
}

void SyncTest::OnProfileAdded(Profile* profile) {
#if BUILDFLAG(IS_CHROMEOS)
  // This cannot run in OnProfileCreationStarted() because it would be too
  // early, and ProfileImpl's constructor would override it once again when
  // invoking ash::InitializeAccountManager().
  if (server_type_ == IN_PROCESS_FAKE_SERVER) {
    account_manager::AccountManager* account_manager =
        ash::AccountManagerFactory::Get()->GetAccountManager(
            profile->GetPath().value());
    account_manager->SetUrlLoaderFactoryForTests(
        test_url_loader_factory_.GetSafeWeakWrapper());
  }
#endif  // BUILDFLAG(IS_CHROMEOS)
}

void SyncTest::OnProfileManagerDestroying() {
  profile_manager_observation_.Reset();
}

void SyncTest::OnProfileCreationStarted(Profile* profile) {
  if (server_type_ == EXTERNAL_LIVE_SERVER) {
    // DO NOTHING. External live sync servers use real factories without quirks
    // or overrides.
    return;
  }

  CHECK(GetFakeServer());

  gcm::GCMProfileServiceFactory::GetInstance()->SetTestingFactory(
      profile, base::BindRepeating(&SyncTest::CreateGCMProfileService,
                                   base::Unretained(this)));
  ChromeSigninClientFactory::GetInstance()->SetTestingFactory(
      profile, base::BindRepeating(&BuildChromeSigninClientWithURLLoader,
                                   &test_url_loader_factory_));
}

std::unique_ptr<KeyedService> SyncTest::CreateGCMProfileService(
    content::BrowserContext* context) {
  scoped_refptr<base::SequencedTaskRunner> blocking_task_runner(
      base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}));

  Profile* profile = Profile::FromBrowserContext(context);
  CHECK(!profile_to_fake_gcm_driver_.contains(profile))
      << "CreateGCMProfileService called multiple times for profile: "
      << profile->GetDebugName() << ", is_otr: " << context->IsOffTheRecord();

  auto fake_gcm_driver =
      std::make_unique<FakeSyncGCMDriver>(profile, blocking_task_runner);
  profile_to_fake_gcm_driver_[profile] = fake_gcm_driver.get();
  fake_gcm_driver->WaitForAppIdBeforeConnection(
      fake_server::FakeServerSyncInvalidationSender::kSyncInvalidationsAppId);
  return std::make_unique<gcm::FakeGCMProfileService>(
      std::move(fake_gcm_driver));
}

bool SyncTest::ResetSyncForPrimaryAccount() {
  if (server_type_ != EXTERNAL_LIVE_SERVER) {
    // No-op for anything other than when external servers are used.
    return true;
  }

  // For external server testing, we need to have a clean account. The following
  // code will sign in one chrome browser, get the client id and access token,
  // then clean the server data.
#if BUILDFLAG(IS_ANDROID)
  Profile& profile = CHECK_DEREF(ProfileManager::GetLastUsedProfile());
#else   // BUILDFLAG(IS_ANDROID)
  base::ScopedAllowBlockingForTesting allow_blocking;
  Profile& profile = profiles::testing::CreateProfileSync(
      g_browser_process->profile_manager(),
      g_browser_process->profile_manager()->GenerateNextProfileDirectoryPath());
#endif  // BUILDFLAG(IS_ANDROID)

  std::unique_ptr<SyncServiceImplHarness> client =
      SyncServiceImplHarness::Create(
          &profile, SyncServiceImplHarness::SigninType::UI_SIGNIN);
  CHECK(client);
  if (!client->SignInNoWaitForCompletion()) {
    LOG(ERROR) << "Failed to sign in primary account";
    return false;
  }
  if (!client->AwaitSyncTransportActive()) {
    return false;
  }
  if (!client->ResetSyncForPrimaryAccount()) {
    LOG(ERROR) << "Failed to reset sync for primary account";
    return false;
  }

#if !BUILDFLAG(IS_CHROMEOS)
  client->SignOutPrimaryAccount();
#endif  // !BUILDFLAG(IS_CHROMEOS)

  // After reset, this client will disable sync. It may log some messages that
  // do not contribute to test failures. It includes:
  //   PostClientToServerMessage with SERVER_RETURN_NOT_MY_BIRTHDAY
  //   PostClientToServerMessage with NETWORK_CONNECTION_UNAVAILABLE
  //   mcs_client fails with 401.
  LOG(WARNING) << "Finished reset account. Warning logs before "
               << "this log may be safe to ignore.";
  return true;
}

void SyncTest::WaitForDataModels(Profile* profile) {
  // Ideally the waiting for bookmarks should be done exclusively for
  // bookmark-related tests, but there are several tests that use bookmarks as
  // a way to generally check if sync is working, although the test is not
  // really about bookmarks.
  bookmarks::test::WaitForBookmarkModelToLoad(
      BookmarkModelFactory::GetForBrowserContext(profile));
}

void SyncTest::SetupMockGaiaResponses() {
  gaia::FakeOAuth2TokenResponse::Success("at1").AddToTestURLLoaderFactory(
      test_url_loader_factory_);
  test_url_loader_factory_.AddResponse(
      GaiaUrls::GetInstance()->oauth_user_info_url().spec(),
      "{ \"id\": \"12345\" }");
  test_url_loader_factory_.AddResponse(
      GaiaUrls::GetInstance()->oauth2_revoke_url().spec(), "");
}

void SyncTest::SetOAuth2TokenResponse(
    const gaia::FakeOAuth2TokenResponse& response) {
  response.AddToTestURLLoaderFactory(test_url_loader_factory_);
  base::RunLoop().RunUntilIdle();
}

void SyncTest::ClearMockGaiaResponses() {
  // Clear any mock gaia responses that might have been set.
  test_url_loader_factory_.ClearResponses();
}

bool SyncTest::TestUsesSelfNotifications() {
  // Default is True unless we are running against external servers.
  return server_type_ != EXTERNAL_LIVE_SERVER;
}

bool SyncTest::AwaitQuiescence() {
  return SyncServiceImplHarness::AwaitQuiescence(GetSyncClients());
}

void SyncTest::TriggerMigrationDoneError(syncer::DataTypeSet data_types) {
  ASSERT_TRUE(server_type_ == IN_PROCESS_FAKE_SERVER);
  fake_server_->TriggerMigrationDoneError(data_types);
}

fake_server::FakeServer* SyncTest::GetFakeServer() const {
  return fake_server_.get();
}

void SyncTest::DisableNetwork() {
  // Prevent communication with EmbeddedTestServer or any other server.
  url_loader_interceptor_ =
      std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
          [](content::URLLoaderInterceptor::RequestParams* params) -> bool {
            params->client->OnComplete(network::URLLoaderCompletionStatus(
                net::ERR_INTERNET_DISCONNECTED));
            return true;
          }));

  connection_change_simulator_.SetConnectionType(
      net::NetworkChangeNotifier::ConnectionType::CONNECTION_NONE);
}

void SyncTest::EnableNetwork() {
  url_loader_interceptor_.reset();
  connection_change_simulator_.SetConnectionType(
      net::NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET);
}

void SyncTest::TriggerSyncForDataTypes(int index,
                                       syncer::DataTypeSet data_types) {
  GetSyncService(index)->TriggerRefresh(
      syncer::SyncService::TriggerRefreshSource::kUnknown, data_types);
}

arc::SyncArcPackageHelper* SyncTest::sync_arc_helper() {
#if BUILDFLAG(IS_CHROMEOS)
  return arc::SyncArcPackageHelper::GetInstance();
#else
  return nullptr;
#endif
}

std::string SyncTest::GetCacheGuid(size_t profile_index) const {
  syncer::SyncTransportDataPrefs prefs(
      GetProfile(profile_index)->GetPrefs(),
      GetClient(profile_index)->GetGaiaIdHashForPrimaryAccount());
  return prefs.GetCacheGuid();
}

bool SyncTest::WaitForAsyncChangesToBeCommitted(size_t profile_index) const {
  // This is a workaround for E2E tests because currently there is no a good way
  // to wait for asynchronous commits to the external servers. Although
  // CommittedAllNudgedChangesChecker will wait for all the local changes to be
  // committed, it doesn't cover all the cases.
  if (server_type_ != EXTERNAL_LIVE_SERVER) {
#if BUILDFLAG(IS_ANDROID)
    // On Android, default about:blank page is loaded by default. Wait for
    // Session to be committed to prevent unexpected commit requests during
    // test. It shouldn't be called when custom passphrase is enabled because
    // SessionHierarchyMatchChecker doesn't support custom passphrases.
    if (GetSyncService(profile_index)
            ->GetUserSettings()
            ->GetSelectedTypes()
            .Has(syncer::UserSelectableType::kTabs) &&
        !SessionHierarchyMatchChecker(
             fake_server::SessionsHierarchy({{url::kAboutBlankURL}}),
             GetSyncService(profile_index), GetFakeServer())
             .Wait()) {
      return false;
    }
#endif  // BUILDFLAG(IS_ANDROID)
  }

  // Wait for any other locally nudged changes to be committed.
  if (!CommittedAllNudgedChangesChecker(GetSyncService(profile_index)).Wait()) {
    return false;
  }

  return true;
}

void SyncTest::CheckForDataTypeFailures(size_t client_index) const {
  CHECK(GetClient(client_index));

  auto* service = GetClient(client_index)->service();
  syncer::DataTypeSet types_to_check = service->GetRegisteredDataTypesForTest();
  types_to_check.RemoveAll(excluded_types_from_check_for_data_type_failures_);

  ASSERT_FALSE(service->HasAnyModelErrorForTest(types_to_check))
      << " for client " << client_index << " and types "
      << syncer::DataTypeSetToDebugString(types_to_check);
}

void SyncTest::ExcludeDataTypesFromCheckForDataTypeFailures(
    syncer::DataTypeSet types) {
  excluded_types_from_check_for_data_type_failures_ = types;
}

std::ostream& operator<<(std::ostream& stream,
                         SyncTest::SetupSyncMode sync_test_mode) {
  stream << SetupSyncModeAsString(sync_test_mode);
  return stream;
}

std::string SetupSyncModeAsString(SyncTest::SetupSyncMode sync_test_mode) {
  switch (sync_test_mode) {
    case SyncTest::SetupSyncMode::kSyncTransportOnly:
      return "kSyncTransportOnly";
    case SyncTest::SetupSyncMode::kSyncTheFeature:
      return "kSyncTheFeature";
  }
  NOTREACHED();
}

// The set of types that *can* run in transport mode. Doesn't mean they are all
// enabled by default, e.g. HISTORY requires a dedicated opt-in via
// SyncUserSettings::SetSelectedTypes().
syncer::DataTypeSet AllowedTypesInStandaloneTransportMode() {
  static_assert(65 == syncer::GetNumDataTypes(),
                "Add new types below if they can run in transport mode");

#if BUILDFLAG(IS_ANDROID)
  // On Android, `kReplaceSyncPromosWithSignInPromos` has been enabled by
  // default for a long time, so it is not expected to be exercised in tests.
  CHECK(syncer::IsReplaceSyncPromosWithSignInPromosEnabled());
#endif  // BUILDFLAG(IS_ANDROID)

  // Only some types will run by default in transport mode (i.e. without their
  // own separate opt-in).
  syncer::DataTypeSet allowed_types = {syncer::AUTOFILL_WALLET_CREDENTIAL,
                                       syncer::AUTOFILL_WALLET_DATA,
                                       syncer::AUTOFILL_WALLET_USAGE,
                                       syncer::DEVICE_INFO,
                                       syncer::SECURITY_EVENTS,
                                       syncer::SEND_TAB_TO_SELF,
                                       syncer::SHARING_MESSAGE,
                                       syncer::USER_CONSENTS};
  allowed_types.PutAll(syncer::ControlTypes());
  allowed_types.Put(syncer::CONTACT_INFO);
  allowed_types.Put(syncer::PASSWORDS);

#if BUILDFLAG(IS_CHROMEOS)
  // OS sync types run in transport mode.
  allowed_types.PutAll({syncer::APP_LIST, syncer::ARC_PACKAGE, syncer::WEB_APPS,
                        syncer::OS_PREFERENCES,
                        syncer::OS_PRIORITY_PREFERENCES});
#endif  // BUILDFLAG(IS_CHROMEOS)

  if (base::FeatureList::IsEnabled(
          switches::kEnablePreferencesAccountStorage)) {
    allowed_types.Put(syncer::PRIORITY_PREFERENCES);
#if BUILDFLAG(IS_ANDROID)
    allowed_types.Put(syncer::PREFERENCES);
#else
    // On desktop, support for transport mode for preferences is implemented
    // alongside that of search engines.
    if (base::FeatureList::IsEnabled(
            syncer::kSeparateLocalAndAccountSearchEngines)) {
      allowed_types.Put(syncer::PREFERENCES);
    }
#endif  // BUILDFLAG(IS_ANDROID)
  }
  if (base::FeatureList::IsEnabled(
          switches::kSyncEnableBookmarksInTransportMode)) {
    allowed_types.Put(syncer::BOOKMARKS);
  }
  if (syncer::IsReadingListAccountStorageEnabled()) {
    allowed_types.Put(syncer::READING_LIST);
  }
  if (syncer::IsReplaceSyncPromosWithSignInPromosEnabled()) {
    allowed_types.Put(syncer::AUTOFILL_WALLET_METADATA);
    allowed_types.Put(syncer::AUTOFILL_WALLET_OFFER);
    if (base::FeatureList::IsEnabled(
            syncer::kSyncEncryptedTabContextContainer)) {
      allowed_types.Put(syncer::ENCRYPTED_TAB_CONTEXT_CONTAINER);
      allowed_types.Put(syncer::ENCRYPTED_TAB_CONTEXT_ITEM);
    }
    if (base::FeatureList::IsEnabled(syncer::kSyncNotebook)) {
      allowed_types.Put(syncer::NOTEBOOK);
    }
    allowed_types.Put(syncer::HISTORY);
    allowed_types.Put(syncer::HISTORY_DELETE_DIRECTIVES);
    allowed_types.Put(syncer::SAVED_TAB_GROUP);
    allowed_types.Put(syncer::SESSIONS);
    allowed_types.Put(syncer::USER_EVENTS);
#if BUILDFLAG(IS_CHROMEOS)
    allowed_types.Put(syncer::APPS);
    allowed_types.Put(syncer::APP_SETTINGS);
    allowed_types.Put(syncer::PRINTERS);
    allowed_types.Put(syncer::WIFI_CONFIGURATIONS);
    allowed_types.Put(syncer::WORKSPACE_DESK);
#endif  // BUILDFLAG(IS_CHROMEOS)

#if BUILDFLAG(ENABLE_EXTENSIONS) && !BUILDFLAG(IS_CHROMEOS)
    allowed_types.Put(syncer::WEB_APPS);
#endif  // BUILDFLAG(ENABLE_EXTENSIONS) && !BUILDFLAG(IS_CHROMEOS)

    if (data_sharing::features::IsDataSharingFunctionalityEnabled()) {
      allowed_types.Put(syncer::SHARED_TAB_GROUP_DATA);
      allowed_types.Put(syncer::COLLABORATION_GROUP);

      if (base::FeatureList::IsEnabled(
              syncer::kSyncSharedTabGroupAccountData)) {
        allowed_types.Put(syncer::SHARED_TAB_GROUP_ACCOUNT_DATA);
      }

      if (base::FeatureList::IsEnabled(syncer::kSyncSharedComment)) {
        allowed_types.Put(syncer::SHARED_COMMENT);
      }
    }

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
#if BUILDFLAG(IS_CHROMEOS)
    if (base::FeatureList::IsEnabled(
            syncer::kReplaceSyncPromosWithSignInPromos)) {
      allowed_types.Put(syncer::EXTENSIONS);
      allowed_types.Put(syncer::EXTENSION_SETTINGS);
    }
#else
    allowed_types.Put(syncer::EXTENSIONS);
    allowed_types.Put(syncer::EXTENSION_SETTINGS);
#endif
#endif  // BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  }
  allowed_types.Put(syncer::AUTOFILL_VALUABLE);

  if (base::FeatureList::IsEnabled(syncer::kSyncAutofillValuableMetadata)) {
    allowed_types.Put(syncer::AUTOFILL_VALUABLE_METADATA);
  }

  if (base::FeatureList::IsEnabled(features::kSkillsEnabled)) {
    allowed_types.Put(syncer::SKILL);
  }

  if (base::FeatureList::IsEnabled(syncer::kSyncGeminiThread)) {
    allowed_types.Put(syncer::GEMINI_THREAD);
  }

  if (base::FeatureList::IsEnabled(syncer::kSyncThemesIos)) {
    allowed_types.Put(syncer::THEMES_IOS);
  }

  if (base::FeatureList::IsEnabled(syncer::kNewTabPageCustomizationThemeSync)) {
    allowed_types.Put(syncer::THEMES_ANDROID);
  }

  if (base::FeatureList::IsEnabled(syncer::kSyncNotebook)) {
    allowed_types.Put(syncer::NOTEBOOK);
  }

  if (base::FeatureList::IsEnabled(syncer::kSyncJourney)) {
    allowed_types.Put(syncer::JOURNEY);
  }

  if (base::FeatureList::IsEnabled(syncer::kSyncAccountSettings)) {
    allowed_types.Put(syncer::ACCOUNT_SETTING);
  }

  if (base::FeatureList::IsEnabled(syncer::kSyncAIThread)) {
    allowed_types.Put(syncer::AI_THREAD);
  }

  if (base::FeatureList::IsEnabled(syncer::kSyncContextualTask)) {
    allowed_types.Put(syncer::CONTEXTUAL_TASK);
  }

  if (base::FeatureList::IsEnabled(
          syncer::kSeparateLocalAndAccountSearchEngines) &&
      // Support for transport mode for search engines is implemented alongside
      // that of preferences.
      base::FeatureList::IsEnabled(switches::kEnablePreferencesAccountStorage)
#if BUILDFLAG(IS_ANDROID)
      && base::FeatureList::IsEnabled(syncer::kSyncSearchEnginesAndroidLFF)
#endif
  ) {
    allowed_types.Put(syncer::SEARCH_ENGINES);
  }

#if BUILDFLAG(IS_ANDROID)
  if (base::FeatureList::IsEnabled(syncer::kWebApkBackupAndRestoreBackend)) {
    allowed_types.Put(syncer::WEB_APKS);
  }
#else   // BUILDFLAG(IS_ANDROID)
  if (base::FeatureList::IsEnabled(syncer::kSeparateLocalAndAccountThemes)) {
    allowed_types.Put(syncer::THEMES);
  }

  // These types are excluded on Android as they run outside Chrome.
  allowed_types.Put(syncer::INCOMING_PASSWORD_SHARING_INVITATION);
  allowed_types.Put(syncer::OUTGOING_PASSWORD_SHARING_INVITATION);
  allowed_types.Put(syncer::WEBAUTHN_CREDENTIAL);
#endif  // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
  if (base::FeatureList::IsEnabled(
          syncer::kSpellcheckSeparateLocalAndAccountDictionaries)) {
    allowed_types.Put(syncer::DICTIONARY);
  }
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)

  return allowed_types;
}
