chromium/chromeos/ash/services/multidevice_setup/global_state_feature_manager_impl_unittest.cc

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

#include "chromeos/ash/services/multidevice_setup/global_state_feature_manager_impl.h"

#include <memory>
#include <optional>
#include <utility>
#include <vector>

#include "ash/constants/ash_features.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/timer/mock_timer.h"
#include "chromeos/ash/components/multidevice/remote_device_ref.h"
#include "chromeos/ash/components/multidevice/remote_device_test_util.h"
#include "chromeos/ash/components/multidevice/software_feature.h"
#include "chromeos/ash/components/multidevice/software_feature_state.h"
#include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h"
#include "chromeos/ash/services/multidevice_setup/fake_host_status_provider.h"
#include "chromeos/ash/services/multidevice_setup/global_state_feature_manager.h"
#include "chromeos/ash/services/multidevice_setup/public/cpp/prefs.h"
#include "chromeos/ash/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
#include "chromeos/ash/services/multidevice_setup/wifi_sync_notification_controller.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

namespace multidevice_setup {

namespace {

const GlobalStateFeatureManagerImpl::Factory::Option kTestOption =
    GlobalStateFeatureManagerImpl::Factory::Option::kWifiSync;
const multidevice::SoftwareFeature kTestHostFeature =
    multidevice::SoftwareFeature::kWifiSyncHost;
const multidevice::SoftwareFeature kTestClientFeature =
    multidevice::SoftwareFeature::kWifiSyncClient;
const std::string& kFeatureAllowedPrefName = kWifiSyncAllowedPrefName;
const char kPendingStatePrefName[] =
    "multidevice_setup.pending_set_wifi_sync_enabled_request";
const base::Feature& kTestFeatureFlag = features::kWifiSyncAndroid;

enum PendingState {
  kPendingNone = 0,
  kPendingEnable = 1,
  kPendingDisable = 2,
  kSetPendingEnableOnVerify = 3
};

const size_t kNumTestDevices = 4;

}  // namespace

class MultiDeviceSetupGlobalStateFeatureManagerImplTest
    : public ::testing::TestWithParam<bool> {
 public:
  MultiDeviceSetupGlobalStateFeatureManagerImplTest(
      const MultiDeviceSetupGlobalStateFeatureManagerImplTest&) = delete;
  MultiDeviceSetupGlobalStateFeatureManagerImplTest& operator=(
      const MultiDeviceSetupGlobalStateFeatureManagerImplTest&) = delete;

 protected:
  MultiDeviceSetupGlobalStateFeatureManagerImplTest()
      : test_devices_(
            multidevice::CreateRemoteDeviceRefListForTest(kNumTestDevices)) {}
  ~MultiDeviceSetupGlobalStateFeatureManagerImplTest() override = default;

  // testing::Test:
  void SetUp() override {
    // Tests are run once to simulate when v1 DeviceSync is enabled and once to
    // simulate when it is disabled, leaving only v2 DeviceSync operational. In
    // the former case, only public keys are needed, and in the latter case,
    // only Instance IDs are needed.
    for (multidevice::RemoteDeviceRef device : test_devices_) {
      if (features::ShouldUseV1DeviceSync())
        GetMutableRemoteDevice(device)->instance_id.clear();
      else
        GetMutableRemoteDevice(device)->public_key.clear();
    }

    SetFeatureSupportedInDeviceSyncClient();

    fake_host_status_provider_ = std::make_unique<FakeHostStatusProvider>();

    test_pref_service_ =
        std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
    GlobalStateFeatureManagerImpl::RegisterPrefs(
        test_pref_service_->registry());
    WifiSyncNotificationController::RegisterPrefs(
        test_pref_service_->registry());
    test_pref_service_->registry()->RegisterBooleanPref(kFeatureAllowedPrefName,
                                                        true);
    fake_device_sync_client_ =
        std::make_unique<device_sync::FakeDeviceSyncClient>();
    fake_device_sync_client_->set_synced_devices(test_devices_);
    multidevice::RemoteDeviceRef local_device =
        multidevice::CreateRemoteDeviceRefForTest();
    GetMutableRemoteDevice(local_device)
        ->software_features[kTestClientFeature] =
        multidevice::SoftwareFeatureState::kSupported;
    fake_device_sync_client_->set_local_device_metadata(local_device);
  }

  void TearDown() override {}

  void SetHostInDeviceSyncClient(
      const std::optional<multidevice::RemoteDeviceRef>& host_device) {
    for (const auto& remote_device : test_devices_) {
      bool should_be_host =
          host_device != std::nullopt &&
          ((!remote_device.instance_id().empty() &&
            host_device->instance_id() == remote_device.instance_id()) ||
           (!remote_device.GetDeviceId().empty() &&
            host_device->GetDeviceId() == remote_device.GetDeviceId()));

      GetMutableRemoteDevice(remote_device)
          ->software_features
              [multidevice::SoftwareFeature::kBetterTogetherHost] =
          should_be_host ? multidevice::SoftwareFeatureState::kEnabled
                         : multidevice::SoftwareFeatureState::kSupported;
    }
    fake_device_sync_client_->NotifyNewDevicesSynced();
  }

  void SetFeatureSupportedInDeviceSyncClient() {
    for (const auto& remote_device : test_devices_) {
      GetMutableRemoteDevice(remote_device)
          ->software_features[kTestHostFeature] =
          multidevice::SoftwareFeatureState::kSupported;
    }
  }

  void CreateDelegate(
      const std::optional<multidevice::RemoteDeviceRef>& initial_host,
      int initial_pending_state = kPendingNone) {
    SetHostInDeviceSyncClient(initial_host);
    test_pref_service_->SetInteger(kPendingStatePrefName,
                                   initial_pending_state);

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

    SetHostWithStatus(initial_host);

    delegate_ = GlobalStateFeatureManagerImpl::Factory::Create(
        kTestOption, fake_host_status_provider_.get(), test_pref_service_.get(),
        fake_device_sync_client_.get(), std::move(mock_timer));
  }

  void SetHostWithStatus(
      const std::optional<multidevice::RemoteDeviceRef>& host_device) {
    mojom::HostStatus host_status =
        (host_device == std::nullopt ? mojom::HostStatus::kNoEligibleHosts
                                     : mojom::HostStatus::kHostVerified);
    fake_host_status_provider_->SetHostWithStatus(host_status, host_device);
  }

  void SetIsFeatureEnabled(bool enabled) {
    delegate_->SetIsFeatureEnabled(enabled);

    HostStatusProvider::HostStatusWithDevice host_with_status =
        fake_host_status_provider_->GetHostWithStatus();
    if (host_with_status.host_status() != mojom::HostStatus::kHostVerified) {
      return;
    }

    multidevice::RemoteDeviceRef host_device = *host_with_status.host_device();

    bool enabled_on_backend =
        (host_device.GetSoftwareFeatureState(kTestHostFeature) ==
         multidevice::SoftwareFeatureState::kEnabled);
    bool pending_request_state_same_as_backend =
        (enabled == enabled_on_backend);

    if (pending_request_state_same_as_backend) {
      return;
    }

    VerifyLatestSetHostNetworkRequest(host_device, enabled);
  }

  void VerifyLatestSetHostNetworkRequest(
      const multidevice::RemoteDeviceRef expected_host,
      bool expected_should_enable) {
    if (features::ShouldUseV1DeviceSync()) {
      ASSERT_FALSE(
          fake_device_sync_client_->set_software_feature_state_inputs_queue()
              .empty());
      const device_sync::FakeDeviceSyncClient::SetSoftwareFeatureStateInputs&
          inputs = fake_device_sync_client_
                       ->set_software_feature_state_inputs_queue()
                       .back();
      EXPECT_EQ(expected_host.public_key(), inputs.public_key);
      EXPECT_EQ(kTestHostFeature, inputs.software_feature);
      EXPECT_EQ(expected_should_enable, inputs.enabled);
      EXPECT_EQ(expected_should_enable, inputs.is_exclusive);
      return;
    }

    // Verify inputs to SetFeatureStatus().
    ASSERT_FALSE(
        fake_device_sync_client_->set_feature_status_inputs_queue().empty());
    const device_sync::FakeDeviceSyncClient::SetFeatureStatusInputs& inputs =
        fake_device_sync_client_->set_feature_status_inputs_queue().back();
    EXPECT_EQ(expected_host.instance_id(), inputs.device_instance_id);
    EXPECT_EQ(kTestHostFeature, inputs.feature);
    EXPECT_EQ(expected_should_enable
                  ? device_sync::FeatureStatusChange::kEnableExclusively
                  : device_sync::FeatureStatusChange::kDisable,
              inputs.status_change);
  }

  int GetSetHostNetworkRequestCallbackQueueSize() {
    return features::ShouldUseV1DeviceSync()
               ? fake_device_sync_client_
                     ->GetSetSoftwareFeatureStateInputsQueueSize()
               : fake_device_sync_client_->GetSetFeatureStatusInputsQueueSize();
  }

  void InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult result_code,
      bool expected_to_notify_observer_and_start_retry_timer) {
    if (features::ShouldUseV1DeviceSync()) {
      fake_device_sync_client_->InvokePendingSetSoftwareFeatureStateCallback(
          result_code);
    } else {
      fake_device_sync_client_->InvokePendingSetFeatureStatusCallback(
          result_code);
    }

    EXPECT_EQ(expected_to_notify_observer_and_start_retry_timer,
              mock_timer_->IsRunning());
  }

