chromium/chromeos/ash/services/device_sync/device_sync_service_unittest.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <vector>

#include "ash/constants/ash_features.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/null_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "base/timer/mock_timer.h"
#include "chromeos/ash/components/multidevice/remote_device_test_util.h"
#include "chromeos/ash/components/multidevice/secure_message_delegate.h"
#include "chromeos/ash/components/network/network_handler_test_helper.h"
#include "chromeos/ash/services/device_sync/attestation_certificates_syncer.h"
#include "chromeos/ash/services/device_sync/cryptauth_device_manager_impl.h"
#include "chromeos/ash/services/device_sync/cryptauth_device_registry_impl.h"
#include "chromeos/ash/services/device_sync/cryptauth_enroller.h"
#include "chromeos/ash/services/device_sync/cryptauth_enrollment_manager_impl.h"
#include "chromeos/ash/services/device_sync/cryptauth_feature_type.h"
#include "chromeos/ash/services/device_sync/cryptauth_gcm_manager_impl.h"
#include "chromeos/ash/services/device_sync/cryptauth_key_registry_impl.h"
#include "chromeos/ash/services/device_sync/cryptauth_scheduler_impl.h"
#include "chromeos/ash/services/device_sync/cryptauth_v2_device_manager_impl.h"
#include "chromeos/ash/services/device_sync/cryptauth_v2_enrollment_manager_impl.h"
#include "chromeos/ash/services/device_sync/device_sync_impl.h"
#include "chromeos/ash/services/device_sync/fake_cryptauth_device_manager.h"
#include "chromeos/ash/services/device_sync/fake_cryptauth_device_notifier.h"
#include "chromeos/ash/services/device_sync/fake_cryptauth_enrollment_manager.h"
#include "chromeos/ash/services/device_sync/fake_cryptauth_feature_status_setter.h"
#include "chromeos/ash/services/device_sync/fake_cryptauth_gcm_manager.h"
#include "chromeos/ash/services/device_sync/fake_cryptauth_scheduler.h"
#include "chromeos/ash/services/device_sync/fake_cryptauth_v2_device_manager.h"
#include "chromeos/ash/services/device_sync/fake_device_sync_observer.h"
#include "chromeos/ash/services/device_sync/fake_remote_device_provider.h"
#include "chromeos/ash/services/device_sync/fake_software_feature_manager.h"
#include "chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_client_app_metadata.pb.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_common.pb.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_v2_test_util.h"
#include "chromeos/ash/services/device_sync/public/cpp/device_sync_prefs.h"
#include "chromeos/ash/services/device_sync/public/cpp/fake_client_app_metadata_provider.h"
#include "chromeos/ash/services/device_sync/public/cpp/fake_gcm_device_info_provider.h"
#include "chromeos/ash/services/device_sync/public/mojom/device_sync.mojom.h"
#include "chromeos/ash/services/device_sync/remote_device_provider_impl.h"
#include "chromeos/ash/services/device_sync/software_feature_manager_impl.h"
#include "components/gcm_driver/fake_gcm_driver.h"
#include "components/gcm_driver/instance_id/instance_id.h"
#include "components/gcm_driver/instance_id/instance_id_driver.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

namespace device_sync {

namespace {

const char kTestEmail[] = "[email protected]";
const char kTestGcmDeviceInfoLongDeviceId[] = "longDeviceId";
const char kTestCryptAuthGCMRegistrationId[] =
    "aValid:cryptAuth-RegistrationId";
const char kTestDeprecatedCryptAuthGCMRegistrationId[] =
    "inValid-cryptAuthRegistrationId";
const char kLocalDevicePublicKey[] = "localDevicePublicKey";
const size_t kNumTestDevices = 5u;

const cryptauth::GcmDeviceInfo& GetTestGcmDeviceInfo() {
  static const base::NoDestructor<cryptauth::GcmDeviceInfo> gcm_device_info([] {
    cryptauth::GcmDeviceInfo gcm_device_info;
    gcm_device_info.set_long_device_id(kTestGcmDeviceInfoLongDeviceId);
    return gcm_device_info;
  }());

  return *gcm_device_info;
}

multidevice::RemoteDeviceList GenerateTestRemoteDevices() {
  multidevice::RemoteDeviceList devices =
      multidevice::CreateRemoteDeviceListForTest(kNumTestDevices);

  // One of the synced devices refers to the current (i.e., local) device.
  // Arbitrarily choose the 0th device as the local one and set its public key
  // accordingly.
  devices[0].public_key = kLocalDevicePublicKey;

  return devices;
}

std::vector<cryptauth::ExternalDeviceInfo> GenerateTestExternalDeviceInfos(
    const multidevice::RemoteDeviceList& remote_devices) {
  std::vector<cryptauth::ExternalDeviceInfo> device_infos;

  for (const auto& remote_device : remote_devices) {
    cryptauth::ExternalDeviceInfo info;
    info.set_public_key(remote_device.public_key);
    device_infos.push_back(info);
  }

  return device_infos;
}

std::vector<cryptauth::IneligibleDevice> GenerateTestIneligibleDevices(
    const std::vector<cryptauth::ExternalDeviceInfo>& device_infos) {
  std::vector<cryptauth::IneligibleDevice> ineligible_devices;

  for (const auto& device_info : device_infos) {
    cryptauth::IneligibleDevice device;
    device.mutable_device()->CopyFrom(device_info);
    ineligible_devices.push_back(device);
  }

  return ineligible_devices;
}

// Delegate which invokes the Closure provided to its constructor when a
// delegate function is invoked.
class FakeSoftwareFeatureManagerDelegate
    : public FakeSoftwareFeatureManager::Delegate {
 public:
  explicit FakeSoftwareFeatureManagerDelegate(
      base::RepeatingClosure on_delegate_call_closure)
      : on_delegate_call_closure_(on_delegate_call_closure) {}

  ~FakeSoftwareFeatureManagerDelegate() override = default;

  // FakeSoftwareFeatureManager::Delegate:
  void OnSetSoftwareFeatureStateCalled() override {
    on_delegate_call_closure_.Run();
  }
  void OnSetFeatureStatusCalled() override { on_delegate_call_closure_.Run(); }
  void OnFindEligibleDevicesCalled() override {
    on_delegate_call_closure_.Run();
  }

 private:
  base::RepeatingClosure on_delegate_call_closure_;
};

// Delegate which invokes the Closure provided to its constructor when a
// delegate function is invoked.
class FakeCryptAuthFeatureStatusSetterDelegate
    : public FakeCryptAuthFeatureStatusSetter::Delegate {
 public:
  explicit FakeCryptAuthFeatureStatusSetterDelegate(
      base::RepeatingClosure on_delegate_call_closure)
      : on_delegate_call_closure_(on_delegate_call_closure) {}

  ~FakeCryptAuthFeatureStatusSetterDelegate() override = default;

  // FakeCryptAuthFeatureStatusSetter::Delegate:
  void OnSetFeatureStatusCalled() override { on_delegate_call_closure_.Run(); }

 private:
  base::RepeatingClosure on_delegate_call_closure_;
};

// Delegate which invokes the Closure provided to its constructor when a
// delegate function is invoked.
class FakeCryptAuthDeviceNotifierDelegate
    : public FakeCryptAuthDeviceNotifier::Delegate {
 public:
  explicit FakeCryptAuthDeviceNotifierDelegate(
      base::RepeatingClosure on_delegate_call_closure)
      : on_delegate_call_closure_(on_delegate_call_closure) {}

  ~FakeCryptAuthDeviceNotifierDelegate() override = default;

  // FakeCryptAuthDeviceNotifier::Delegate:
  void OnNotifyDevicesCalled() override { on_delegate_call_closure_.Run(); }

 private:
  base::RepeatingClosure on_delegate_call_closure_;
};

class MockInstanceIDDriver : public instance_id::InstanceIDDriver {
 public:
  MockInstanceIDDriver() : InstanceIDDriver(/*gcm_driver=*/nullptr) {}
  ~MockInstanceIDDriver() override = default;
  MOCK_METHOD(instance_id::InstanceID*,
              GetInstanceID,
              (const std::string& app_id),
              (override));
  MOCK_METHOD(void, RemoveInstanceID, (const std::string& app_id), (override));
  MOCK_METHOD(bool,
              ExistsInstanceID,
              (const std::string& app_id),
              (const override));
};

class FakeCryptAuthGCMManagerFactory : public CryptAuthGCMManagerImpl::Factory {
 public:
  FakeCryptAuthGCMManagerFactory(
      instance_id::InstanceIDDriver* fake_instance_id_driver,
      TestingPrefServiceSimple* test_pref_service)
      : fake_instance_id_driver_(fake_instance_id_driver),
        test_pref_service_(test_pref_service) {}

  ~FakeCryptAuthGCMManagerFactory() override = default;

  FakeCryptAuthGCMManager* instance() { return instance_; }

  void SetInitialRegistrationId(const std::string& registration_id) {
    initial_registration_id_ = registration_id;
  }

 private:
  // CryptAuthGCMManagerImpl::Factory:
  std::unique_ptr<CryptAuthGCMManager> CreateInstance(
      instance_id::InstanceIDDriver* instance_id_driver,
      PrefService* pref_service) override {
    EXPECT_EQ(fake_instance_id_driver_, instance_id_driver);
    EXPECT_EQ(test_pref_service_, pref_service);

    // Only one instance is expected to be created per test.
    EXPECT_FALSE(instance_);

    auto instance =
        std::make_unique<FakeCryptAuthGCMManager>(initial_registration_id_);
    instance_ = instance.get();

    return instance;
  }

  raw_ptr<instance_id::InstanceIDDriver, DanglingUntriaged>
      fake_instance_id_driver_;
  raw_ptr<TestingPrefServiceSimple> test_pref_service_;
  std::string initial_registration_id_;
  raw_ptr<FakeCryptAuthGCMManager, DanglingUntriaged> instance_ = nullptr;
};

class FakeCryptAuthDeviceManagerFactory
    : public CryptAuthDeviceManagerImpl::Factory {
 public:
  FakeCryptAuthDeviceManagerFactory(
      base::SimpleTestClock* simple_test_clock,
      FakeCryptAuthGCMManagerFactory* fake_cryptauth_gcm_manager_factory,
      TestingPrefServiceSimple* test_pref_service)
      : simple_test_clock_(simple_test_clock),
        fake_cryptauth_gcm_manager_factory_(fake_cryptauth_gcm_manager_factory),
        test_pref_service_(test_pref_service) {}

  ~FakeCryptAuthDeviceManagerFactory() override = default;

  FakeCryptAuthDeviceManager* instance() { return instance_; }

 private:
  // CryptAuthDeviceManagerImpl::Factory:
  std::unique_ptr<CryptAuthDeviceManager> CreateInstance(
      base::Clock* clock,
      CryptAuthClientFactory* client_factory,
      CryptAuthGCMManager* gcm_manager,
      PrefService* pref_service) override {
    EXPECT_TRUE(features::ShouldUseV1DeviceSync());
    EXPECT_EQ(simple_test_clock_, clock);
    EXPECT_EQ(fake_cryptauth_gcm_manager_factory_->instance(), gcm_manager);
    EXPECT_EQ(test_pref_service_, pref_service);

    // Only one instance is expected to be created per test.
    EXPECT_FALSE(instance_);

    auto instance = std::make_unique<FakeCryptAuthDeviceManager>();
    instance_ = instance.get();

    return instance;
  }

  raw_ptr<base::SimpleTestClock> simple_test_clock_;
  raw_ptr<FakeCryptAuthGCMManagerFactory> fake_cryptauth_gcm_manager_factory_;
  raw_ptr<TestingPrefServiceSimple> test_pref_service_;

  raw_ptr<FakeCryptAuthDeviceManager, DanglingUntriaged> instance_ = nullptr;
};

class FakeCryptAuthDeviceRegistry : public CryptAuthDeviceRegistry {
 public:
  FakeCryptAuthDeviceRegistry() = default;
  ~FakeCryptAuthDeviceRegistry() override = default;

 private:
  // CryptAuthDeviceRegistry:
  void OnDeviceRegistryUpdated() override {}
};

class FakeCryptAuthDeviceRegistryFactory
    : public CryptAuthDeviceRegistryImpl::Factory {
 public:
  explicit FakeCryptAuthDeviceRegistryFactory(
      TestingPrefServiceSimple* test_pref_service)
      : test_pref_service_(test_pref_service) {}

  ~FakeCryptAuthDeviceRegistryFactory() override = default;

  FakeCryptAuthDeviceRegistry* instance() { return instance_; }

 private:
  // CryptAuthDeviceRegistryImpl::Factory:
  std::unique_ptr<CryptAuthDeviceRegistry> CreateInstance(
      PrefService* pref_service) override {
    EXPECT_TRUE(features::ShouldUseV2DeviceSync());
    EXPECT_EQ(test_pref_service_, pref_service);

    // Only one instance is expected to be created per test.
    EXPECT_FALSE(instance_);
    auto instance = std::make_unique<FakeCryptAuthDeviceRegistry>();
    instance_ = instance.get();
    return instance;
  }

  raw_ptr<TestingPrefServiceSimple> test_pref_service_;
  raw_ptr<FakeCryptAuthDeviceRegistry, DanglingUntriaged> instance_ = nullptr;
};

class FakeCryptAuthKeyRegistry : public CryptAuthKeyRegistry {
 public:
  FakeCryptAuthKeyRegistry() = default;
  ~FakeCryptAuthKeyRegistry() override = default;

