| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| |
| #include "base/memory/scoped_refptr.h" |
| #include "base/no_destructor.h" |
| #include "base/optional.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/null_task_runner.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/timer/mock_timer.h" |
| #include "chromeos/components/multidevice/remote_device_test_util.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/services/device_sync/cryptauth_device_manager_impl.h" |
| #include "chromeos/services/device_sync/cryptauth_enrollment_manager_impl.h" |
| #include "chromeos/services/device_sync/cryptauth_gcm_manager_impl.h" |
| #include "chromeos/services/device_sync/device_sync_impl.h" |
| #include "chromeos/services/device_sync/device_sync_service.h" |
| #include "chromeos/services/device_sync/fake_cryptauth_device_manager.h" |
| #include "chromeos/services/device_sync/fake_cryptauth_enrollment_manager.h" |
| #include "chromeos/services/device_sync/fake_cryptauth_gcm_manager.h" |
| #include "chromeos/services/device_sync/fake_device_sync_observer.h" |
| #include "chromeos/services/device_sync/fake_remote_device_provider.h" |
| #include "chromeos/services/device_sync/fake_software_feature_manager.h" |
| #include "chromeos/services/device_sync/public/cpp/fake_gcm_device_info_provider.h" |
| #include "chromeos/services/device_sync/public/mojom/constants.mojom.h" |
| #include "chromeos/services/device_sync/public/mojom/device_sync.mojom.h" |
| #include "chromeos/services/device_sync/remote_device_provider_impl.h" |
| #include "chromeos/services/device_sync/software_feature_manager_impl.h" |
| #include "components/gcm_driver/fake_gcm_driver.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "services/identity/public/cpp/identity_test_environment.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/service_manager/public/cpp/test/test_connector_factory.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace chromeos { |
| |
| namespace device_sync { |
| |
| namespace { |
| |
| const char kTestEmail[] = "example@gmail.com"; |
| const char kTestGcmDeviceInfoLongDeviceId[] = "longDeviceId"; |
| const char kTestCryptAuthGCMRegistrationId[] = "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::Closure 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 OnFindEligibleDevicesCalled() override { |
| on_delegate_call_closure_.Run(); |
| } |
| |
| private: |
| base::Closure on_delegate_call_closure_; |
| }; |
| |
| class FakeCryptAuthGCMManagerFactory : public CryptAuthGCMManagerImpl::Factory { |
| public: |
| FakeCryptAuthGCMManagerFactory(gcm::FakeGCMDriver* fake_gcm_driver, |
| TestingPrefServiceSimple* test_pref_service) |
| : fake_gcm_driver_(fake_gcm_driver), |
| test_pref_service_(test_pref_service) {} |
| |
| ~FakeCryptAuthGCMManagerFactory() override = default; |
| |
| FakeCryptAuthGCMManager* instance() { return instance_; } |
| |
| // CryptAuthGCMManagerImpl::Factory: |
| std::unique_ptr<CryptAuthGCMManager> BuildInstance( |
| gcm::GCMDriver* gcm_driver, |
| PrefService* pref_service) override { |
| EXPECT_EQ(fake_gcm_driver_, gcm_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>( |
| kTestCryptAuthGCMRegistrationId); |
| instance_ = instance.get(); |
| |
| return std::move(instance); |
| } |
| |
| private: |
| gcm::FakeGCMDriver* fake_gcm_driver_; |
| TestingPrefServiceSimple* test_pref_service_; |
| |
| FakeCryptAuthGCMManager* 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_; } |
| |
| // CryptAuthDeviceManagerImpl::Factory: |
| std::unique_ptr<CryptAuthDeviceManager> BuildInstance( |
| base::Clock* clock, |
| CryptAuthClientFactory* client_factory, |
| CryptAuthGCMManager* gcm_manager, |
| PrefService* pref_service) override { |
| 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 std::move(instance); |
| } |
| |
| private: |
| base::SimpleTestClock* simple_test_clock_; |
| FakeCryptAuthGCMManagerFactory* fake_cryptauth_gcm_manager_factory_; |
| TestingPrefServiceSimple* test_pref_service_; |
| |
| FakeCryptAuthDeviceManager* 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> BuildInstance( |
| 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_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(); |
| |
| return std::move(instance); |
| } |
| |
| private: |
| base::SimpleTestClock* simple_test_clock_; |
| FakeCryptAuthGCMManagerFactory* fake_cryptauth_gcm_manager_factory_; |
| TestingPrefServiceSimple* test_pref_service_; |
| |
| bool device_already_enrolled_in_cryptauth_ = false; |
| FakeCryptAuthEnrollmentManager* instance_ = nullptr; |
| }; |
| |
| class FakeRemoteDeviceProviderFactory |
| : public RemoteDeviceProviderImpl::Factory { |
| public: |
| FakeRemoteDeviceProviderFactory( |
| const multidevice::RemoteDeviceList& initial_devices, |
| identity::IdentityManager* identity_manager, |
| FakeCryptAuthDeviceManagerFactory* fake_cryptauth_device_manager_factory, |
| FakeCryptAuthEnrollmentManagerFactory* |
| fake_cryptauth_enrollment_manager_factory) |
| : initial_devices_(initial_devices), |
| identity_manager_(identity_manager), |
| fake_cryptauth_device_manager_factory_( |
| fake_cryptauth_device_manager_factory), |
| fake_cryptauth_enrollment_manager_factory_( |
| fake_cryptauth_enrollment_manager_factory) {} |
| |
| ~FakeRemoteDeviceProviderFactory() override = default; |
| |
| FakeRemoteDeviceProvider* instance() { return instance_; } |
| |
| // RemoteDeviceProviderImpl::Factory: |
| std::unique_ptr<RemoteDeviceProvider> BuildInstance( |
| CryptAuthDeviceManager* device_manager, |
| const std::string& user_id, |
| const std::string& user_private_key) override { |
| EXPECT_EQ(fake_cryptauth_device_manager_factory_->instance(), |
| device_manager); |
| EXPECT_EQ(identity_manager_->GetPrimaryAccountId(), user_id); |
| 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 std::move(instance); |
| } |
| |
| private: |
| const multidevice::RemoteDeviceList& initial_devices_; |
| |
| identity::IdentityManager* identity_manager_; |
| FakeCryptAuthDeviceManagerFactory* fake_cryptauth_device_manager_factory_; |
| FakeCryptAuthEnrollmentManagerFactory* |
| fake_cryptauth_enrollment_manager_factory_; |
| |
| FakeRemoteDeviceProvider* instance_ = nullptr; |
| }; |
| |
| class FakeSoftwareFeatureManagerFactory |
| : public SoftwareFeatureManagerImpl::Factory { |
| public: |
| FakeSoftwareFeatureManagerFactory() = default; |
| ~FakeSoftwareFeatureManagerFactory() override = default; |
| |
| FakeSoftwareFeatureManager* instance() { return instance_; } |
| |
| // SoftwareFeatureManagerImpl::Factory: |
| std::unique_ptr<SoftwareFeatureManager> BuildInstance( |
| CryptAuthClientFactory* cryptauth_client_factory) override { |
| // Only one instance is expected to be created per test. |
| EXPECT_FALSE(instance_); |
| |
| auto instance = std::make_unique<FakeSoftwareFeatureManager>(); |
| instance_ = instance.get(); |
| |
| return std::move(instance); |
| } |
| |
| private: |
| FakeSoftwareFeatureManager* instance_ = nullptr; |
| }; |
| |
| } // namespace |
| |
| class DeviceSyncServiceTest : public testing::Test { |
| public: |
| class FakePrefConnectionDelegate |
| : public DeviceSyncImpl::PrefConnectionDelegate { |
| public: |
| FakePrefConnectionDelegate( |
| std::unique_ptr<TestingPrefServiceSimple> test_pref_service) |
| : test_pref_service_(std::move(test_pref_service)), |
| test_pref_registry_( |
| base::WrapRefCounted(test_pref_service_->registry())) {} |
| |
| ~FakePrefConnectionDelegate() override = default; |
| |
| void InvokePendingCallback() { |
| EXPECT_FALSE(pending_callback_.is_null()); |
| std::move(pending_callback_).Run(std::move(test_pref_service_)); |
| |
| // Note: |pending_callback_| was passed from within the service, so it is |
| // necessary to let the rest of the current RunLoop run to ensure that |
| // the callback is executed before returning from this function. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| bool HasStartedPrefConnection() { |
| return HasFinishedPrefConnection() || !pending_callback_.is_null(); |
| } |
| |
| bool HasFinishedPrefConnection() { return !test_pref_service_.get(); } |
| |
| // DeviceSyncImpl::PrefConnectionDelegate: |
| scoped_refptr<PrefRegistrySimple> CreatePrefRegistry() override { |
| return test_pref_registry_; |
| } |
| |
| void ConnectToPrefService(service_manager::Connector* connector, |
| scoped_refptr<PrefRegistrySimple> pref_registry, |
| prefs::ConnectCallback callback) override { |
| EXPECT_EQ(test_pref_service_->registry(), pref_registry.get()); |
| pending_callback_ = std::move(callback); |
| } |
| |
| private: |
| std::unique_ptr<TestingPrefServiceSimple> test_pref_service_; |
| scoped_refptr<PrefRegistrySimple> test_pref_registry_; |
| |
| prefs::ConnectCallback pending_callback_; |
| }; |
| |
| class FakeDeviceSyncImplFactory : public DeviceSyncImpl::Factory { |
| public: |
| FakeDeviceSyncImplFactory( |
| std::unique_ptr<FakePrefConnectionDelegate> |
| fake_pref_connection_delegate, |
| std::unique_ptr<base::MockOneShotTimer> mock_timer, |
| base::SimpleTestClock* simple_test_clock) |
| : fake_pref_connection_delegate_( |
| std::move(fake_pref_connection_delegate)), |
| mock_timer_(std::move(mock_timer)), |
| simple_test_clock_(simple_test_clock) {} |
| |
| ~FakeDeviceSyncImplFactory() override = default; |
| |
| // DeviceSyncImpl::Factory: |
| std::unique_ptr<DeviceSyncBase> BuildInstance( |
| identity::IdentityManager* identity_manager, |
| gcm::GCMDriver* gcm_driver, |
| service_manager::Connector* connector, |
| const GcmDeviceInfoProvider* gcm_device_info_provider, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| std::unique_ptr<base::OneShotTimer> timer) override { |
| return base::WrapUnique(new DeviceSyncImpl( |
| identity_manager, gcm_driver, connector, gcm_device_info_provider, |
| std::move(url_loader_factory), simple_test_clock_, |
| std::move(fake_pref_connection_delegate_), std::move(mock_timer_))); |
| } |
| |
| private: |
| std::unique_ptr<FakePrefConnectionDelegate> fake_pref_connection_delegate_; |
| std::unique_ptr<base::MockOneShotTimer> mock_timer_; |
| base::SimpleTestClock* simple_test_clock_; |
| }; |
| |
| DeviceSyncServiceTest() |
| : test_devices_(GenerateTestRemoteDevices()), |
| test_device_infos_(GenerateTestExternalDeviceInfos(test_devices_)), |
| test_ineligible_devices_( |
| GenerateTestIneligibleDevices(test_device_infos_)) {} |
| ~DeviceSyncServiceTest() override = default; |
| |
| void SetUp() override { |
| DBusThreadManager::Initialize(); |
| |
| fake_gcm_driver_ = std::make_unique<gcm::FakeGCMDriver>(); |
| |
| auto test_pref_service = std::make_unique<TestingPrefServiceSimple>(); |
| test_pref_service_ = test_pref_service.get(); |
| |
| simple_test_clock_ = std::make_unique<base::SimpleTestClock>(); |
| |
| // Note: The primary account is guaranteed to be available when the service |
| // starts up since this is a CrOS-only service, and CrOS requires that |
| // the user logs in. |
| identity_test_environment_ = |
| std::make_unique<identity::IdentityTestEnvironment>(); |
| identity_test_environment_->MakePrimaryAccountAvailable(kTestEmail); |
| |
| fake_cryptauth_gcm_manager_factory_ = |
| std::make_unique<FakeCryptAuthGCMManagerFactory>(fake_gcm_driver_.get(), |
| test_pref_service_); |
| CryptAuthGCMManagerImpl::Factory::SetInstanceForTesting( |
| fake_cryptauth_gcm_manager_factory_.get()); |
| |
| fake_cryptauth_device_manager_factory_ = |
| std::make_unique<FakeCryptAuthDeviceManagerFactory>( |
| simple_test_clock_.get(), fake_cryptauth_gcm_manager_factory_.get(), |
| test_pref_service_); |
| CryptAuthDeviceManagerImpl::Factory::SetInstanceForTesting( |
| fake_cryptauth_device_manager_factory_.get()); |
| |
| fake_cryptauth_enrollment_manager_factory_ = |
| std::make_unique<FakeCryptAuthEnrollmentManagerFactory>( |
| simple_test_clock_.get(), fake_cryptauth_gcm_manager_factory_.get(), |
| test_pref_service_); |
| CryptAuthEnrollmentManagerImpl::Factory::SetInstanceForTesting( |
| fake_cryptauth_enrollment_manager_factory_.get()); |
| |
| fake_remote_device_provider_factory_ = |
| std::make_unique<FakeRemoteDeviceProviderFactory>( |
| test_devices_, identity_test_environment_->identity_manager(), |
| fake_cryptauth_device_manager_factory_.get(), |
| fake_cryptauth_enrollment_manager_factory_.get()); |
| RemoteDeviceProviderImpl::Factory::SetInstanceForTesting( |
| fake_remote_device_provider_factory_.get()); |
| |
| fake_software_feature_manager_factory_ = |
| std::make_unique<FakeSoftwareFeatureManagerFactory>(); |
| SoftwareFeatureManagerImpl::Factory::SetInstanceForTesting( |
| fake_software_feature_manager_factory_.get()); |
| |
| auto fake_pref_connection_delegate = |
| std::make_unique<FakePrefConnectionDelegate>( |
| std::move(test_pref_service)); |
| fake_pref_connection_delegate_ = fake_pref_connection_delegate.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(fake_pref_connection_delegate), std::move(mock_timer), |
| simple_test_clock_.get()); |
| DeviceSyncImpl::Factory::SetInstanceForTesting( |
| fake_device_sync_impl_factory_.get()); |
| |
| fake_gcm_device_info_provider_ = |
| std::make_unique<FakeGcmDeviceInfoProvider>(GetTestGcmDeviceInfo()); |
| |
| 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; |
| })); |
| |
| fake_device_sync_observer_ = std::make_unique<FakeDeviceSyncObserver>(); |
| service_ = std::make_unique<DeviceSyncService>( |
| identity_test_environment_->identity_manager(), fake_gcm_driver_.get(), |
| fake_gcm_device_info_provider_.get(), shared_url_loader_factory, |
| connector_factory_.RegisterInstance( |
| chromeos::device_sync::mojom::kServiceName)); |
| } |
| |
| void TearDown() override { DBusThreadManager::Shutdown(); } |
| |
| void ConnectToDeviceSyncService(bool device_already_enrolled_in_cryptauth) { |
| // Used in CompleteConnectionToPrefService(). |
| device_already_enrolled_in_cryptauth_ = |
| device_already_enrolled_in_cryptauth; |
| |
| fake_cryptauth_enrollment_manager_factory_ |
| ->set_device_already_enrolled_in_cryptauth( |
| device_already_enrolled_in_cryptauth); |
| |
| connector_factory_.GetDefaultConnector()->BindInterface(mojom::kServiceName, |
| &device_sync_); |
| |
| // Set |fake_device_sync_observer_|. |
| CallAddObserver(); |
| } |
| |
| void CompleteConnectionToPrefService() { |
| EXPECT_TRUE(fake_pref_connection_delegate()->HasStartedPrefConnection()); |
| EXPECT_FALSE(fake_pref_connection_delegate()->HasFinishedPrefConnection()); |
| |
| fake_pref_connection_delegate_->InvokePendingCallback(); |
| EXPECT_TRUE(fake_pref_connection_delegate()->HasFinishedPrefConnection()); |
| |
| // When connection to preferences is complete, CryptAuth classes are |
| // expected to be created and initialized. |
| EXPECT_TRUE(fake_cryptauth_gcm_manager_factory_->instance() |
| ->has_started_listening()); |
| EXPECT_TRUE( |
| fake_cryptauth_enrollment_manager_factory_->instance()->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() is called as the last step of the |
| // initialization flow. |
| EXPECT_EQ( |
| expected_to_be_initialized, |
| fake_cryptauth_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_factory_->instance(); |
| |
| 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(); |
| FakeRemoteDeviceProvider* remote_device_provider = |
| fake_remote_device_provider_factory_->instance(); |
| |
| 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 (!updated_devices.empty()) { |
| remote_device_provider->set_synced_remote_devices(updated_devices); |
| remote_device_provider->NotifyObserversDeviceListChanged(); |
| } |
| } |
| |
| void InitializeServiceSuccessfully() { |
| ConnectToDeviceSyncService(true /* device_already_enrolled_in_cryptauth */); |
| CompleteConnectionToPrefService(); |
| 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(); |
| } |
| |
| FakePrefConnectionDelegate* fake_pref_connection_delegate() { |
| return fake_pref_connection_delegate_; |
| } |
| |
| base::MockOneShotTimer* mock_timer() { return mock_timer_; } |
| |
| FakeCryptAuthEnrollmentManager* fake_cryptauth_enrollment_manager() { |
| return fake_cryptauth_enrollment_manager_factory_->instance(); |
| } |
| |
| FakeCryptAuthDeviceManager* fake_cryptauth_device_manager() { |
| return fake_cryptauth_device_manager_factory_->instance(); |
| } |
| |
| FakeSoftwareFeatureManager* fake_software_feature_manager() { |
| return fake_software_feature_manager_factory_->instance(); |
| } |
| |
| 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()); |
| |
| // 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); |
| |
| // Likewise, FindEligibleDevices() should also return a struct with the same |
| // 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 */); |
| |
| // GetDebugInfo() returns a null DebugInfo before initialization. |
| EXPECT_FALSE(CallGetDebugInfo()); |
| } |
| |
| void CallAddObserver() { |
| base::RunLoop run_loop; |
| device_sync_->AddObserver( |
| fake_device_sync_observer_->GenerateInterfacePtr(), |
| 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_factory_->instance()) { |
| EXPECT_EQ(last_force_enrollment_now_result_, |
| fake_cryptauth_enrollment_manager_factory_->instance() |
| ->IsEnrollmentInProgress()); |
| } |
| |
| return last_force_enrollment_now_result_; |
| } |
| |
| bool CallForceSyncNow() { |
| base::RunLoop run_loop; |
| device_sync_->ForceSyncNow( |
| base::BindOnce(&DeviceSyncServiceTest::OnForceSyncNowCompleted, |
| base::Unretained(this), run_loop.QuitClosure())); |
| run_loop.Run(); |
| |
| if (fake_cryptauth_device_manager_factory_->instance()) { |
| EXPECT_EQ(last_force_sync_now_result_, |
| fake_cryptauth_device_manager_factory_->instance() |
| ->IsSyncInProgress()); |
| } |
| |
| return last_force_sync_now_result_; |
| } |
| |
| const base::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 base::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::Bind(&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::Bind(&DeviceSyncServiceTest::OnSetSoftwareFeatureStateCompleted, |
| base::Unretained(this))); |
| run_loop.Run(); |
| |
| fake_software_feature_manager_factory_->instance()->set_delegate(nullptr); |
| } |
| |
| 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::Bind(&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::Bind(&DeviceSyncServiceTest::OnFindEligibleDevicesCompleted, |
| base::Unretained(this))); |
| run_loop.Run(); |
| |
| fake_software_feature_manager_factory_->instance()->set_delegate(nullptr); |
| } |
| |
| const base::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 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 OnGetLocalDeviceMetadataCompleted( |
| base::OnceClosure quit_closure, |
| const base::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 base::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 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 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::ScopedTaskEnvironment scoped_task_environment_; |
| const multidevice::RemoteDeviceList test_devices_; |
| const std::vector<cryptauth::ExternalDeviceInfo> test_device_infos_; |
| const std::vector<cryptauth::IneligibleDevice> test_ineligible_devices_; |
| |
| TestingPrefServiceSimple* test_pref_service_; |
| FakePrefConnectionDelegate* fake_pref_connection_delegate_; |
| base::MockOneShotTimer* 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<FakeCryptAuthDeviceManagerFactory> |
| fake_cryptauth_device_manager_factory_; |
| std::unique_ptr<FakeCryptAuthEnrollmentManagerFactory> |
| fake_cryptauth_enrollment_manager_factory_; |
| std::unique_ptr<FakeRemoteDeviceProviderFactory> |
| fake_remote_device_provider_factory_; |
| std::unique_ptr<FakeSoftwareFeatureManagerFactory> |
| fake_software_feature_manager_factory_; |
| |
| std::unique_ptr<identity::IdentityTestEnvironment> identity_test_environment_; |
| std::unique_ptr<gcm::FakeGCMDriver> fake_gcm_driver_; |
| std::unique_ptr<FakeGcmDeviceInfoProvider> fake_gcm_device_info_provider_; |
| |
| service_manager::TestConnectorFactory connector_factory_; |
| std::unique_ptr<DeviceSyncService> service_; |
| |
| bool device_already_enrolled_in_cryptauth_; |
| bool last_force_enrollment_now_result_; |
| bool last_force_sync_now_result_; |
| base::Optional<multidevice::RemoteDeviceList> last_synced_devices_result_; |
| base::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_; |
| base::Optional<mojom::DebugInfo> last_debug_info_result_; |
| |
| std::unique_ptr<FakeDeviceSyncObserver> fake_device_sync_observer_; |
| mojom::DeviceSyncPtr device_sync_; |
| |
| base::HistogramTester histogram_tester_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DeviceSyncServiceTest); |
| }; |
| |
| TEST_F(DeviceSyncServiceTest, PreferencesNeverConnect) { |
| ConnectToDeviceSyncService(false /* device_already_enrolled_in_cryptauth */); |
| |
| // A connection to the Preferences service should have started. |
| EXPECT_TRUE(fake_pref_connection_delegate()->HasStartedPrefConnection()); |
| EXPECT_FALSE(fake_pref_connection_delegate()->HasFinishedPrefConnection()); |
| |
| // Do not complete the connection; without this step, the other API functions |
| // should fail. |
| 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_F(DeviceSyncServiceTest, |
| DeviceNotAlreadyEnrolledInCryptAuth_FailsEnrollment) { |
| ConnectToDeviceSyncService(false /* device_already_enrolled_in_cryptauth */); |
| CompleteConnectionToPrefService(); |
| |
| // 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_F(DeviceSyncServiceTest, |
| DeviceNotAlreadyEnrolledInCryptAuth_FailsEnrollment_ThenSucceeds) { |
| ConnectToDeviceSyncService(false /* device_already_enrolled_in_cryptauth */); |
| CompleteConnectionToPrefService(); |
| |
| // 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_F(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_F(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_F(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_F(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_F(DeviceSyncServiceTest, SetSoftwareFeatureState_Success) { |
| 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. |
| 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(device_sync::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_F(DeviceSyncServiceTest, |
| SetSoftwareFeatureState_RequestSucceedsButDoesNotTakeEffect) { |
| 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(device_sync::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_F(DeviceSyncServiceTest, SetSoftwareFeatureState_Error) { |
| 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. |
| 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_F(DeviceSyncServiceTest, FindEligibleDevices) { |
| 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. |
| 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(device_sync::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. |
| 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_F(DeviceSyncServiceTest, GetDebugInfo) { |
| static const base::TimeDelta kTimeBetweenEpochAndLastEnrollment = |
| base::TimeDelta::FromDays(365 * 50); // 50 years |
| static const base::TimeDelta kTimeUntilNextEnrollment = |
| base::TimeDelta::FromDays(10); |
| |
| static const base::TimeDelta kTimeBetweenEpochAndLastSync = |
| base::TimeDelta::FromDays(366 * 50); // 50 years and 1 day |
| static const base::TimeDelta kTimeUntilNextSync = |
| base::TimeDelta::FromDays(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); |
| |
| 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(true); |
| fake_cryptauth_device_manager()->set_is_sync_in_progress(false); |
| |
| 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_TRUE(result->is_recovering_from_sync_failure); |
| EXPECT_FALSE(result->is_sync_in_progress); |
| } |
| |
| } // namespace device_sync |
| |
| } // namespace chromeos |