  void SetHostInDeviceSyncClient(
      const std::optional<multidevice::RemoteDeviceRef>& host_device,
      bool enabled) {
    GetMutableRemoteDevice(*host_device)->software_features[kTestHostFeature] =
        (enabled ? multidevice::SoftwareFeatureState::kEnabled
                 : multidevice::SoftwareFeatureState::kSupported);
    fake_device_sync_client_->NotifyNewDevicesSynced();
  }

  void SetFeatureFlags(bool use_v1_devicesync, bool enable_feature_flag) {
    std::vector<base::test::FeatureRef> enabled_features;
    std::vector<base::test::FeatureRef> disabled_features;

    // These flags have no direct effect of on the GlobalStateFeatureManager;
    // however, v2 Enrollment and DeviceSync must be enabled before v1
    // DeviceSync can be disabled.
    enabled_features.push_back(features::kCryptAuthV2Enrollment);
    enabled_features.push_back(features::kCryptAuthV2DeviceSync);

    if (use_v1_devicesync) {
      disabled_features.push_back(features::kDisableCryptAuthV1DeviceSync);
    } else {
      enabled_features.push_back(features::kDisableCryptAuthV1DeviceSync);
    }

    if (enable_feature_flag) {
      enabled_features.push_back(kTestFeatureFlag);
    } else {
      disabled_features.push_back(kTestFeatureFlag);
    }

    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
  }