 private:
  // CryptAuthKeyRegistry:
  void OnKeyRegistryUpdated() override {}
};

class FakeCryptAuthKeyRegistryFactory
    : public CryptAuthKeyRegistryImpl::Factory {
 public:
  explicit FakeCryptAuthKeyRegistryFactory(
      TestingPrefServiceSimple* test_pref_service)
      : test_pref_service_(test_pref_service) {}

  ~FakeCryptAuthKeyRegistryFactory() override = default;

  FakeCryptAuthKeyRegistry* instance() { return instance_; }

 private:
  // CryptAuthKeyRegistryImpl::Factory:
  std::unique_ptr<CryptAuthKeyRegistry> CreateInstance(
      PrefService* pref_service) override {
    EXPECT_TRUE(base::FeatureList::IsEnabled(features::kCryptAuthV2Enrollment));
    EXPECT_EQ(test_pref_service_, pref_service);

    // Only one instance is expected to be created per test.
    EXPECT_FALSE(instance_);
    auto instance = std::make_unique<FakeCryptAuthKeyRegistry>();
    instance_ = instance.get();
    return instance;
  }

  raw_ptr<TestingPrefServiceSimple> test_pref_service_;
  raw_ptr<FakeCryptAuthKeyRegistry, DanglingUntriaged> instance_ = nullptr;
};

class FakeCryptAuthSchedulerFactory : public CryptAuthSchedulerImpl::Factory {
 public:
  explicit FakeCryptAuthSchedulerFactory(
      TestingPrefServiceSimple* test_pref_service)
      : test_pref_service_(test_pref_service) {}

  ~FakeCryptAuthSchedulerFactory() override = default;

  FakeCryptAuthScheduler* instance() { return instance_; }

 private:
  // CryptAuthSchedulerImpl::Factory:
  std::unique_ptr<CryptAuthScheduler> CreateInstance(
      PrefService* pref_service,
      NetworkStateHandler* network_state_handler,
      base::Clock* clock,
      std::unique_ptr<base::OneShotTimer> enrollment_timer,
      std::unique_ptr<base::OneShotTimer> device_sync_timer) override {
    EXPECT_TRUE(base::FeatureList::IsEnabled(features::kCryptAuthV2Enrollment));
    EXPECT_EQ(test_pref_service_, pref_service);

    // Only one instance is expected to be created per test.
    EXPECT_FALSE(instance_);

    auto instance = std::make_unique<FakeCryptAuthScheduler>();
    instance_ = instance.get();

    return instance;
  }

  raw_ptr<TestingPrefServiceSimple> test_pref_service_;
  raw_ptr<FakeCryptAuthScheduler, DanglingUntriaged> instance_ = nullptr;
};

class FakeCryptAuthV2DeviceManagerFactory
    : public CryptAuthV2DeviceManagerImpl::Factory {
 public:
  FakeCryptAuthV2DeviceManagerFactory(
      const cryptauthv2::ClientAppMetadata& client_app_metadata,
      FakeCryptAuthDeviceRegistryFactory* fake_device_registry_factory,
      FakeCryptAuthKeyRegistryFactory* fake_key_registry_factory,
      FakeCryptAuthGCMManagerFactory* fake_gcm_manager_factory,
      FakeCryptAuthSchedulerFactory* fake_scheduler_factory,
      TestingPrefServiceSimple* test_pref_service)
      : client_app_metadata_(client_app_metadata),
        fake_device_registry_factory_(fake_device_registry_factory),
        fake_key_registry_factory_(fake_key_registry_factory),
        fake_gcm_manager_factory_(fake_gcm_manager_factory),
        fake_scheduler_factory_(fake_scheduler_factory),
        test_pref_service_(test_pref_service) {}

  ~FakeCryptAuthV2DeviceManagerFactory() override = default;

  FakeCryptAuthV2DeviceManager* instance() { return instance_; }

 private:
  // CryptAuthV2DeviceManagerImpl::Factory:
  std::unique_ptr<CryptAuthV2DeviceManager> CreateInstance(
      const cryptauthv2::ClientAppMetadata& client_app_metadata,
      CryptAuthDeviceRegistry* device_registry,
      CryptAuthKeyRegistry* key_registry,
      CryptAuthClientFactory* client_factory,
      CryptAuthGCMManager* gcm_manager,
      CryptAuthScheduler* scheduler,
      PrefService* pref_service,
      AttestationCertificatesSyncer::GetAttestationCertificatesFunction
          get_attestation_certificates_function) override {
    EXPECT_TRUE(features::ShouldUseV2DeviceSync());
    EXPECT_EQ(client_app_metadata_.SerializeAsString(),
              client_app_metadata.SerializeAsString());
    EXPECT_EQ(fake_device_registry_factory_->instance(), device_registry);
    EXPECT_EQ(fake_key_registry_factory_->instance(), key_registry);
    EXPECT_EQ(fake_gcm_manager_factory_->instance(), gcm_manager);
    EXPECT_EQ(fake_scheduler_factory_->instance(), scheduler);
    EXPECT_EQ(test_pref_service_, pref_service);

    // Only one instance is expected to be created per test.
    EXPECT_FALSE(instance_);

    auto instance = std::make_unique<FakeCryptAuthV2DeviceManager>();
    instance_ = instance.get();

    return instance;
  }

  cryptauthv2::ClientAppMetadata client_app_metadata_;
  raw_ptr<FakeCryptAuthDeviceRegistryFactory> fake_device_registry_factory_ =
      nullptr;
  raw_ptr<FakeCryptAuthKeyRegistryFactory> fake_key_registry_factory_ = nullptr;
  raw_ptr<FakeCryptAuthGCMManagerFactory> fake_gcm_manager_factory_ = nullptr;
  raw_ptr<FakeCryptAuthSchedulerFactory> fake_scheduler_factory_ = nullptr;
  raw_ptr<TestingPrefServiceSimple> test_pref_service_ = nullptr;
  raw_ptr<FakeCryptAuthV2DeviceManager, DanglingUntriaged> instance_ = nullptr;
};

class FakeCryptAuthEnrollmentManagerFactory
    : public CryptAuthEnrollmentManagerImpl::Factory {
 public:
  FakeCryptAuthEnrollmentManagerFactory(
      base::SimpleTestClock* simple_test_clock,
      FakeCryptAuthGCMManagerFactory* fake_cryptauth_gcm_manager_factory,
      TestingPrefServiceSimple* test_pref_service)
      : simple_test_clock_(simple_test_clock),
        fake_cryptauth_gcm_manager_factory_(fake_cryptauth_gcm_manager_factory),
        test_pref_service_(test_pref_service) {}

  ~FakeCryptAuthEnrollmentManagerFactory() override = default;

  void set_device_already_enrolled_in_cryptauth(
      bool device_already_enrolled_in_cryptauth) {
    device_already_enrolled_in_cryptauth_ =
        device_already_enrolled_in_cryptauth;
  }

  FakeCryptAuthEnrollmentManager* instance() { return instance_; }

  // CryptAuthEnrollmentManagerImpl::Factory:
  std::unique_ptr<CryptAuthEnrollmentManager> CreateInstance(
      base::Clock* clock,
      std::unique_ptr<CryptAuthEnrollerFactory> enroller_factory,
      std::unique_ptr<multidevice::SecureMessageDelegate>
          secure_message_delegate,
      const cryptauth::GcmDeviceInfo& device_info,
      CryptAuthGCMManager* gcm_manager,
      PrefService* pref_service) override {
    EXPECT_FALSE(
        base::FeatureList::IsEnabled(features::kCryptAuthV2Enrollment));
    EXPECT_EQ(simple_test_clock_, clock);
    EXPECT_EQ(kTestGcmDeviceInfoLongDeviceId, device_info.long_device_id());
    EXPECT_EQ(fake_cryptauth_gcm_manager_factory_->instance(), gcm_manager);
    EXPECT_EQ(test_pref_service_, pref_service);

    // Only one instance is expected to be created per test.
    EXPECT_FALSE(instance_);

    auto instance = std::make_unique<FakeCryptAuthEnrollmentManager>();
    instance->set_user_public_key(kLocalDevicePublicKey);
    instance->set_is_enrollment_valid(device_already_enrolled_in_cryptauth_);
    instance_ = instance.get();
    LOG(INFO) << " made enrollment manager 1";
    return instance;
  }

 private:
  raw_ptr<base::SimpleTestClock> simple_test_clock_;
  raw_ptr<FakeCryptAuthGCMManagerFactory> fake_cryptauth_gcm_manager_factory_;
  raw_ptr<TestingPrefServiceSimple> test_pref_service_;
  bool device_already_enrolled_in_cryptauth_ = false;
  raw_ptr<FakeCryptAuthEnrollmentManager, DanglingUntriaged> instance_ =
      nullptr;
};

class FakeCryptAuthV2EnrollmentManagerFactory
    : public CryptAuthV2EnrollmentManagerImpl::Factory {
 public:
  FakeCryptAuthV2EnrollmentManagerFactory(
      const cryptauthv2::ClientAppMetadata& client_app_metadata,
      FakeCryptAuthKeyRegistryFactory* fake_cryptauth_key_registry_factory,
      FakeCryptAuthGCMManagerFactory* fake_cryptauth_gcm_manager_factory,
      FakeCryptAuthSchedulerFactory* fake_cryptauth_scheduler_factory,
      TestingPrefServiceSimple* test_pref_service,
      base::SimpleTestClock* simple_test_clock)
      : client_app_metadata_(client_app_metadata),
        fake_cryptauth_key_registry_factory_(
            fake_cryptauth_key_registry_factory),
        fake_cryptauth_gcm_manager_factory_(fake_cryptauth_gcm_manager_factory),
        fake_cryptauth_scheduler_factory_(fake_cryptauth_scheduler_factory),
        test_pref_service_(test_pref_service),
        simple_test_clock_(simple_test_clock) {}

  ~FakeCryptAuthV2EnrollmentManagerFactory() override = default;

  void set_device_already_enrolled_in_cryptauth(
      bool device_already_enrolled_in_cryptauth) {
    device_already_enrolled_in_cryptauth_ =
        device_already_enrolled_in_cryptauth;
  }

  FakeCryptAuthEnrollmentManager* instance() { return instance_; }

  // CryptAuthV2EnrollmentManagerImpl::Factory:
  std::unique_ptr<CryptAuthEnrollmentManager> CreateInstance(
      const cryptauthv2::ClientAppMetadata& client_app_metadata,
      CryptAuthKeyRegistry* key_registry,
      CryptAuthClientFactory* client_factory,
      CryptAuthGCMManager* gcm_manager,
      CryptAuthScheduler* scheduler,
      PrefService* pref_service,
      base::Clock* clock) override {
    EXPECT_TRUE(base::FeatureList::IsEnabled(features::kCryptAuthV2Enrollment));
    EXPECT_EQ(client_app_metadata_.SerializeAsString(),
              client_app_metadata.SerializeAsString());
    EXPECT_EQ(fake_cryptauth_key_registry_factory_->instance(), key_registry);
    EXPECT_EQ(fake_cryptauth_gcm_manager_factory_->instance(), gcm_manager);
    EXPECT_EQ(fake_cryptauth_scheduler_factory_->instance(), scheduler);
    EXPECT_EQ(test_pref_service_, pref_service);
    EXPECT_EQ(simple_test_clock_, clock);

    // Only one instance is expected to be created per test.
    EXPECT_FALSE(instance_);

    auto instance = std::make_unique<FakeCryptAuthEnrollmentManager>();
    instance->set_user_public_key(kLocalDevicePublicKey);
    instance->set_is_enrollment_valid(device_already_enrolled_in_cryptauth_);
    instance_ = instance.get();
    LOG(INFO) << " made enrollment manager 2";
    return instance;
  }

 private:
  cryptauthv2::ClientAppMetadata client_app_metadata_;
  raw_ptr<FakeCryptAuthKeyRegistryFactory> fake_cryptauth_key_registry_factory_;
  raw_ptr<FakeCryptAuthGCMManagerFactory> fake_cryptauth_gcm_manager_factory_;
  raw_ptr<FakeCryptAuthSchedulerFactory> fake_cryptauth_scheduler_factory_;
  raw_ptr<TestingPrefServiceSimple> test_pref_service_;
  raw_ptr<base::SimpleTestClock> simple_test_clock_;
  bool device_already_enrolled_in_cryptauth_ = false;
  raw_ptr<FakeCryptAuthEnrollmentManager, DanglingUntriaged> instance_ =
      nullptr;
};

class FakeRemoteDeviceProviderFactory
    : public RemoteDeviceProviderImpl::Factory {
 public:
  FakeRemoteDeviceProviderFactory(
      const multidevice::RemoteDeviceList& initial_devices,
      signin::IdentityManager* identity_manager,
      FakeCryptAuthDeviceManagerFactory* fake_cryptauth_device_manager_factory,
      FakeCryptAuthV2DeviceManagerFactory*
          fake_cryptauth_v2_device_manager_factory,
      FakeCryptAuthEnrollmentManagerFactory*
          fake_cryptauth_enrollment_manager_factory,
      FakeCryptAuthV2EnrollmentManagerFactory*
          fake_cryptauth_v2_enrollment_manager_factory)
      : initial_devices_(initial_devices),
        identity_manager_(identity_manager),
        fake_cryptauth_device_manager_factory_(
            fake_cryptauth_device_manager_factory),
        fake_cryptauth_v2_device_manager_factory_(
            fake_cryptauth_v2_device_manager_factory),
        fake_cryptauth_enrollment_manager_factory_(
            fake_cryptauth_enrollment_manager_factory),
        fake_cryptauth_v2_enrollment_manager_factory_(
            fake_cryptauth_v2_enrollment_manager_factory) {}

  ~FakeRemoteDeviceProviderFactory() override = default;

  FakeRemoteDeviceProvider* instance() { return instance_; }

  // RemoteDeviceProviderImpl::Factory:
  std::unique_ptr<RemoteDeviceProvider> CreateInstance(
      CryptAuthDeviceManager* device_manager,
      CryptAuthV2DeviceManager* v2_device_manager,
      const std::string& user_email,
      const std::string& user_private_key) override {
    EXPECT_EQ(fake_cryptauth_device_manager_factory_->instance(),
              device_manager);
    EXPECT_EQ(fake_cryptauth_v2_device_manager_factory_->instance(),
              v2_device_manager);
    EXPECT_EQ(
        identity_manager_->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
            .email,
        user_email);
    if (base::FeatureList::IsEnabled(features::kCryptAuthV2Enrollment)) {
      EXPECT_EQ(fake_cryptauth_v2_enrollment_manager_factory_->instance()
                    ->GetUserPrivateKey(),
                user_private_key);
    } else {
      EXPECT_EQ(fake_cryptauth_enrollment_manager_factory_->instance()
                    ->GetUserPrivateKey(),
                user_private_key);
    }

    // Only one instance is expected to be created per test.
    EXPECT_FALSE(instance_);

    auto instance = std::make_unique<FakeRemoteDeviceProvider>();
    instance->set_synced_remote_devices(*initial_devices_);
    instance_ = instance.get();

    return instance;
  }

 private:
  const raw_ref<const multidevice::RemoteDeviceList> initial_devices_;

  raw_ptr<signin::IdentityManager, DanglingUntriaged> identity_manager_;
  raw_ptr<FakeCryptAuthDeviceManagerFactory>
      fake_cryptauth_device_manager_factory_;
  raw_ptr<FakeCryptAuthV2DeviceManagerFactory>
      fake_cryptauth_v2_device_manager_factory_;
  raw_ptr<FakeCryptAuthEnrollmentManagerFactory>
      fake_cryptauth_enrollment_manager_factory_;
  raw_ptr<FakeCryptAuthV2EnrollmentManagerFactory>
      fake_cryptauth_v2_enrollment_manager_factory_;

  raw_ptr<FakeRemoteDeviceProvider, DanglingUntriaged> instance_ = nullptr;
};

class FakeSoftwareFeatureManagerFactory
    : public SoftwareFeatureManagerImpl::Factory {
 public:
  FakeSoftwareFeatureManagerFactory() = default;
  ~FakeSoftwareFeatureManagerFactory() override = default;

  FakeSoftwareFeatureManager* instance() { return instance_; }

  // SoftwareFeatureManagerImpl::Factory:
  std::unique_ptr<SoftwareFeatureManager> CreateInstance(
      CryptAuthClientFactory* cryptauth_client_factory,
      CryptAuthFeatureStatusSetter* feature_status_setter) override {
    EXPECT_TRUE(features::ShouldUseV1DeviceSync());

    // Only one instance is expected to be created per test.
    EXPECT_FALSE(instance_);

    auto instance = std::make_unique<FakeSoftwareFeatureManager>();
    instance_ = instance.get();

    return instance;
  }

 private:
  raw_ptr<FakeSoftwareFeatureManager, DanglingUntriaged> instance_ = nullptr;
};

}  // namespace

// TODO(jamescook): Rename to DeviceSyncImplTest because it's actually testing
// the DeviceSync implementation.
class DeviceSyncServiceTest
    : public ::testing::TestWithParam<std::tuple<bool, bool, bool>> {
 public:
  class FakeDeviceSyncImplFactory : public DeviceSyncImpl::Factory {
   public:
    FakeDeviceSyncImplFactory(
        std::unique_ptr<base::MockOneShotTimer> mock_timer,
        base::SimpleTestClock* simple_test_clock)
        : mock_timer_(std::move(mock_timer)),
          simple_test_clock_(simple_test_clock) {}

    ~FakeDeviceSyncImplFactory() override = default;

    // DeviceSyncImpl::Factory:
    std::unique_ptr<DeviceSyncBase> CreateInstance(
        signin::IdentityManager* identity_manager,
        instance_id::InstanceIDDriver* instance_id_driver,
        PrefService* profile_prefs,
        const GcmDeviceInfoProvider* gcm_device_info_provider,
        ClientAppMetadataProvider* client_app_metadata_provider,
        scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
        std::unique_ptr<base::OneShotTimer> timer,
        AttestationCertificatesSyncer::GetAttestationCertificatesFunction
            get_attestation_certificates_function) override {
      return base::WrapUnique(new DeviceSyncImpl(
          identity_manager, instance_id_driver, profile_prefs,
          gcm_device_info_provider, client_app_metadata_provider,
          std::move(url_loader_factory), simple_test_clock_,
          std::move(mock_timer_), get_attestation_certificates_function));
    }

   private:
    std::unique_ptr<base::MockOneShotTimer> mock_timer_;
    raw_ptr<base::SimpleTestClock> simple_test_clock_;
  };

  DeviceSyncServiceTest()
      : test_devices_(GenerateTestRemoteDevices()),
        test_device_infos_(GenerateTestExternalDeviceInfos(test_devices_)),
        test_ineligible_devices_(
            GenerateTestIneligibleDevices(test_device_infos_)) {}

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

  ~DeviceSyncServiceTest() override = default;

  void SetUp() override {
    std::vector<base::test::FeatureRef> enabled_features;
    std::vector<base::test::FeatureRef> disabled_features;

    // Choose between v1 and v2 Enrollment infrastructure based on the first
    // parameter provided by ::testing::TestWithParam<std::tuple<bool, bool,
    // bool>>.
    if (std::get<0>(GetParam())) {
      enabled_features.push_back(features::kCryptAuthV2Enrollment);
    } else {
      disabled_features.push_back(features::kCryptAuthV2Enrollment);
    }

    // Choose whether or not to enable v2 DeviceSync feature flag based on the
    // second parameter provided by ::testing::TestWithParam<std::tuple<bool,
    // bool, bool>>. Even if the flag is enabled, v2 DeviceSync should only be
    // used if v2 Enrollment is also enabled.
    if (std::get<1>(GetParam())) {
      enabled_features.push_back(features::kCryptAuthV2DeviceSync);
    } else {
      disabled_features.push_back(features::kCryptAuthV2DeviceSync);
    }

    // Choose whether or not to flip the flag to disable v1 DeviceSync based on
    // the third parameter provided by ::testing::TestWithParam<std::tuple<bool,
    // bool, bool>>. Even if the flag is flipped, v1 DeviceSync should only be
    // disabled if v2 Enrollment and v2 DeviceSync are enabled.
    if (std::get<2>(GetParam())) {
      enabled_features.push_back(features::kDisableCryptAuthV1DeviceSync);
    } else {
      disabled_features.push_back(features::kDisableCryptAuthV1DeviceSync);
    }

    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);

    network_handler_test_helper_ = std::make_unique<NetworkHandlerTestHelper>();
    base::RunLoop().RunUntilIdle();

    test_pref_service_ = std::make_unique<TestingPrefServiceSimple>();
    RegisterProfilePrefs(test_pref_service_->registry());

    simple_test_clock_ = std::make_unique<base::SimpleTestClock>();

    identity_test_environment_ =
        std::make_unique<signin::IdentityTestEnvironment>();

    fake_cryptauth_gcm_manager_factory_ =
        std::make_unique<FakeCryptAuthGCMManagerFactory>(
            &fake_instance_id_driver_, test_pref_service_.get());
    CryptAuthGCMManagerImpl::Factory::SetFactoryForTesting(
        fake_cryptauth_gcm_manager_factory_.get());

    // ---------- Begin: Only used for v2 Enrollment ----------
    fake_client_app_metadata_provider_ =
        std::make_unique<FakeClientAppMetadataProvider>();

    fake_cryptauth_key_registry_factory_ =
        std::make_unique<FakeCryptAuthKeyRegistryFactory>(
            test_pref_service_.get());
    CryptAuthKeyRegistryImpl::Factory::SetFactoryForTesting(
        fake_cryptauth_key_registry_factory_.get());

    fake_cryptauth_scheduler_factory_ =
        std::make_unique<FakeCryptAuthSchedulerFactory>(
            test_pref_service_.get());
    CryptAuthSchedulerImpl::Factory::SetFactoryForTesting(
        fake_cryptauth_scheduler_factory_.get());

    fake_cryptauth_v2_enrollment_manager_factory_ =
        std::make_unique<FakeCryptAuthV2EnrollmentManagerFactory>(
            cryptauthv2::GetClientAppMetadataForTest(),
            fake_cryptauth_key_registry_factory_.get(),
            fake_cryptauth_gcm_manager_factory_.get(),
            fake_cryptauth_scheduler_factory_.get(), test_pref_service_.get(),
            simple_test_clock_.get());
    CryptAuthV2EnrollmentManagerImpl::Factory::SetFactoryForTesting(
        fake_cryptauth_v2_enrollment_manager_factory_.get());
    // ---------- End: Only used for v2 Enrollment ----------

    // ---------- Begin: Only used for v1 Enrollment ----------
    fake_cryptauth_enrollment_manager_factory_ =
        std::make_unique<FakeCryptAuthEnrollmentManagerFactory>(
            simple_test_clock_.get(), fake_cryptauth_gcm_manager_factory_.get(),
            test_pref_service_.get());
    CryptAuthEnrollmentManagerImpl::Factory::SetFactoryForTesting(
        fake_cryptauth_enrollment_manager_factory_.get());
    // ---------- End: Only used for v1 Enrollment ----------

    // ---------- Begin: Only used for v1 DeviceSync ----------
    fake_cryptauth_device_manager_factory_ =
        std::make_unique<FakeCryptAuthDeviceManagerFactory>(
            simple_test_clock_.get(), fake_cryptauth_gcm_manager_factory_.get(),
            test_pref_service_.get());
    CryptAuthDeviceManagerImpl::Factory::SetFactoryForTesting(
        fake_cryptauth_device_manager_factory_.get());

    fake_software_feature_manager_factory_ =
        std::make_unique<FakeSoftwareFeatureManagerFactory>();
    SoftwareFeatureManagerImpl::Factory::SetFactoryForTesting(
        fake_software_feature_manager_factory_.get());
    // ---------- Begin: Only used for v1 DeviceSync ----------

    // ---------- Begin: Only used for v2 DeviceSync ----------
    fake_cryptauth_device_registry_factory_ =
        std::make_unique<FakeCryptAuthDeviceRegistryFactory>(
            test_pref_service_.get());
    CryptAuthDeviceRegistryImpl::Factory::SetFactoryForTesting(
        fake_cryptauth_device_registry_factory_.get());

    fake_cryptauth_v2_device_manager_factory_ =
        std::make_unique<FakeCryptAuthV2DeviceManagerFactory>(
            cryptauthv2::GetClientAppMetadataForTest(),
            fake_cryptauth_device_registry_factory_.get(),
            fake_cryptauth_key_registry_factory_.get(),
            fake_cryptauth_gcm_manager_factory_.get(),
            fake_cryptauth_scheduler_factory_.get(), test_pref_service_.get());
    CryptAuthV2DeviceManagerImpl::Factory::SetFactoryForTesting(
        fake_cryptauth_v2_device_manager_factory_.get());

    fake_device_notifier_factory_ =
        std::make_unique<FakeCryptAuthDeviceNotifierFactory>();
    CryptAuthDeviceNotifierImpl::Factory::SetFactoryForTesting(
        fake_device_notifier_factory_.get());

    fake_feature_status_setter_factory_ =
        std::make_unique<FakeCryptAuthFeatureStatusSetterFactory>();
    CryptAuthFeatureStatusSetterImpl::Factory::SetFactoryForTesting(
        fake_feature_status_setter_factory_.get());
    // ---------- End: Only used for v2 DeviceSync ----------

    fake_remote_device_provider_factory_ =
        std::make_unique<FakeRemoteDeviceProviderFactory>(
            test_devices_, identity_test_environment_->identity_manager(),
            fake_cryptauth_device_manager_factory_.get(),
            fake_cryptauth_v2_device_manager_factory_.get(),
            fake_cryptauth_enrollment_manager_factory_.get(),
            fake_cryptauth_v2_enrollment_manager_factory_.get());
    RemoteDeviceProviderImpl::Factory::SetFactoryForTesting(
        fake_remote_device_provider_factory_.get());

    auto mock_timer = std::make_unique<base::MockOneShotTimer>();
    mock_timer_ = mock_timer.get();

    fake_device_sync_impl_factory_ =
        std::make_unique<FakeDeviceSyncImplFactory>(std::move(mock_timer),
                                                    simple_test_clock_.get());
    DeviceSyncImpl::Factory::SetCustomFactory(
        fake_device_sync_impl_factory_.get());

    fake_gcm_device_info_provider_ =
        std::make_unique<FakeGcmDeviceInfoProvider>(GetTestGcmDeviceInfo());
  }