  FakeHostStatusProvider* fake_host_status_provider() {
    return fake_host_status_provider_.get();
  }

  device_sync::FakeDeviceSyncClient* fake_device_sync_client() {
    return fake_device_sync_client_.get();
  }

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

  GlobalStateFeatureManager* delegate() { return delegate_.get(); }

  sync_preferences::TestingPrefServiceSyncable* test_pref_service() {
    return test_pref_service_.get();
  }

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

 private:
  base::test::TaskEnvironment task_environment_;
  multidevice::RemoteDeviceRefList test_devices_;

  std::unique_ptr<FakeHostStatusProvider> fake_host_status_provider_;
  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable>
      test_pref_service_;
  std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;

  raw_ptr<base::MockOneShotTimer, DanglingUntriaged> mock_timer_;

  std::unique_ptr<GlobalStateFeatureManager> delegate_;

  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest, Success) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(test_devices()[0] /* initial_host */);

  // Attempt to enable the feature on host device and succeed
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  SetIsFeatureEnabled(true);
  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_TRUE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);
  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kSuccess,
      false /* expected_to_notify_observer_and_start_retry_timer */);
  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kEnabled);

  // Attempt to disable the feature on host device and succeed
  SetIsFeatureEnabled(false);
  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kEnabled);

  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kSuccess,
      false /* expected_to_notify_observer_and_start_retry_timer */);
  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
  SetHostInDeviceSyncClient(test_devices()[0], false /* enabled */);
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       NewDevicesSyncedBeforeCallback) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(test_devices()[0] /* initial_host */);

  // Attempt to enable the feature on host device and succeed
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  SetIsFeatureEnabled(true);
  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_TRUE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);
  // Triggers OnNewDevicesSynced
  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
  // Triggers Success Callback
  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kSuccess,
      false /* expected_to_notify_observer_and_start_retry_timer */);
  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());

  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kEnabled);
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest, Failure) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(test_devices()[0] /* initial_host */);

  // Attempt to enable the feature on host device and fail
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  SetIsFeatureEnabled(true);
  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_TRUE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kOffline,
      true /* expected_to_notify_observer_and_start_retry_timer */);
  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  // A retry should have been scheduled, so fire the timer to start the retry.
  mock_timer()->Fire();

  // Simulate another failure.
  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_TRUE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kOffline,
      true /* expected_to_notify_observer_and_start_retry_timer */);
  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       MultipleRequests_FirstFail_ThenSucceed) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(test_devices()[0] /* initial_host */);

  // Attempt to enable the feature on host device and fail
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  SetIsFeatureEnabled(true);
  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_TRUE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kOffline,
      true /* expected_to_notify_observer_and_start_retry_timer */);
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  // The retry timer is running; however, instead of relying on that, call
  // SetIsFeatureEnabled() again to trigger an immediate
  // retry without the timer.
  SetIsFeatureEnabled(true);
  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_TRUE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kSuccess,
      false /* expected_to_notify_observer_and_start_retry_timer */);
  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kEnabled);
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       PendingRequest_NoSyncedHostDevice) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(test_devices()[0] /* initial_host */);

  // Attempt to enable the feature on test_device 0
  SetIsFeatureEnabled(true);
  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_TRUE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  // Fail to set host enabled on test_device 0
  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kOffline,
      true /* expected_to_notify_observer_and_start_retry_timer */);
  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);
  EXPECT_TRUE(mock_timer()->IsRunning());

  // Remove synced device. This should remove the pending request and stop the
  // retry timer.
  SetHostInDeviceSyncClient(std::nullopt);
  SetHostWithStatus(std::nullopt);
  EXPECT_FALSE(mock_timer()->IsRunning());
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       InitialPendingEnableRequest_NoInitialDevice) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(std::nullopt /* initial_host */,
                 kPendingEnable /* initial_pending_state*/);

  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       InitialPendingEnableRequest_Success) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(test_devices()[0] /* initial_host */,
                 kPendingEnable /* initial_pending_state*/);

  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_TRUE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kSuccess,
      false /* expected_to_notify_observer_and_start_retry_timer */);
  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kEnabled);
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       MultiplePendingRequests_EnableDisable) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(test_devices()[0] /* initial_host */);

  // Attempt to enable->disable the feature without invoking any
  // callbacks.
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  SetIsFeatureEnabled(true);
  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_TRUE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  // The feature is already disabled on back-end so there should be no new
  // pending request
  SetIsFeatureEnabled(false);
  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       PendingRequest_SyncedHostBecomesUnverified) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(test_devices()[0] /* initial_host */,
                 kPendingEnable /* initial_pending_state */);

  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);

  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
            kPendingNone);
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       Retrying_SyncedHostBecomesUnverified) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(test_devices()[0] /* initial_host */);

  SetIsFeatureEnabled(true);
  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_TRUE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kOffline,
      true /* expected_to_notify_observer_and_start_retry_timer */);
  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_TRUE(mock_timer()->IsRunning());

  // Host becomes unverified, this should stop timer and clear pending request
  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
            kPendingNone);
  EXPECT_FALSE(mock_timer()->IsRunning());
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       FailureCallback_SyncedHostBecomesUnverified) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(test_devices()[0] /* initial_host */);

  SetIsFeatureEnabled(true);
  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_TRUE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  // Set host unverified. This should reset pending request.
  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
            kPendingNone);

  // Invoke failure callback. No retry should be scheduled.
  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kOffline,
      false /* expected_to_notify_observer_and_start_retry_timer */);
  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_FALSE(mock_timer()->IsRunning());
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       NoVerifiedHost_AttemptToEnable) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(test_devices()[0] /* initial_host */);

  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);

  // Attempt to enable the feature on host device
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  SetIsFeatureEnabled(true);
  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       StatusChangedOnRemoteDevice) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(test_devices()[0] /* initial_host */);

  EXPECT_FALSE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);

  // Simulate enabled on a remote device.
  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
  EXPECT_TRUE(delegate()->IsFeatureEnabled());
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       SimultaneousRequests_StartOff_ToggleOnOff) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(test_devices()[0] /* initial_host */);

  // Attempt to enable
  SetIsFeatureEnabled(true);
  // Attempt to disable
  SetIsFeatureEnabled(false);

  // Only one network request should be in flight at a time
  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());

  // Successfully enable on host
  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kSuccess,
      false /* expected_to_notify_observer_and_start_retry_timer */);
  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kEnabled);

  // A new network request should be scheduled to disable
  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kSuccess,
      false /* expected_to_notify_observer_and_start_retry_timer */);
  SetHostInDeviceSyncClient(test_devices()[0], false /* enabled */);
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       SetPendingEnableOnVerify_HostSetLocallyThenHostVerified) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(std::nullopt /* initial_host */);

  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
  // setup flow has been completed on the local device.
  SetHostInDeviceSyncClient(test_devices()[0]);
  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
      test_devices()[0]);
  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
            kSetPendingEnableOnVerify);
  EXPECT_FALSE(delegate()->IsFeatureEnabled());

  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kHostVerified, test_devices()[0]);
  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
            kPendingEnable);
  EXPECT_TRUE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);
  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kSuccess,
      false /* expected_to_notify_observer_and_start_retry_timer */);
  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);

  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kEnabled);
}