  void TearDown() override {
    CryptAuthGCMManagerImpl::Factory::SetFactoryForTesting(nullptr);
    CryptAuthDeviceManagerImpl::Factory::SetFactoryForTesting(nullptr);
    CryptAuthKeyRegistryImpl::Factory::SetFactoryForTesting(nullptr);
    CryptAuthV2EnrollmentManagerImpl::Factory::SetFactoryForTesting(nullptr);
    CryptAuthEnrollmentManagerImpl::Factory::SetFactoryForTesting(nullptr);
    RemoteDeviceProviderImpl::Factory::SetFactoryForTesting(nullptr);
    SoftwareFeatureManagerImpl::Factory::SetFactoryForTesting(nullptr);
    DeviceSyncImpl::Factory::SetCustomFactory(nullptr);

    network_handler_test_helper_.reset();
  }

  // Creates and initializes |device_sync_|. Done here instead of in SetUp()
  // so we can control whether or not the primary account is available at
  // construction time.
  void InitializeDeviceSync(bool device_already_enrolled_in_cryptauth) {
    device_already_enrolled_in_cryptauth_ =
        device_already_enrolled_in_cryptauth;

    if (base::FeatureList::IsEnabled(features::kCryptAuthV2Enrollment)) {
      fake_cryptauth_v2_enrollment_manager_factory_
          ->set_device_already_enrolled_in_cryptauth(
              device_already_enrolled_in_cryptauth);
    } else {
      fake_cryptauth_enrollment_manager_factory_
          ->set_device_already_enrolled_in_cryptauth(
              device_already_enrolled_in_cryptauth);
    }

    auto shared_url_loader_factory =
        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
            base::BindOnce([]() -> network::mojom::URLLoaderFactory* {
              ADD_FAILURE() << "Did not expect this to actually be used";
              return nullptr;
            }));

    device_sync_ = DeviceSyncImpl::Factory::Create(
        identity_test_environment_->identity_manager(),
        &fake_instance_id_driver_, test_pref_service_.get(),
        fake_gcm_device_info_provider_.get(),
        fake_client_app_metadata_provider_.get(), shared_url_loader_factory,
        std::make_unique<base::OneShotTimer>(),
        base::BindRepeating(
            [](AttestationCertificatesSyncer::NotifyCallback notifyCallback,
               const std::string&) {}));

    fake_device_sync_observer_ = std::make_unique<FakeDeviceSyncObserver>();

    // Set |fake_device_sync_observer_|.
    CallAddObserver();
  }

  void MakePrimaryAccountAvailable() {
    identity_test_environment_->MakePrimaryAccountAvailable(
        kTestEmail, signin::ConsentLevel::kSignin);
  }

  void SetInitialRegistrationId(const std::string& registration_id) {
    fake_cryptauth_gcm_manager_factory_->SetInitialRegistrationId(
        registration_id);
  }

  void SucceedGcmRegistration() {
    FakeCryptAuthGCMManager* manager =
        fake_cryptauth_gcm_manager_factory_->instance();
    ASSERT_TRUE(manager);
    EXPECT_TRUE(manager->IsListening());
    EXPECT_TRUE(manager->GetRegistrationId().empty());
    EXPECT_TRUE(manager->registration_in_progress());
    manager->CompleteRegistration(kTestCryptAuthGCMRegistrationId);
    EXPECT_FALSE(manager->registration_in_progress());
  }

  void SucceedClientAppMetadataFetch() {
    if (!base::FeatureList::IsEnabled(features::kCryptAuthV2Enrollment)) {
      return;
    }

    ASSERT_EQ(1u,
              fake_client_app_metadata_provider_->metadata_requests().size());
    EXPECT_EQ(kTestCryptAuthGCMRegistrationId,
              fake_client_app_metadata_provider_->metadata_requests()
                  .back()
                  .gcm_registration_id);
    std::move(
        fake_client_app_metadata_provider_->metadata_requests().back().callback)
        .Run(cryptauthv2::GetClientAppMetadataForTest());
  }

  void FinishInitialization() {
    // CryptAuth classes are expected to be created and initialized.
    EXPECT_TRUE(fake_cryptauth_enrollment_manager()->has_started());
    // If the device was already enrolled in CryptAuth, initialization should
    // now be complete; otherwise, enrollment needs to finish before
    // the flow has finished up.
    VerifyInitializationStatus(
        device_already_enrolled_in_cryptauth_ /* expected_to_be_initialized */);
    if (!device_already_enrolled_in_cryptauth_)
      return;
    // Now that the service is initialized, RemoteDeviceProvider is expected to
    // load all relevant RemoteDevice objects.
    fake_remote_device_provider_factory_->instance()
        ->NotifyObserversDeviceListChanged();
  }

  void VerifyInitializationStatus(bool expected_to_be_initialized) {
    // CryptAuthDeviceManager::Start() and/or
    // CryptAuthV2DeviceManager::Start() are called as the last step of the
    // initialization flow.
    if (features::ShouldUseV1DeviceSync()) {
      EXPECT_EQ(
          expected_to_be_initialized,
          fake_cryptauth_device_manager_factory_->instance()->has_started());
    }
    if (features::ShouldUseV2DeviceSync()) {
      EXPECT_EQ(
          expected_to_be_initialized,
          fake_cryptauth_v2_device_manager_factory_->instance()->has_started());
    }
  }

  // Simulates an enrollment with success == |success|. If enrollment was not
  // yet in progress before this call, it is started before it is completed.
  void SimulateEnrollment(bool success) {
    FakeCryptAuthEnrollmentManager* enrollment_manager =
        fake_cryptauth_enrollment_manager();

    bool had_valid_enrollment_before_call =
        enrollment_manager->IsEnrollmentValid();

    if (!enrollment_manager->IsEnrollmentInProgress()) {
      enrollment_manager->ForceEnrollmentNow(
          cryptauth::InvocationReason::INVOCATION_REASON_MANUAL);
    }

    enrollment_manager->FinishActiveEnrollment(success);

    // If this was the first successful enrollment for this device,
    // RemoteDeviceProvider is expected to load all relevant RemoteDevice
    // objects.
    if (success && !had_valid_enrollment_before_call) {
      fake_remote_device_provider_factory_->instance()
          ->NotifyObserversDeviceListChanged();
    }
  }

  // Simulates a device sync with success == |success|. Optionally, if
  // |updated_devices| is provided, these devices will set on the
  // FakeRemoteDeviceProvider.
  void SimulateSync(bool success,
                    const multidevice::RemoteDeviceList& updated_devices =
                        multidevice::RemoteDeviceList()) {
    FakeCryptAuthDeviceManager* device_manager =
        fake_cryptauth_device_manager_factory_->instance();
    FakeCryptAuthV2DeviceManager* v2_device_manager =
        fake_cryptauth_v2_device_manager_factory_->instance();
    FakeRemoteDeviceProvider* remote_device_provider =
        fake_remote_device_provider_factory_->instance();

    if (features::ShouldUseV1DeviceSync()) {
      EXPECT_TRUE(device_manager->IsSyncInProgress());
      device_manager->FinishActiveSync(
          success ? CryptAuthDeviceManager::SyncResult::SUCCESS
                  : CryptAuthDeviceManager::SyncResult::FAILURE,
          updated_devices.empty()
              ? CryptAuthDeviceManager::DeviceChangeResult::UNCHANGED
              : CryptAuthDeviceManager::DeviceChangeResult::CHANGED);
    }

    if (features::ShouldUseV2DeviceSync()) {
      EXPECT_TRUE(v2_device_manager->IsDeviceSyncInProgress());
      v2_device_manager->FinishNextForcedDeviceSync(
          CryptAuthDeviceSyncResult(
              success ? CryptAuthDeviceSyncResult::ResultCode::kSuccess
                      : CryptAuthDeviceSyncResult::ResultCode::
                            kErrorSyncMetadataApiCallBadRequest,
              !updated_devices.empty(), std::nullopt /* client_directive */),
          base::Time::Now());
    }

    if (!updated_devices.empty()) {
      remote_device_provider->set_synced_remote_devices(updated_devices);
      remote_device_provider->NotifyObserversDeviceListChanged();
    }
  }

  void InitializeServiceSuccessfully() {
    // In most login scenarios the primary account is available immediately.
    MakePrimaryAccountAvailable();
    InitializeDeviceSync(true /* device_already_enrolled_in_cryptauth */);
    SucceedGcmRegistration();
    SucceedClientAppMetadataFetch();
    FinishInitialization();
    VerifyInitializationStatus(true /* expected_to_be_initialized */);

    base::RunLoop().RunUntilIdle();

    // Enrollment did not occur since the device was already in a valid state.
    EXPECT_EQ(0u, fake_device_sync_observer()->num_enrollment_events());

    // The initial set of synced devices was set.
    EXPECT_EQ(1u, fake_device_sync_observer()->num_sync_events());
  }

  const multidevice::RemoteDeviceList& test_devices() { return test_devices_; }

  const std::vector<cryptauth::ExternalDeviceInfo>& test_device_infos() {
    return test_device_infos_;
  }

  const std::vector<cryptauth::IneligibleDevice>& test_ineligible_devices() {
    return test_ineligible_devices_;
  }

  FakeDeviceSyncObserver* fake_device_sync_observer() {
    return fake_device_sync_observer_.get();
  }

  base::MockOneShotTimer* mock_timer() { return mock_timer_; }

  FakeClientAppMetadataProvider* fake_client_app_metadata_provider() {
    return fake_client_app_metadata_provider_.get();
  }

  FakeCryptAuthGCMManager* fake_cryptauth_gcm_manager() {
    return fake_cryptauth_gcm_manager_factory_->instance();
  }

  FakeCryptAuthEnrollmentManager* fake_cryptauth_enrollment_manager() {
    return base::FeatureList::IsEnabled(features::kCryptAuthV2Enrollment)
               ? fake_cryptauth_v2_enrollment_manager_factory_->instance()
               : fake_cryptauth_enrollment_manager_factory_->instance();
  }

  FakeCryptAuthDeviceManager* fake_cryptauth_device_manager() {
    return fake_cryptauth_device_manager_factory_->instance();
  }

  FakeCryptAuthV2DeviceManager* fake_cryptauth_v2_device_manager() {
    return fake_cryptauth_v2_device_manager_factory_->instance();
  }

  FakeSoftwareFeatureManager* fake_software_feature_manager() {
    return fake_software_feature_manager_factory_->instance();
  }

  FakeCryptAuthFeatureStatusSetter* fake_feature_status_setter() {
    if (fake_feature_status_setter_factory_->instances().empty())
      return nullptr;

    EXPECT_EQ(1u, fake_feature_status_setter_factory_->instances().size());
    EXPECT_EQ(cryptauthv2::GetClientAppMetadataForTest().instance_id(),
              fake_device_notifier_factory_->last_instance_id());
    EXPECT_EQ(cryptauthv2::GetClientAppMetadataForTest().instance_id_token(),
              fake_device_notifier_factory_->last_instance_id_token());
    return fake_feature_status_setter_factory_->instances()[0];
  }

  const std::vector<mojom::NetworkRequestResult>& set_feature_status_results() {
    return set_feature_status_results_;
  }

  FakeCryptAuthDeviceNotifier* fake_device_notifier() {
    if (fake_device_notifier_factory_->instances().empty())
      return nullptr;

    EXPECT_EQ(1u, fake_device_notifier_factory_->instances().size());
    EXPECT_EQ(cryptauthv2::GetClientAppMetadataForTest().instance_id(),
              fake_device_notifier_factory_->last_instance_id());
    EXPECT_EQ(cryptauthv2::GetClientAppMetadataForTest().instance_id_token(),
              fake_device_notifier_factory_->last_instance_id_token());
    return fake_device_notifier_factory_->instances()[0];
  }

  const std::vector<mojom::NetworkRequestResult>& notify_devices_results() {
    return notify_devices_results_;
  }

  std::unique_ptr<mojom::NetworkRequestResult>
  GetLastSetSoftwareFeatureStateResponseAndReset() {
    return std::move(last_set_software_feature_state_response_);
  }

  std::unique_ptr<std::pair<mojom::NetworkRequestResult,
                            mojom::FindEligibleDevicesResponsePtr>>
  GetLastFindEligibleDevicesResponseAndReset() {
    return std::move(last_find_eligible_devices_response_);
  }

  // Verifies that API functions return error codes before initialization has
  // completed. This function should not be invoked after initialization.
  void VerifyApiFunctionsFailBeforeInitialization() {
    // Force*Now() functions return false when they are not handled.
    EXPECT_FALSE(CallForceEnrollmentNow());
    EXPECT_FALSE(CallForceSyncNow());

    // GetSyncedDevices() returns a null list before initialization.
    EXPECT_FALSE(CallGetSyncedDevices());

    // GetLocalDeviceMetadata() returns a null RemoteDevice before
    // initialization.
    EXPECT_FALSE(CallGetLocalDeviceMetadata());

    // GetGroupPrivateKeyStatus() and GetBetterTogetherMetadataStatus() both
    // should return kStatusUnavailableBecauseDeviceSyncIsNotInitialized.
    EXPECT_EQ(GroupPrivateKeyStatus::
                  kStatusUnavailableBecauseDeviceSyncIsNotInitialized,
              CallGetGroupPrivateKeyStatus());
    EXPECT_EQ(BetterTogetherMetadataStatus::
                  kStatusUnavailableBecauseDeviceSyncIsNotInitialized,
              CallGetBetterTogetherMetadataStatus());

    if (features::ShouldUseV1DeviceSync()) {
      // SetSoftwareFeatureState() should return a struct with the special
      // kErrorNotInitialized error code.
      CallSetSoftwareFeatureState(
          test_devices()[0].public_key,
          multidevice::SoftwareFeature::kBetterTogetherHost, true /* enabled */,
          true /* is_exclusive */);
      auto last_set_response = GetLastSetSoftwareFeatureStateResponseAndReset();
      EXPECT_EQ(mojom::NetworkRequestResult::kServiceNotYetInitialized,
                *last_set_response);
    }

    if (features::ShouldUseV2DeviceSync()) {
      // SetFeatureStatus() should return a struct with the special
      // kErrorNotInitialized error code.
      CallSetFeatureStatus(test_devices()[0].instance_id,
                           multidevice::SoftwareFeature::kBetterTogetherHost,
                           FeatureStatusChange::kEnableExclusively);
      EXPECT_EQ(1u, set_feature_status_results_.size());
      EXPECT_EQ(mojom::NetworkRequestResult::kServiceNotYetInitialized,
                set_feature_status_results_[0]);
    }

    if (features::ShouldUseV1DeviceSync()) {
      // FindEligibleDevices() should return a struct with the special
      // kErrorNotInitialized error code.
      CallFindEligibleDevices(
          multidevice::SoftwareFeature::kBetterTogetherHost);
      auto last_find_response = GetLastFindEligibleDevicesResponseAndReset();
      EXPECT_EQ(mojom::NetworkRequestResult::kServiceNotYetInitialized,
                last_find_response->first);
      EXPECT_FALSE(last_find_response->second /* response */);
    }

    if (features::ShouldUseV2DeviceSync()) {
      // NotifyDevices() should return a struct with the special
      // kErrorNotInitialized error code.
      CallNotifyDevices(
          {test_devices()[0].instance_id, test_devices()[1].instance_id},
          cryptauthv2::TargetService::DEVICE_SYNC,
          multidevice::SoftwareFeature::kBetterTogetherHost);
      EXPECT_EQ(1u, notify_devices_results_.size());
      EXPECT_EQ(mojom::NetworkRequestResult::kServiceNotYetInitialized,
                notify_devices_results_[0]);
    }

    // GetDebugInfo() returns a null DebugInfo before initialization.
    EXPECT_FALSE(CallGetDebugInfo());
  }

  void CallAddObserver() {
    base::RunLoop run_loop;
    device_sync_->AddObserver(
        fake_device_sync_observer_->GenerateRemote(),
        base::BindOnce(&DeviceSyncServiceTest::OnAddObserverCompleted,
                       base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();
  }

  bool CallForceEnrollmentNow() {
    base::RunLoop run_loop;
    device_sync_->ForceEnrollmentNow(
        base::BindOnce(&DeviceSyncServiceTest::OnForceEnrollmentNowCompleted,
                       base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();

    if (fake_cryptauth_enrollment_manager()) {
      EXPECT_EQ(last_force_enrollment_now_result_,
                fake_cryptauth_enrollment_manager()->IsEnrollmentInProgress());
    }

    return last_force_enrollment_now_result_;
  }

  bool CallForceSyncNow() {
    FakeCryptAuthDeviceManager* device_manager =
        fake_cryptauth_device_manager_factory_->instance();
    FakeCryptAuthV2DeviceManager* v2_device_manager =
        fake_cryptauth_v2_device_manager_factory_->instance();

    size_t expected_num_v2_force_device_sync_now_calls = 0;
    if (v2_device_manager) {
      expected_num_v2_force_device_sync_now_calls =
          v2_device_manager->force_device_sync_now_requests().size();
    }

    base::RunLoop run_loop;
    device_sync_->ForceSyncNow(
        base::BindOnce(&DeviceSyncServiceTest::OnForceSyncNowCompleted,
                       base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();

    if (device_manager) {
      EXPECT_EQ(last_force_sync_now_result_,
                device_manager->IsSyncInProgress());
    }

    if (v2_device_manager) {
      if (last_force_sync_now_result_)
        ++expected_num_v2_force_device_sync_now_calls;

      EXPECT_EQ(expected_num_v2_force_device_sync_now_calls,
                v2_device_manager->force_device_sync_now_requests().size());
    }

    return last_force_sync_now_result_;
  }

  GroupPrivateKeyStatus CallGetGroupPrivateKeyStatus() {
    base::RunLoop run_loop;
    device_sync_->GetGroupPrivateKeyStatus(base::BindOnce(
        &DeviceSyncServiceTest::OnGetGroupPrivateKeyStatusCompleted,
        base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();
    return last_group_private_key_status_result_;
  }

  BetterTogetherMetadataStatus CallGetBetterTogetherMetadataStatus() {
    base::RunLoop run_loop;
    device_sync_->GetBetterTogetherMetadataStatus(base::BindOnce(
        &DeviceSyncServiceTest::OnGetBetterTogetherMetadataStatusCompleted,
        base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();
    return last_better_together_metadata_status_result_;
  }

  const std::optional<multidevice::RemoteDevice>& CallGetLocalDeviceMetadata() {
    base::RunLoop run_loop;
    device_sync_->GetLocalDeviceMetadata(base::BindOnce(
        &DeviceSyncServiceTest::OnGetLocalDeviceMetadataCompleted,
        base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();
    return last_local_device_metadata_result_;
  }

  const std::optional<multidevice::RemoteDeviceList>& CallGetSyncedDevices() {
    base::RunLoop run_loop;
    device_sync_->GetSyncedDevices(
        base::BindOnce(&DeviceSyncServiceTest::OnGetSyncedDevicesCompleted,
                       base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();
    return last_synced_devices_result_;
  }

  void CallSetSoftwareFeatureState(
      const std::string& public_key,
      multidevice::SoftwareFeature software_feature,
      bool enabled,
      bool is_exclusive) {
    base::RunLoop run_loop;
    FakeSoftwareFeatureManager* manager = fake_software_feature_manager();

    // If the manager has not yet been created, the service has not been
    // initialized. SetSoftwareFeatureState() is expected to respond
    // synchronously with an error.
    if (!manager) {
      device_sync_->SetSoftwareFeatureState(
          public_key, software_feature, enabled, is_exclusive,
          base::BindOnce(&DeviceSyncServiceTest::
                             OnSetSoftwareFeatureStateCompletedSynchronously,
                         base::Unretained(this), run_loop.QuitClosure()));
      run_loop.Run();
      return;
    }

    // If the manager has been created, the service responds asynchronously.
    FakeSoftwareFeatureManagerDelegate delegate(run_loop.QuitClosure());
    fake_software_feature_manager_factory_->instance()->set_delegate(&delegate);

    device_sync_->SetSoftwareFeatureState(
        public_key, software_feature, enabled, is_exclusive,
        base::BindOnce(
            &DeviceSyncServiceTest::OnSetSoftwareFeatureStateCompleted,
            base::Unretained(this)));
    run_loop.Run();

    fake_software_feature_manager_factory_->instance()->set_delegate(nullptr);
  }

  void CallSetFeatureStatus(const std::string& device_instance_id,
                            multidevice::SoftwareFeature software_feature,
                            FeatureStatusChange status_change) {
    if (features::ShouldUseV1DeviceSync()) {
      CallSetFeatureStatusV1andV2DeviceSync(device_instance_id,
                                            software_feature, status_change);
      return;
    }

    CallSetFeatureStatusV2DeviceSyncOnly(device_instance_id, software_feature,
                                         status_change);
  }

  void CallFindEligibleDevices(multidevice::SoftwareFeature software_feature) {
    base::RunLoop run_loop;
    FakeSoftwareFeatureManager* manager = fake_software_feature_manager();

    // If the manager has not yet been created, the service has not been
    // initialized. FindEligibleDevices() is expected to respond synchronously
    // with an error.
    if (!manager) {
      device_sync_->FindEligibleDevices(
          software_feature,
          base::BindOnce(&DeviceSyncServiceTest::
                             OnFindEligibleDevicesCompletedSynchronously,
                         base::Unretained(this), run_loop.QuitClosure()));
      run_loop.Run();
      return;
    }

    // If the manager has been created, the service responds asynchronously.
    FakeSoftwareFeatureManagerDelegate delegate(run_loop.QuitClosure());
    fake_software_feature_manager_factory_->instance()->set_delegate(&delegate);

    device_sync_->FindEligibleDevices(
        software_feature,
        base::BindOnce(&DeviceSyncServiceTest::OnFindEligibleDevicesCompleted,
                       base::Unretained(this)));
    run_loop.Run();

    fake_software_feature_manager_factory_->instance()->set_delegate(nullptr);
  }

  void CallNotifyDevices(const std::vector<std::string>& device_instance_ids,
                         cryptauthv2::TargetService target_service,
                         multidevice::SoftwareFeature feature) {
    base::RunLoop run_loop;

    // If the device notifier has not yet been created, the service has not been
    // initialized. NotifyDevices() is expected to respond synchronously with an
    // error.
    if (!fake_device_notifier()) {
      device_sync_->NotifyDevices(
          device_instance_ids, target_service, feature,
          base::BindOnce(
              &DeviceSyncServiceTest::OnNotifyDevicesCompletedSynchronously,
              base::Unretained(this), run_loop.QuitClosure()));
      run_loop.Run();
      return;
    }

    FakeCryptAuthDeviceNotifierDelegate delegate(run_loop.QuitClosure());
    fake_device_notifier()->set_delegate(&delegate);
    device_sync_->NotifyDevices(
        device_instance_ids, target_service, feature,
        base::BindOnce(&DeviceSyncServiceTest::OnNotifyDevicesCompleted,
                       base::Unretained(this)));
    run_loop.Run();
    fake_device_notifier()->set_delegate(nullptr);
  }

  const std::optional<mojom::DebugInfo>& CallGetDebugInfo() {
    base::RunLoop run_loop;
    device_sync_->GetDebugInfo(
        base::BindOnce(&DeviceSyncServiceTest::OnGetDebugInfoCompleted,
                       base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();
    return last_debug_info_result_;
  }

  const base::HistogramTester& histogram_tester() const {
    return histogram_tester_;
  }

 private:
  void CallSetFeatureStatusV1andV2DeviceSync(
      const std::string& device_instance_id,
      multidevice::SoftwareFeature software_feature,
      FeatureStatusChange status_change) {
    base::RunLoop run_loop;
    FakeSoftwareFeatureManager* manager = fake_software_feature_manager();

    // If the manager has not yet been created, the service has not been
    // initialized. SetFeatureStatus() is expected to respond synchronously with
    // an error.
    if (!manager) {
      device_sync_->SetFeatureStatus(
          device_instance_id, software_feature, status_change,
          base::BindOnce(
              &DeviceSyncServiceTest::OnSetFeatureStatusCompletedSynchronously,
              base::Unretained(this), run_loop.QuitClosure()));
      run_loop.Run();
      return;
    }

    // If the manager has been created, the service responds asynchronously.
    FakeSoftwareFeatureManagerDelegate delegate(run_loop.QuitClosure());
    fake_software_feature_manager_factory_->instance()->set_delegate(&delegate);

    device_sync_->SetFeatureStatus(
        device_instance_id, software_feature, status_change,
        base::BindOnce(&DeviceSyncServiceTest::OnSetFeatureStatusCompleted,
                       base::Unretained(this)));
    run_loop.Run();

    fake_software_feature_manager_factory_->instance()->set_delegate(nullptr);
  }

  void CallSetFeatureStatusV2DeviceSyncOnly(
      const std::string& device_instance_id,
      multidevice::SoftwareFeature software_feature,
      FeatureStatusChange status_change) {
    base::RunLoop run_loop;

    // If the feature setter has not yet been created, the service has not been
    // initialized. SetFeatureStatus() is expected to respond synchronously with
    // an error.
    if (!fake_feature_status_setter()) {
      device_sync_->SetFeatureStatus(
          device_instance_id, software_feature, status_change,
          base::BindOnce(
              &DeviceSyncServiceTest::OnSetFeatureStatusCompletedSynchronously,
              base::Unretained(this), run_loop.QuitClosure()));
      run_loop.Run();
      return;
    }

    FakeCryptAuthFeatureStatusSetterDelegate delegate(run_loop.QuitClosure());
    fake_feature_status_setter()->set_delegate(&delegate);
    device_sync_->SetFeatureStatus(
        device_instance_id, software_feature, status_change,
        base::BindOnce(&DeviceSyncServiceTest::OnSetFeatureStatusCompleted,
                       base::Unretained(this)));
    run_loop.Run();

    fake_feature_status_setter()->set_delegate(nullptr);
  }

  void OnAddObserverCompleted(base::OnceClosure quit_closure) {
    std::move(quit_closure).Run();
  }

  void OnForceEnrollmentNowCompleted(base::OnceClosure quit_closure,
                                     bool success) {
    last_force_enrollment_now_result_ = success;
    std::move(quit_closure).Run();
  }

  void OnForceSyncNowCompleted(base::OnceClosure quit_closure, bool success) {
    last_force_sync_now_result_ = success;
    std::move(quit_closure).Run();
  }

  void OnGetGroupPrivateKeyStatusCompleted(base::OnceClosure quit_closure,
                                           GroupPrivateKeyStatus status) {
    last_group_private_key_status_result_ = status;
    std::move(quit_closure).Run();
  }

  void OnGetBetterTogetherMetadataStatusCompleted(
      base::OnceClosure quit_closure,
      BetterTogetherMetadataStatus status) {
    last_better_together_metadata_status_result_ = status;
    std::move(quit_closure).Run();
  }

  void OnGetLocalDeviceMetadataCompleted(
      base::OnceClosure quit_closure,
      const std::optional<multidevice::RemoteDevice>& local_device_metadata) {
    last_local_device_metadata_result_ = local_device_metadata;
    std::move(quit_closure).Run();
  }

  void OnGetSyncedDevicesCompleted(
      base::OnceClosure quit_closure,
      const std::optional<multidevice::RemoteDeviceList>& synced_devices) {
    last_synced_devices_result_ = synced_devices;
    std::move(quit_closure).Run();
  }

  void OnSetSoftwareFeatureStateCompleted(
      mojom::NetworkRequestResult result_code) {
    EXPECT_FALSE(last_set_software_feature_state_response_);
    last_set_software_feature_state_response_ =
        std::make_unique<mojom::NetworkRequestResult>(result_code);
  }

  void OnSetSoftwareFeatureStateCompletedSynchronously(
      base::OnceClosure quit_closure,
      mojom::NetworkRequestResult result_code) {
    OnSetSoftwareFeatureStateCompleted(result_code);
    std::move(quit_closure).Run();
  }

  void OnSetFeatureStatusCompleted(mojom::NetworkRequestResult result_code) {
    set_feature_status_results_.push_back(result_code);
  }

  void OnSetFeatureStatusCompletedSynchronously(
      base::OnceClosure quit_closure,
      mojom::NetworkRequestResult result_code) {
    OnSetFeatureStatusCompleted(result_code);
    std::move(quit_closure).Run();
  }

  void OnFindEligibleDevicesCompleted(
      mojom::NetworkRequestResult result_code,
      mojom::FindEligibleDevicesResponsePtr response) {
    EXPECT_FALSE(last_find_eligible_devices_response_);
    last_find_eligible_devices_response_ =
        std::make_unique<std::pair<mojom::NetworkRequestResult,
                                   mojom::FindEligibleDevicesResponsePtr>>(
            result_code, std::move(response));
  }

  void OnNotifyDevicesCompleted(mojom::NetworkRequestResult result_code) {
    notify_devices_results_.push_back(result_code);
  }

  void OnNotifyDevicesCompletedSynchronously(
      base::OnceClosure quit_closure,
      mojom::NetworkRequestResult result_code) {
    OnNotifyDevicesCompleted(result_code);
    std::move(quit_closure).Run();
  }

  void OnFindEligibleDevicesCompletedSynchronously(
      base::OnceClosure quit_closure,
      mojom::NetworkRequestResult result_code,
      mojom::FindEligibleDevicesResponsePtr response) {
    OnFindEligibleDevicesCompleted(result_code, std::move(response));
    std::move(quit_closure).Run();
  }

  void OnGetDebugInfoCompleted(base::OnceClosure quit_closure,
                               mojom::DebugInfoPtr debug_info) {
    EXPECT_FALSE(last_debug_info_result_);
    if (debug_info)
      last_debug_info_result_ = *debug_info;
    else
      last_debug_info_result_.reset();
    std::move(quit_closure).Run();
  }

  const base::test::TaskEnvironment task_environment_;
  const multidevice::RemoteDeviceList test_devices_;
  const std::vector<cryptauth::ExternalDeviceInfo> test_device_infos_;
  const std::vector<cryptauth::IneligibleDevice> test_ineligible_devices_;

  std::unique_ptr<NetworkHandlerTestHelper> network_handler_test_helper_;
  std::unique_ptr<TestingPrefServiceSimple> test_pref_service_;
  raw_ptr<base::MockOneShotTimer, DanglingUntriaged> mock_timer_;
  std::unique_ptr<base::SimpleTestClock> simple_test_clock_;
  std::unique_ptr<FakeDeviceSyncImplFactory> fake_device_sync_impl_factory_;
  std::unique_ptr<FakeCryptAuthGCMManagerFactory>
      fake_cryptauth_gcm_manager_factory_;
  std::unique_ptr<FakeCryptAuthEnrollmentManagerFactory>
      fake_cryptauth_enrollment_manager_factory_;
  std::unique_ptr<FakeClientAppMetadataProvider>
      fake_client_app_metadata_provider_;
  std::unique_ptr<FakeCryptAuthKeyRegistryFactory>
      fake_cryptauth_key_registry_factory_;
  std::unique_ptr<FakeCryptAuthSchedulerFactory>
      fake_cryptauth_scheduler_factory_;
  std::unique_ptr<FakeCryptAuthV2EnrollmentManagerFactory>
      fake_cryptauth_v2_enrollment_manager_factory_;
  std::unique_ptr<FakeCryptAuthDeviceNotifierFactory>
      fake_device_notifier_factory_;
  std::unique_ptr<FakeCryptAuthFeatureStatusSetterFactory>
      fake_feature_status_setter_factory_;
  std::unique_ptr<FakeCryptAuthDeviceManagerFactory>
      fake_cryptauth_device_manager_factory_;
  std::unique_ptr<FakeSoftwareFeatureManagerFactory>
      fake_software_feature_manager_factory_;
  std::unique_ptr<FakeCryptAuthDeviceRegistryFactory>
      fake_cryptauth_device_registry_factory_;
  std::unique_ptr<FakeCryptAuthV2DeviceManagerFactory>
      fake_cryptauth_v2_device_manager_factory_;
  std::unique_ptr<FakeRemoteDeviceProviderFactory>
      fake_remote_device_provider_factory_;

  std::unique_ptr<signin::IdentityTestEnvironment> identity_test_environment_;
  testing::NiceMock<MockInstanceIDDriver> fake_instance_id_driver_;
  std::unique_ptr<FakeGcmDeviceInfoProvider> fake_gcm_device_info_provider_;

  bool device_already_enrolled_in_cryptauth_;
  bool last_force_enrollment_now_result_;
  bool last_force_sync_now_result_;
  GroupPrivateKeyStatus last_group_private_key_status_result_;
  BetterTogetherMetadataStatus last_better_together_metadata_status_result_;
  std::optional<multidevice::RemoteDeviceList> last_synced_devices_result_;
  std::optional<multidevice::RemoteDevice> last_local_device_metadata_result_;
  std::unique_ptr<mojom::NetworkRequestResult>
      last_set_software_feature_state_response_;
  std::unique_ptr<std::pair<mojom::NetworkRequestResult,
                            mojom::FindEligibleDevicesResponsePtr>>
      last_find_eligible_devices_response_;
  std::optional<mojom::DebugInfo> last_debug_info_result_;
  std::vector<mojom::NetworkRequestResult> set_feature_status_results_;
  std::vector<mojom::NetworkRequestResult> notify_devices_results_;

  std::unique_ptr<FakeDeviceSyncObserver> fake_device_sync_observer_;
  std::unique_ptr<DeviceSyncBase> device_sync_;

  base::HistogramTester histogram_tester_;

  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_P(DeviceSyncServiceTest, PrimaryAccountAvailableLater) {
  InitializeDeviceSync(true /* device_already_enrolled_in_cryptauth */);

  // API functions should fail if the primary account isn't ready yet.
  VerifyApiFunctionsFailBeforeInitialization();

  // No observer callbacks should have been invoked.
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(0u, fake_device_sync_observer()->num_enrollment_events());
  EXPECT_EQ(0u, fake_device_sync_observer()->num_sync_events());

  MakePrimaryAccountAvailable();
  SucceedGcmRegistration();
  SucceedClientAppMetadataFetch();
  FinishInitialization();
  VerifyInitializationStatus(true /* expected_to_be_initialized */);

  // API functions should be operational and synced devices should be available.
  EXPECT_TRUE(CallForceEnrollmentNow());
  EXPECT_TRUE(CallForceSyncNow());
  EXPECT_EQ(test_devices(), CallGetSyncedDevices());
}

TEST_P(DeviceSyncServiceTest, GcmRegistration) {
  MakePrimaryAccountAvailable();
  InitializeDeviceSync(true /* device_already_enrolled_in_cryptauth */);

  // Registers with GCM, failing twice then succeeding.
  VerifyApiFunctionsFailBeforeInitialization();
  ASSERT_TRUE(fake_cryptauth_gcm_manager());
  EXPECT_TRUE(fake_cryptauth_gcm_manager()->IsListening());
  for (size_t num_retries = 0; num_retries < 2; ++num_retries) {
    EXPECT_TRUE(fake_cryptauth_gcm_manager()->GetRegistrationId().empty());
    EXPECT_TRUE(fake_cryptauth_gcm_manager()->registration_in_progress());
    fake_cryptauth_gcm_manager()->CompleteRegistration(
        std::string() /* registration_id */);
    EXPECT_TRUE(mock_timer()->IsRunning());
    mock_timer()->Fire();
  }
  fake_cryptauth_gcm_manager()->CompleteRegistration(
      kTestCryptAuthGCMRegistrationId);
  EXPECT_FALSE(fake_cryptauth_gcm_manager()->registration_in_progress());

  SucceedClientAppMetadataFetch();
  FinishInitialization();
}

TEST_P(DeviceSyncServiceTest, GcmRegistration_SkipIfAlreadyRegistered) {
  // Assume GCM registration already happened. Then, no need to register again.
  SetInitialRegistrationId(kTestCryptAuthGCMRegistrationId);
  MakePrimaryAccountAvailable();
  InitializeDeviceSync(true /* device_already_enrolled_in_cryptauth */);
  SucceedClientAppMetadataFetch();
  FinishInitialization();
}

TEST_P(DeviceSyncServiceTest, GcmRegistration_RegisterAgainIfDeprecated) {
  // Assume GCM registration already happened, but with a deprecated
  // registration id. This time, we should fail.
  SetInitialRegistrationId(kTestDeprecatedCryptAuthGCMRegistrationId);
  MakePrimaryAccountAvailable();
  InitializeDeviceSync(true /* device_already_enrolled_in_cryptauth */);

  // Unlike in the previous case, GCM Registration should have been started
  // since our existing registration id is deprecated.
  ASSERT_TRUE(fake_cryptauth_gcm_manager()->registration_in_progress());
  fake_cryptauth_gcm_manager()->CompleteRegistration(
      kTestCryptAuthGCMRegistrationId);
  EXPECT_FALSE(fake_cryptauth_gcm_manager()->registration_in_progress());

  SucceedClientAppMetadataFetch();
  FinishInitialization();
}

TEST_P(DeviceSyncServiceTest, ClientAppMetadataFetch) {
  if (!base::FeatureList::IsEnabled(features::kCryptAuthV2Enrollment)) {
    return;
  }

  MakePrimaryAccountAvailable();
  InitializeDeviceSync(true /* device_already_enrolled_in_cryptauth */);
  SucceedGcmRegistration();

  // Fetch ClientAppMetadata by failing twice, timing out once, then succeeding.
  VerifyApiFunctionsFailBeforeInitialization();
  EXPECT_TRUE(mock_timer()->IsRunning());  // Timeout timer is running.
  for (size_t attempt = 1; attempt <= 4; ++attempt) {
    ASSERT_EQ(attempt,
              fake_client_app_metadata_provider()->metadata_requests().size());
    EXPECT_EQ(kTestCryptAuthGCMRegistrationId,
              fake_client_app_metadata_provider()
                  ->metadata_requests()[attempt - 1]
                  .gcm_registration_id);
    if (attempt <= 2) {
      // Fail and retry.
      std::move(fake_client_app_metadata_provider()
                    ->metadata_requests()[attempt - 1]
                    .callback)
          .Run(std::nullopt /* client_app_metadata */);
      EXPECT_TRUE(mock_timer()->IsRunning());  // Retry timer is running.
      mock_timer()->Fire();
    } else if (attempt == 3) {
      // Time out and retry.
      EXPECT_TRUE(mock_timer()->IsRunning());  // Timeout timer is running.
      mock_timer()->Fire();
      EXPECT_TRUE(mock_timer()->IsRunning());  // Retry timer is running.
      mock_timer()->Fire();
    } else {
      // Succeed.
      std::move(fake_client_app_metadata_provider()
                    ->metadata_requests()[attempt - 1]
                    .callback)
          .Run(cryptauthv2::GetClientAppMetadataForTest());
    }
  }

  FinishInitialization();
}

TEST_P(DeviceSyncServiceTest,
       DeviceNotAlreadyEnrolledInCryptAuth_FailsEnrollment) {
  MakePrimaryAccountAvailable();
  InitializeDeviceSync(false /* device_already_enrolled_in_cryptauth */);
  SucceedGcmRegistration();
  SucceedClientAppMetadataFetch();
  FinishInitialization();

  // Simulate enrollment failing.
  SimulateEnrollment(false /* success */);
  VerifyInitializationStatus(false /* success */);

  // Fail again; initialization still should not complete.
  SimulateEnrollment(false /* success */);
  VerifyInitializationStatus(false /* expected_to_be_initialized */);

  // Other API functions should still fail since initialization never completed.
  VerifyApiFunctionsFailBeforeInitialization();

  // No observer callbacks should have been invoked.
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(0u, fake_device_sync_observer()->num_enrollment_events());
  EXPECT_EQ(0u, fake_device_sync_observer()->num_sync_events());
}

TEST_P(DeviceSyncServiceTest,
       DeviceNotAlreadyEnrolledInCryptAuth_FailsEnrollment_ThenSucceeds) {
  MakePrimaryAccountAvailable();
  InitializeDeviceSync(false /* device_already_enrolled_in_cryptauth */);
  SucceedGcmRegistration();
  SucceedClientAppMetadataFetch();
  FinishInitialization();

  // Initialization has not yet completed, so no devices should be available.
  EXPECT_FALSE(CallGetSyncedDevices());

  // Simulate enrollment failing.
  SimulateEnrollment(false /* success */);
  VerifyInitializationStatus(false /* success */);

  // Simulate enrollment succeeding; this should result in a fully-initialized
  // service.
  SimulateEnrollment(true /* success */);
  VerifyInitializationStatus(true /* expected_to_be_initialized */);

  // Enrollment occurred successfully, and the initial set of synced devices was
  // set.
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1u, fake_device_sync_observer()->num_enrollment_events());
  EXPECT_EQ(1u, fake_device_sync_observer()->num_sync_events());

  // Now that the service is initialized, API functions should be operation and
  // synced devices should be available.
  EXPECT_TRUE(CallForceEnrollmentNow());
  EXPECT_TRUE(CallForceSyncNow());
  EXPECT_EQ(test_devices(), CallGetSyncedDevices());
}

TEST_P(DeviceSyncServiceTest,
       DeviceAlreadyEnrolledInCryptAuth_InitializationFlow) {
  InitializeServiceSuccessfully();

  // Now that the service is initialized, API functions should be operation and
  // synced devices should be available.
  EXPECT_TRUE(CallForceEnrollmentNow());
  EXPECT_TRUE(CallForceSyncNow());
  EXPECT_EQ(test_devices(), CallGetSyncedDevices());
}

TEST_P(DeviceSyncServiceTest, EnrollAgainAfterInitialization) {
  InitializeServiceSuccessfully();

  // Force an enrollment.
  EXPECT_TRUE(CallForceEnrollmentNow());

  // Simulate that enrollment failing.
  SimulateEnrollment(false /* success */);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(0u, fake_device_sync_observer()->num_enrollment_events());

  // Force an enrollment again.
  EXPECT_TRUE(CallForceEnrollmentNow());

  // This time, simulate the enrollment succeeding.
  SimulateEnrollment(true /* success */);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1u, fake_device_sync_observer()->num_enrollment_events());
}

TEST_P(DeviceSyncServiceTest, GetGroupPrivateKeyStatus) {
  if (!features::ShouldUseV2DeviceSync())
    return;

  InitializeServiceSuccessfully();
  EXPECT_EQ(GroupPrivateKeyStatus::kWaitingForGroupPrivateKey,
            CallGetGroupPrivateKeyStatus());
}

TEST_P(DeviceSyncServiceTest, GetBetterTogetherMetadataStatus) {
  if (!features::ShouldUseV2DeviceSync())
    return;

  InitializeServiceSuccessfully();
  EXPECT_EQ(BetterTogetherMetadataStatus::kWaitingToProcessDeviceMetadata,
            CallGetBetterTogetherMetadataStatus());
}

TEST_P(DeviceSyncServiceTest, GetLocalDeviceMetadata) {
  InitializeServiceSuccessfully();

  const auto& result = CallGetLocalDeviceMetadata();
  EXPECT_TRUE(result);
  EXPECT_EQ(kLocalDevicePublicKey, result->public_key);
  // Note: In GenerateTestRemoteDevices(), the 0th test device is arbitrarily
  // chosen as the local device.
  EXPECT_EQ(test_devices()[0], *result);
}

TEST_P(DeviceSyncServiceTest, SyncedDeviceUpdates) {
  InitializeServiceSuccessfully();
  EXPECT_EQ(1u, fake_device_sync_observer()->num_sync_events());

  // Force a device sync.
  EXPECT_TRUE(CallForceSyncNow());

  // Simulate failed sync.
  SimulateSync(false /* success */);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1u, fake_device_sync_observer()->num_sync_events());

  // Force a sync again.
  EXPECT_TRUE(CallForceSyncNow());

  // Simulate successful sync which does not change the synced device list.
  SimulateSync(true /* success */);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1u, fake_device_sync_observer()->num_sync_events());

  // Force a sync again.
  EXPECT_TRUE(CallForceSyncNow());

  // Create a new list which is the same as the initial test devices except that
  // the first device is removed.
  multidevice::RemoteDeviceList updated_device_list(test_devices().begin() + 1,
                                                    test_devices().end());
  EXPECT_EQ(kNumTestDevices - 1, updated_device_list.size());

  // Simulate successful sync which does change the synced device list.
  SimulateSync(true /* success */, updated_device_list);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(2u, fake_device_sync_observer()->num_sync_events());

  // The updated list should be available via GetSyncedDevices().
  EXPECT_EQ(updated_device_list, CallGetSyncedDevices());
}

TEST_P(DeviceSyncServiceTest, SetSoftwareFeatureState_Success) {
  if (!features::ShouldUseV1DeviceSync())
    return;

  InitializeServiceSuccessfully();

  const auto& set_software_calls =
      fake_software_feature_manager()->set_software_feature_state_calls();
  EXPECT_EQ(0u, set_software_calls.size());

  // Set the kBetterTogetherHost field to "supported".
  multidevice::RemoteDevice device_for_test = test_devices()[0];
  device_for_test
      .software_features[multidevice::SoftwareFeature::kBetterTogetherHost] =
      multidevice::SoftwareFeatureState::kSupported;
  EXPECT_TRUE(CallForceSyncNow());
  SimulateSync(true /* success */, {device_for_test});

  // Enable kBetterTogetherHost for the device.
  CallSetSoftwareFeatureState(device_for_test.public_key,
                              multidevice::SoftwareFeature::kBetterTogetherHost,
                              true /* enabled */, true /* is_exclusive */);
  EXPECT_EQ(1u, set_software_calls.size());
  EXPECT_EQ(multidevice::SoftwareFeature::kBetterTogetherHost,
            set_software_calls[0]->software_feature);
  EXPECT_TRUE(set_software_calls[0]->enabled);
  EXPECT_TRUE(set_software_calls[0]->is_exclusive);

  // The callback has not yet been invoked.
  EXPECT_FALSE(GetLastSetSoftwareFeatureStateResponseAndReset());

  // Now, invoke the success callback.
  std::move(set_software_calls[0]->success_callback).Run();

  // The callback still has not yet been invoked, since a device sync has not
  // confirmed the feature state change yet.
  EXPECT_FALSE(GetLastSetSoftwareFeatureStateResponseAndReset());

  // Simulate a sync which includes the device with the correct "enabled" state.
  device_for_test
      .software_features[multidevice::SoftwareFeature::kBetterTogetherHost] =
      multidevice::SoftwareFeatureState::kEnabled;
  base::RunLoop().RunUntilIdle();
  SimulateSync(true /* success */, {device_for_test});
  base::RunLoop().RunUntilIdle();

  auto last_response = GetLastSetSoftwareFeatureStateResponseAndReset();
  EXPECT_TRUE(last_response);
  EXPECT_EQ(mojom::NetworkRequestResult::kSuccess, *last_response);

  histogram_tester().ExpectBucketCount<bool>(
      "MultiDevice.DeviceSyncService.SetSoftwareFeatureState.Result", false, 0);
  histogram_tester().ExpectBucketCount<bool>(
      "MultiDevice.DeviceSyncService.SetSoftwareFeatureState.Result", true, 1);
  histogram_tester().ExpectTotalCount(
      "MultiDevice.DeviceSyncService.SetSoftwareFeatureState.Result."
      "FailureReason",
      0);
}

TEST_P(DeviceSyncServiceTest,
       SetSoftwareFeatureState_RequestSucceedsButDoesNotTakeEffect) {
  if (!features::ShouldUseV1DeviceSync())
    return;

  InitializeServiceSuccessfully();

  const auto& set_software_calls =
      fake_software_feature_manager()->set_software_feature_state_calls();
  EXPECT_EQ(0u, set_software_calls.size());

  // Set the kBetterTogetherHost field to "supported".
  multidevice::RemoteDevice device_for_test = test_devices()[0];
  device_for_test
      .software_features[multidevice::SoftwareFeature::kBetterTogetherHost] =
      multidevice::SoftwareFeatureState::kSupported;
  EXPECT_TRUE(CallForceSyncNow());
  SimulateSync(true /* success */, {device_for_test});

  // Enable kBetterTogetherHost for the device.
  CallSetSoftwareFeatureState(device_for_test.public_key,
                              multidevice::SoftwareFeature::kBetterTogetherHost,
                              true /* enabled */, true /* is_exclusive */);
  EXPECT_EQ(1u, set_software_calls.size());
  EXPECT_EQ(multidevice::SoftwareFeature::kBetterTogetherHost,
            set_software_calls[0]->software_feature);
  EXPECT_TRUE(set_software_calls[0]->enabled);
  EXPECT_TRUE(set_software_calls[0]->is_exclusive);

  // The callback has not yet been invoked.
  EXPECT_FALSE(GetLastSetSoftwareFeatureStateResponseAndReset());

  // Fire the timer, simulating that the updated device metadata did not arrive
  // in time.
  mock_timer()->Fire();
  base::RunLoop().RunUntilIdle();

  auto last_response = GetLastSetSoftwareFeatureStateResponseAndReset();
  EXPECT_TRUE(last_response);
  EXPECT_EQ(mojom::NetworkRequestResult::kRequestSucceededButUnexpectedResult,
            *last_response);

  histogram_tester().ExpectBucketCount<bool>(
      "MultiDevice.DeviceSyncService.SetSoftwareFeatureState.Result", false, 1);
  histogram_tester().ExpectTotalCount(
      "MultiDevice.DeviceSyncService.SetSoftwareFeatureState.Result."
      "FailureReason",
      1);
  histogram_tester().ExpectBucketCount<bool>(
      "MultiDevice.DeviceSyncService.SetSoftwareFeatureState.Result", true, 0);
}

TEST_P(DeviceSyncServiceTest, SetSoftwareFeatureState_Error) {
  if (!features::ShouldUseV1DeviceSync())
    return;

  InitializeServiceSuccessfully();

  const auto& set_software_calls =
      fake_software_feature_manager()->set_software_feature_state_calls();
  EXPECT_EQ(0u, set_software_calls.size());

  // Set the kBetterTogetherHost field to "supported".
  multidevice::RemoteDevice device_for_test = test_devices()[0];
  device_for_test
      .software_features[multidevice::SoftwareFeature::kBetterTogetherHost] =
      multidevice::SoftwareFeatureState::kSupported;
  EXPECT_TRUE(CallForceSyncNow());
  SimulateSync(true /* success */, {device_for_test});

  // Enable kBetterTogetherHost for the device.
  CallSetSoftwareFeatureState(device_for_test.public_key,
                              multidevice::SoftwareFeature::kBetterTogetherHost,
                              true /* enabled */, true /* is_exclusive */);
  ASSERT_EQ(1u, set_software_calls.size());
  EXPECT_EQ(multidevice::SoftwareFeature::kBetterTogetherHost,
            set_software_calls[0]->software_feature);
  EXPECT_TRUE(set_software_calls[0]->enabled);
  EXPECT_TRUE(set_software_calls[0]->is_exclusive);

  // The callback has not yet been invoked.
  EXPECT_FALSE(GetLastSetSoftwareFeatureStateResponseAndReset());

  // Now, invoke the error callback.
  std::move(set_software_calls[0]->error_callback)
      .Run(NetworkRequestError::kOffline);
  base::RunLoop().RunUntilIdle();
  auto last_response = GetLastSetSoftwareFeatureStateResponseAndReset();
  EXPECT_TRUE(last_response);
  EXPECT_EQ(mojom::NetworkRequestResult::kOffline, *last_response);

  histogram_tester().ExpectBucketCount<bool>(
      "MultiDevice.DeviceSyncService.SetSoftwareFeatureState.Result", false, 1);
  histogram_tester().ExpectTotalCount(
      "MultiDevice.DeviceSyncService.SetSoftwareFeatureState.Result."
      "FailureReason",
      1);
  histogram_tester().ExpectBucketCount<bool>(
      "MultiDevice.DeviceSyncService.SetSoftwareFeatureState.Result", true, 0);
}

TEST_P(DeviceSyncServiceTest, SetFeatureStatus_Success) {
  if (!features::ShouldUseV2DeviceSync())
    return;

  InitializeServiceSuccessfully();

  if (features::ShouldUseV1DeviceSync()) {
    EXPECT_EQ(
        0u, fake_software_feature_manager()->set_feature_status_calls().size());
  } else {
    EXPECT_EQ(0u, fake_feature_status_setter()->requests().size());
  }

  multidevice::RemoteDevice device_for_test = test_devices()[0];

  // Exclusively enable kBetterTogetherHost for the device.
  CallSetFeatureStatus(device_for_test.instance_id,
                       multidevice::SoftwareFeature::kBetterTogetherHost,
                       FeatureStatusChange::kEnableExclusively);

  if (features::ShouldUseV1DeviceSync()) {
    EXPECT_EQ(
        1u, fake_software_feature_manager()->set_feature_status_calls().size());
    EXPECT_EQ(device_for_test.instance_id, fake_software_feature_manager()
                                               ->set_feature_status_calls()[0]
                                               ->device_id);
    EXPECT_EQ(multidevice::SoftwareFeature::kBetterTogetherHost,
              fake_software_feature_manager()
                  ->set_feature_status_calls()[0]
                  ->feature);
    EXPECT_EQ(FeatureStatusChange::kEnableExclusively,
              fake_software_feature_manager()
                  ->set_feature_status_calls()[0]
                  ->status_change);
    EXPECT_TRUE(fake_software_feature_manager()
                    ->set_feature_status_calls()[0]
                    ->success_callback);
    EXPECT_TRUE(fake_software_feature_manager()
                    ->set_feature_status_calls()[0]
                    ->error_callback);
  } else {
    EXPECT_EQ(1u, fake_feature_status_setter()->requests().size());
    EXPECT_EQ(device_for_test.instance_id,
              fake_feature_status_setter()->requests()[0].device_id);
    EXPECT_EQ(multidevice::SoftwareFeature::kBetterTogetherHost,
              fake_feature_status_setter()->requests()[0].feature);
    EXPECT_EQ(FeatureStatusChange::kEnableExclusively,
              fake_feature_status_setter()->requests()[0].status_change);
    EXPECT_TRUE(fake_feature_status_setter()->requests()[0].success_callback);
    EXPECT_TRUE(fake_feature_status_setter()->requests()[0].error_callback);
  }

  // The DeviceSyncImpl::SetFeatureStatus() callback has not yet been invoked.
  EXPECT_TRUE(set_feature_status_results().empty());

  // Now, invoke the success callback.
  if (features::ShouldUseV1DeviceSync()) {
    std::move(fake_software_feature_manager()
                  ->set_feature_status_calls()[0]
                  ->success_callback)
        .Run();
  } else {
    std::move(fake_feature_status_setter()->requests()[0].success_callback)
        .Run();
  }

  // The DeviceSyncImpl::SetFeatureStatus() callback still has not yet been
  // invoked since a device sync has not confirmed the feature state change yet.
  EXPECT_TRUE(set_feature_status_results().empty());

  // Simulate a sync which includes the device with the correct "enabled" state.
  device_for_test
      .software_features[multidevice::SoftwareFeature::kBetterTogetherHost] =
      multidevice::SoftwareFeatureState::kEnabled;
  SimulateSync(true /* success */, {device_for_test});
  base::RunLoop().RunUntilIdle();

  ASSERT_EQ(1u, set_feature_status_results().size());
  EXPECT_EQ(mojom::NetworkRequestResult::kSuccess,
            set_feature_status_results()[0]);
}

TEST_P(DeviceSyncServiceTest,
       SetFeatureStatus_RequestSucceedsButDoesNotTakeEffect) {
  if (!features::ShouldUseV2DeviceSync())
    return;

  InitializeServiceSuccessfully();

  // Expected device feature states after SetFeatureStatus() calls:
  // * Device 0 has kSmartLockHost disabled.
  // * Device 1 has kSmartLockHost disabled.
  // * Device 2 has kBetterTogetherHost enabled exclusively.
  // * Device 3 has kInstantTetheringHost enabled.
  // * Device 4 has kMessagesForWebHost disabled.
  multidevice::RemoteDeviceList expected_remote_devices =
      multidevice::CreateRemoteDeviceListForTest(5u);
  expected_remote_devices[0]
      .software_features[multidevice::SoftwareFeature::kSmartLockHost] =
      multidevice::SoftwareFeatureState::kSupported;
  expected_remote_devices[1]
      .software_features[multidevice::SoftwareFeature::kSmartLockHost] =
      multidevice::SoftwareFeatureState::kSupported;
  expected_remote_devices[2]
      .software_features[multidevice::SoftwareFeature::kBetterTogetherHost] =
      multidevice::SoftwareFeatureState::kEnabled;
  expected_remote_devices[3]
      .software_features[multidevice::SoftwareFeature::kInstantTetheringHost] =
      multidevice::SoftwareFeatureState::kEnabled;
  expected_remote_devices[4]
      .software_features[multidevice::SoftwareFeature::kMessagesForWebHost] =
      multidevice::SoftwareFeatureState::kSupported;

  CallSetFeatureStatus(expected_remote_devices[0].instance_id,
                       multidevice::SoftwareFeature::kSmartLockHost,
                       FeatureStatusChange::kDisable);
  CallSetFeatureStatus(expected_remote_devices[1].instance_id,
                       multidevice::SoftwareFeature::kSmartLockHost,
                       FeatureStatusChange::kDisable);
  CallSetFeatureStatus(expected_remote_devices[2].instance_id,
                       multidevice::SoftwareFeature::kBetterTogetherHost,
                       FeatureStatusChange::kEnableExclusively);
  CallSetFeatureStatus(expected_remote_devices[3].instance_id,
                       multidevice::SoftwareFeature::kInstantTetheringHost,
                       FeatureStatusChange::kEnableNonExclusively);
  CallSetFeatureStatus(expected_remote_devices[4].instance_id,
                       multidevice::SoftwareFeature::kMessagesForWebHost,
                       FeatureStatusChange::kDisable);

  if (features::ShouldUseV1DeviceSync()) {
    EXPECT_EQ(
        5u, fake_software_feature_manager()->set_feature_status_calls().size());
  } else {
    EXPECT_EQ(5u, fake_feature_status_setter()->requests().size());
  }

  // The DeviceSyncImpl::SetFeatureStatus() callbacks have not yet been invoked.
  EXPECT_TRUE(set_feature_status_results().empty());

  // Now, invoke the success callbacks.
  for (size_t i = 0; i < 5u; ++i) {
    if (features::ShouldUseV1DeviceSync()) {
      std::move(fake_software_feature_manager()
                    ->set_feature_status_calls()[i]
                    ->success_callback)
          .Run();
    } else {
      std::move(fake_feature_status_setter()->requests()[i].success_callback)
          .Run();
    }
  }

  // The DeviceSyncImpl::SetFeatureStatus() callbacks still have not been
  // invoked since a DeviceSync has not confirmed any of the requested feature
  // state changes yet.
  EXPECT_TRUE(set_feature_status_results().empty());

  // Simulate a DeviceSync which returns unexpected device feature states:
  // * Device 0 not in list of devices.
  // * Device 1 missing kSmartLockHost entry in the feature list.
  // * Device 2 has kBetterTogetherHost enabled but not exclusively since device
  //   1 also has it enabled.
  // * Device 3 does not have kInstantTetheringHost enabled.
  // * Device 4 does not have kMessagesForWebHost disabled.
  multidevice::RemoteDeviceList remote_devices_from_first_sync =
      multidevice::CreateRemoteDeviceListForTest(5u);
  remote_devices_from_first_sync[1]
      .software_features[multidevice::SoftwareFeature::kBetterTogetherHost] =
      multidevice::SoftwareFeatureState::kEnabled;
  remote_devices_from_first_sync[2]
      .software_features[multidevice::SoftwareFeature::kBetterTogetherHost] =
      multidevice::SoftwareFeatureState::kEnabled;
  remote_devices_from_first_sync[3]
      .software_features[multidevice::SoftwareFeature::kInstantTetheringHost] =
      multidevice::SoftwareFeatureState::kSupported;
  remote_devices_from_first_sync[4]
      .software_features[multidevice::SoftwareFeature::kMessagesForWebHost] =
      multidevice::SoftwareFeatureState::kEnabled;
  remote_devices_from_first_sync.erase(remote_devices_from_first_sync.begin());

  SimulateSync(true /* success */, remote_devices_from_first_sync);
  base::RunLoop().RunUntilIdle();

  // The DeviceSyncImpl::SetFeatureStatus() callbacks still have not yet been
  // invoked since the latest DeviceSync did not reflect the requested feature
  // state changes.
  EXPECT_EQ(0u, set_feature_status_results().size());

  // Simulate a DeviceSync which returns the expected device feature states:
  EXPECT_TRUE(CallForceSyncNow());
  SimulateSync(true /* success */, expected_remote_devices);
  base::RunLoop().RunUntilIdle();

  ASSERT_EQ(5u, set_feature_status_results().size());
  for (mojom::NetworkRequestResult result : set_feature_status_results())
    EXPECT_EQ(mojom::NetworkRequestResult::kSuccess, result);
}

TEST_P(DeviceSyncServiceTest, SetFeatureStatus_Error) {
  if (!features::ShouldUseV2DeviceSync())
    return;

  InitializeServiceSuccessfully();

  multidevice::RemoteDevice device_for_test = test_devices()[0];

  // Attempt to exclusively enable kBetterTogetherHost for the device.
  CallSetFeatureStatus(device_for_test.instance_id,
                       multidevice::SoftwareFeature::kBetterTogetherHost,
                       FeatureStatusChange::kEnableExclusively);

  // The DeviceSyncImpl::SetFeatureStatus() callback has not yet been invoked.
  EXPECT_TRUE(set_feature_status_results().empty());

  // Now, invoke the error callback.
  if (features::ShouldUseV1DeviceSync()) {
    std::move(fake_software_feature_manager()
                  ->set_feature_status_calls()[0]
                  ->error_callback)
        .Run(NetworkRequestError::kBadRequest);
  } else {
    std::move(fake_feature_status_setter()->requests()[0].error_callback)
        .Run(NetworkRequestError::kBadRequest);
  }

  // The DeviceSyncImpl::SetFeatureStatus() callback is invoked with the same
  // error code.
  ASSERT_EQ(1u, set_feature_status_results().size());
  EXPECT_EQ(mojom::NetworkRequestResult::kBadRequest,
            set_feature_status_results()[0]);
}

TEST_P(DeviceSyncServiceTest, FindEligibleDevices) {
  if (!features::ShouldUseV1DeviceSync())
    return;

  InitializeServiceSuccessfully();

  const auto& find_eligible_calls =
      fake_software_feature_manager()->find_eligible_multidevice_host_calls();
  EXPECT_EQ(0u, find_eligible_calls.size());

  // Find devices which are kBetterTogetherHost.
  CallFindEligibleDevices(multidevice::SoftwareFeature::kBetterTogetherHost);
  EXPECT_EQ(1u, find_eligible_calls.size());
  EXPECT_EQ(multidevice::SoftwareFeature::kBetterTogetherHost,
            find_eligible_calls[0]->software_feature);

  // The callback has not yet been invoked.
  EXPECT_FALSE(GetLastFindEligibleDevicesResponseAndReset());

  // Now, invoke the success callback, simultating that device 0 is eligible and
  // devices 1-4 are not.
  std::move(find_eligible_calls[0]->success_callback)
      .Run(std::vector<cryptauth::ExternalDeviceInfo>(
               test_device_infos().begin(), test_device_infos().begin()),
           std::vector<cryptauth::IneligibleDevice>(
               test_ineligible_devices().begin() + 1,
               test_ineligible_devices().end()));
  base::RunLoop().RunUntilIdle();
  auto last_response = GetLastFindEligibleDevicesResponseAndReset();
  EXPECT_TRUE(last_response);
  EXPECT_EQ(mojom::NetworkRequestResult::kSuccess, last_response->first);
  EXPECT_EQ(last_response->second->eligible_devices,
            multidevice::RemoteDeviceList(test_devices().begin(),
                                          test_devices().begin()));
  EXPECT_EQ(last_response->second->ineligible_devices,
            multidevice::RemoteDeviceList(test_devices().begin() + 1,
                                          test_devices().end()));

  histogram_tester().ExpectBucketCount<bool>(
      "MultiDevice.DeviceSyncService.FindEligibleDevices.Result", false, 0);
  histogram_tester().ExpectBucketCount<bool>(
      "MultiDevice.DeviceSyncService.FindEligibleDevices.Result", true, 1);

  // Find devices which are BETTER_TOGETHER_HOSTs again.
  CallFindEligibleDevices(multidevice::SoftwareFeature::kBetterTogetherHost);
  EXPECT_EQ(2u, find_eligible_calls.size());
  EXPECT_EQ(multidevice::SoftwareFeature::kBetterTogetherHost,
            find_eligible_calls[1]->software_feature);

  // The callback has not yet been invoked.
  EXPECT_FALSE(GetLastFindEligibleDevicesResponseAndReset());

  // Now, invoke the error callback.
  std::move(find_eligible_calls[1]->error_callback)
      .Run(NetworkRequestError::kOffline);
  base::RunLoop().RunUntilIdle();
  last_response = GetLastFindEligibleDevicesResponseAndReset();
  EXPECT_TRUE(last_response);
  EXPECT_EQ(mojom::NetworkRequestResult::kOffline, last_response->first);
  EXPECT_FALSE(last_response->second /* response */);

  histogram_tester().ExpectBucketCount<bool>(
      "MultiDevice.DeviceSyncService.FindEligibleDevices.Result", false, 1);
  histogram_tester().ExpectTotalCount(
      "MultiDevice.DeviceSyncService.FindEligibleDevices.Result."
      "FailureReason",
      1);
  histogram_tester().ExpectBucketCount<bool>(
      "MultiDevice.DeviceSyncService.FindEligibleDevices.Result", true, 1);
}

TEST_P(DeviceSyncServiceTest, NotifyDevices_Success) {
  if (!features::ShouldUseV2DeviceSync())
    return;

  InitializeServiceSuccessfully();

  EXPECT_EQ(0u, fake_device_notifier()->requests().size());

  std::vector<std::string> device_instance_ids = {
      test_devices()[0].instance_id, test_devices()[1].instance_id};

  CallNotifyDevices(device_instance_ids,
                    cryptauthv2::TargetService::DEVICE_SYNC,
                    multidevice::SoftwareFeature::kBetterTogetherHost);
  EXPECT_EQ(1u, fake_device_notifier()->requests().size());
  EXPECT_EQ(device_instance_ids,
            fake_device_notifier()->requests()[0].device_ids);
  EXPECT_EQ(cryptauthv2::TargetService::DEVICE_SYNC,
            fake_device_notifier()->requests()[0].target_service);
  EXPECT_EQ(CryptAuthFeatureType::kBetterTogetherHostEnabled,
            fake_device_notifier()->requests()[0].feature_type);
  EXPECT_TRUE(fake_device_notifier()->requests()[0].success_callback);
  EXPECT_TRUE(fake_device_notifier()->requests()[0].error_callback);

  // The DeviceSyncImpl::NotifyDevices() callback has not yet been invoked.
  EXPECT_TRUE(notify_devices_results().empty());

  // Now, invoke the CryptAuthDeviceNotifier success callback.
  std::move(fake_device_notifier()->requests()[0].success_callback).Run();

  // The DeviceSyncImpl::NotifyDevices() callback should have been invoked.
  ASSERT_EQ(1u, notify_devices_results().size());
  EXPECT_EQ(mojom::NetworkRequestResult::kSuccess, notify_devices_results()[0]);
}

TEST_P(DeviceSyncServiceTest, NotifyDevices_Error) {
  if (!features::ShouldUseV2DeviceSync())
    return;

  InitializeServiceSuccessfully();

  CallNotifyDevices(
      {test_devices()[0].instance_id, test_devices()[1].instance_id},
      cryptauthv2::TargetService::DEVICE_SYNC,
      multidevice::SoftwareFeature::kBetterTogetherHost);

  // The DeviceSyncImpl::NotifyDevices() callback has not yet been invoked.
  EXPECT_TRUE(notify_devices_results().empty());

  // Now, invoke the CryptAuthDeviceNotifier error callback.
  std::move(fake_device_notifier()->requests()[0].error_callback)
      .Run(NetworkRequestError::kBadRequest);

  // The DeviceSyncImpl::NotifyDevices() callback is invoked with the same
  // error code.
  ASSERT_EQ(1u, notify_devices_results().size());
  EXPECT_EQ(mojom::NetworkRequestResult::kBadRequest,
            notify_devices_results()[0]);
}

TEST_P(DeviceSyncServiceTest, GetDebugInfo) {
  static const base::TimeDelta kTimeBetweenEpochAndLastEnrollment =
      base::Days(365 * 50);  // 50 years
  static const base::TimeDelta kTimeUntilNextEnrollment = base::Days(10);

  static const base::TimeDelta kTimeBetweenEpochAndLastSync =
      base::Days(366 * 50);  // 50 years and 1 day
  static const base::TimeDelta kTimeUntilNextSync = base::Days(11);

  InitializeServiceSuccessfully();

  fake_cryptauth_enrollment_manager()->set_last_enrollment_time(
      base::Time::FromDeltaSinceWindowsEpoch(
          kTimeBetweenEpochAndLastEnrollment));
  fake_cryptauth_enrollment_manager()->set_time_to_next_attempt(
      kTimeUntilNextEnrollment);
  fake_cryptauth_enrollment_manager()->set_is_recovering_from_failure(false);
  fake_cryptauth_enrollment_manager()->set_is_enrollment_in_progress(true);

  if (features::ShouldUseV1DeviceSync()) {
    fake_cryptauth_device_manager()->set_last_sync_time(
        base::Time::FromDeltaSinceWindowsEpoch(kTimeBetweenEpochAndLastSync));
    fake_cryptauth_device_manager()->set_time_to_next_attempt(
        kTimeUntilNextSync);
    fake_cryptauth_device_manager()->set_is_recovering_from_failure(false);
    fake_cryptauth_device_manager()->set_is_sync_in_progress(false);
  }

  if (features::ShouldUseV2DeviceSync()) {
    fake_cryptauth_v2_device_manager()->ForceDeviceSyncNow(
        cryptauthv2::ClientMetadata::MANUAL /* invocation_reason */,
        std::nullopt /* session_id */);
    fake_cryptauth_v2_device_manager()->FinishNextForcedDeviceSync(
        CryptAuthDeviceSyncResult(
            CryptAuthDeviceSyncResult::ResultCode::kSuccess,
            true /* did_device_registry_change */,
            std::nullopt /* client_directive */),
        base::Time::FromDeltaSinceWindowsEpoch(
            kTimeBetweenEpochAndLastSync) /* device_sync_finish_time */);
    fake_cryptauth_v2_device_manager()->set_time_to_next_attempt(
        kTimeUntilNextSync);
  }

  const auto& result = CallGetDebugInfo();
  EXPECT_TRUE(result);
  EXPECT_EQ(base::Time::FromDeltaSinceWindowsEpoch(
                kTimeBetweenEpochAndLastEnrollment),
            result->last_enrollment_time);
  EXPECT_EQ(base::TimeDelta(kTimeUntilNextEnrollment),
            result->time_to_next_enrollment_attempt);
  EXPECT_FALSE(result->is_recovering_from_enrollment_failure);
  EXPECT_TRUE(result->is_enrollment_in_progress);
  EXPECT_EQ(
      base::Time::FromDeltaSinceWindowsEpoch(kTimeBetweenEpochAndLastSync),
      result->last_sync_time);
  EXPECT_EQ(base::TimeDelta(kTimeUntilNextSync),
            result->time_to_next_sync_attempt);
  EXPECT_FALSE(result->is_recovering_from_sync_failure);
  EXPECT_FALSE(result->is_sync_in_progress);
}

// Runs tests 8 times with all possible combinations of v2 Enrollment, v2
// DeviceSync, and v1 DeviceSync flags.
INSTANTIATE_TEST_SUITE_P(All,
                         DeviceSyncServiceTest,
                         ::testing::Combine(::testing::Bool(),
                                            ::testing::Bool(),
                                            ::testing::Bool()));

}  // namespace device_sync

}  // namespace ash