TEST_P(
    MultiDeviceSetupGlobalStateFeatureManagerImplTest,
    SetPendingEnableOnVerify_HostSetLocallyThenHostSetNotVerifiedThenHostVerified) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(std::nullopt /* initial_host */);

  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
  // setup flow has been completed on the local device.
  SetHostInDeviceSyncClient(test_devices()[0]);
  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
      test_devices()[0]);
  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
            kSetPendingEnableOnVerify);
  EXPECT_FALSE(delegate()->IsFeatureEnabled());

  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
            kSetPendingEnableOnVerify);
  EXPECT_FALSE(delegate()->IsFeatureEnabled());

  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kHostVerified, test_devices()[0]);
  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
            kPendingEnable);
  EXPECT_TRUE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);
  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kSuccess,
      false /* expected_to_notify_observer_and_start_retry_timer */);
  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);

  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kEnabled);
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       SetPendingEnableOnVerify_FeatureFlagOff) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  false /* enable_feature_flag */);
  CreateDelegate(std::nullopt /* initial_host */);

  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
  // setup flow has been completed on the local device.
  SetHostInDeviceSyncClient(test_devices()[0]);
  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
      test_devices()[0]);
  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
            kPendingNone);
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       SetPendingEnableOnVerify_FeatureNotAllowedByPolicy) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  // Disable by policy
  test_pref_service()->SetBoolean(kFeatureAllowedPrefName, false);
  CreateDelegate(std::nullopt /* initial_host */);

  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
  // setup flow has been completed on the local device.
  SetHostInDeviceSyncClient(test_devices()[0]);
  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
      test_devices()[0]);
  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
            kPendingNone);
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       SetPendingEnableOnVerify_FeatureNotSupportedOnHostDevice) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(std::nullopt /* initial_host */);
  GetMutableRemoteDevice(test_devices()[0])
      ->software_features[kTestHostFeature] =
      multidevice::SoftwareFeatureState::kNotSupported;

  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
  // setup flow has been completed on the local device.
  SetHostInDeviceSyncClient(test_devices()[0]);
  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
      test_devices()[0]);
  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
            kPendingNone);
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       SetPendingEnableOnVerify_HostRemoved) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  CreateDelegate(std::nullopt /* initial_host */);

  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
  // setup flow has been completed on the local device.
  SetHostInDeviceSyncClient(test_devices()[0]);
  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
      test_devices()[0]);
  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
            kSetPendingEnableOnVerify);
  EXPECT_FALSE(delegate()->IsFeatureEnabled());

  // Host is added but not verified.
  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
            kSetPendingEnableOnVerify);
  EXPECT_FALSE(delegate()->IsFeatureEnabled());

  // Host is removed before it was verified. This simulates the user going
  // through the forget phone flow before the phone was able to be verified.
  // The feature manager should stop the enable attempt because it requires a
  // paired host device that transitions from unverified to verified.
  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kEligibleHostExistsButNoHostSet, std::nullopt);
  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
            kPendingNone);
  EXPECT_FALSE(delegate()->IsFeatureEnabled());
}

TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
       SetPendingEnableOnVerify_InitialPendingRequest) {
  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
                  true /* enable_feature_flag */);
  fake_host_status_provider()->SetHostWithStatus(
      mojom::HostStatus::kHostVerified, test_devices()[0]);
  CreateDelegate(test_devices()[0] /* initial_host */,
                 kSetPendingEnableOnVerify /* initial_pending_state */);

  EXPECT_TRUE(delegate()->IsFeatureEnabled());
  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kSupported);
  InvokePendingSetHostNetworkRequestCallback(
      device_sync::mojom::NetworkRequestResult::kSuccess,
      false /* expected_to_notify_observer_and_start_retry_timer */);
  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);

  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
            multidevice::SoftwareFeatureState::kEnabled);
}

// Runs tests twice; once with v1 DeviceSync enabled and once with it disabled.
// TODO(crbug.com/40105247): Remove when v1 DeviceSync is disabled,
// when all devices should have an Instance ID.
INSTANTIATE_TEST_SUITE_P(All,
                         MultiDeviceSetupGlobalStateFeatureManagerImplTest,
                         ::testing::Bool());

}  // namespace multidevice_setup

}  // namespace ash