chromium/ash/webui/eche_app_ui/apps_access_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 "ash/webui/eche_app_ui/apps_access_manager_impl.h"

#include "ash/constants/ash_features.h"
#include "ash/webui/eche_app_ui/apps_access_setup_operation.h"
#include "ash/webui/eche_app_ui/fake_eche_connector.h"
#include "ash/webui/eche_app_ui/fake_eche_message_receiver.h"
#include "ash/webui/eche_app_ui/fake_feature_status_provider.h"
#include "ash/webui/eche_app_ui/pref_names.h"
#include "ash/webui/eche_app_ui/proto/exo_messages.pb.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/ash/components/phonehub/multidevice_feature_access_manager.h"
#include "chromeos/ash/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
#include "chromeos/ash/services/multidevice_setup/public/cpp/prefs.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_connection_manager.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace eche_app {

using AccessStatus =
    ash::phonehub::MultideviceFeatureAccessManager::AccessStatus;

using OnboardingUserActionMetric =
    AppsAccessManagerImpl::OnboardingUserActionMetric;

namespace {
class FakeObserver : public AppsAccessManager::Observer {
 public:
  FakeObserver() = default;
  ~FakeObserver() override = default;

  size_t num_calls() const { return num_calls_; }

  // AppsAccessManager::Observer:
  void OnAppsAccessChanged() override { ++num_calls_; }

 private:
  size_t num_calls_ = 0;
};

class FakeOperationDelegate : public AppsAccessSetupOperation::Delegate {
 public:
  FakeOperationDelegate() = default;
  ~FakeOperationDelegate() override = default;

  AppsAccessSetupOperation::Status status() const { return status_; }

  // AppsAccessSetupOperation::Delegate:
  void OnAppsStatusChange(
      AppsAccessSetupOperation::Status new_status) override {
    status_ = new_status;
  }

 private:
  AppsAccessSetupOperation::Status status_ =
      AppsAccessSetupOperation::Status::kConnecting;
};
}  // namespace

using multidevice_setup::mojom::Feature;
using multidevice_setup::mojom::FeatureState;

class AppsAccessManagerImplTest : public testing::Test {
 protected:
  AppsAccessManagerImplTest() = default;
  AppsAccessManagerImplTest(const AppsAccessManagerImplTest&) = delete;
  AppsAccessManagerImplTest& operator=(const AppsAccessManagerImplTest&) =
      delete;
  ~AppsAccessManagerImplTest() override = default;

  void SetUp() override {
    AppsAccessManagerImpl::RegisterPrefs(pref_service_.registry());
    multidevice_setup::RegisterFeaturePrefs(pref_service_.registry());

    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kEcheSWA},
        /*disabled_features=*/{});

    fake_eche_connector_ = std::make_unique<FakeEcheConnector>();
    fake_eche_message_receiver_ = std::make_unique<FakeEcheMessageReceiver>();
    fake_feature_status_provider_ = std::make_unique<FakeFeatureStatusProvider>(
        FeatureStatus::kDependentFeature);
  }

  void TearDown() override {
    apps_access_manager_->RemoveObserver(&fake_observer_);
    apps_access_manager_.reset();
    fake_eche_connector_.reset();
    fake_eche_message_receiver_.reset();
    fake_feature_status_provider_.reset();
  }

  void Initialize(AccessStatus expected_status) {
    pref_service_.SetInteger(prefs::kAppsAccessStatus,
                             static_cast<int>(expected_status));
    apps_access_manager_ = std::make_unique<AppsAccessManagerImpl>(
        fake_eche_connector_.get(), fake_eche_message_receiver_.get(),
        fake_feature_status_provider_.get(), &pref_service_,
        &fake_multidevice_setup_client_, &fake_connection_manager_);
    apps_access_manager_->AddObserver(&fake_observer_);
  }

  void FakeGetAppsAccessStateResponse(eche_app::proto::Result result,
                                      eche_app::proto::AppsAccessState status) {
    fake_eche_message_receiver_->FakeGetAppsAccessStateResponse(result, status);
  }

  void FakeSendAppsSetupResponse(eche_app::proto::Result result,
                                 eche_app::proto::AppsAccessState status) {
    fake_eche_message_receiver_->FakeSendAppsSetupResponse(result, status);
  }

  void FakeSendAppsPolicyStateChange(
      eche_app::proto::AppStreamingPolicy app_policy_state) {
    fake_eche_message_receiver_->FakeAppPolicyStateChange(app_policy_state);
  }

  void SetFeatureStatus(FeatureStatus status) {
    fake_feature_status_provider_->SetStatus(status);
  }

  FeatureStatus GetFeatureStatus() {
    return fake_feature_status_provider_->GetStatus();
  }

  void SetConnectionStatus(secure_channel::ConnectionManager::Status status) {
    fake_connection_manager_.SetStatus(status);
  }

  void VerifyAppsAccessGrantedState(AccessStatus expected_status) {
    EXPECT_EQ(static_cast<int>(expected_status),
              pref_service_.GetInteger(prefs::kAppsAccessStatus));
    EXPECT_EQ(expected_status, apps_access_manager_->GetAccessStatus());
  }

  AppsAccessSetupOperation::Status GetAppsAccessSetupOperationStatus() {
    return fake_delegate_.status();
  }

  std::unique_ptr<AppsAccessSetupOperation> StartSetupOperation() {
    return apps_access_manager_->AttemptAppsAccessSetup(&fake_delegate_);
  }

  bool IsSetupOperationInProgress() {
    return apps_access_manager_->IsSetupOperationInProgress();
  }

  size_t GetNumObserverCalls() const { return fake_observer_.num_calls(); }

  size_t GetAppsSetupRequestCount() const {
    return fake_eche_connector_->send_apps_setup_request_count();
  }

  size_t GetAppsAccessStateRequestCount() const {
    return fake_eche_connector_->get_apps_access_state_request_count();
  }

  size_t GetAttemptNearbyConnectionCount() const {
    return fake_eche_connector_->attempt_nearby_connection_count();
  }

  void SetFeatureState(Feature feature, FeatureState feature_state) {
    fake_multidevice_setup_client_.SetFeatureState(feature, feature_state);
  }

  void NotifyAppsAccessCanceled() {
    return apps_access_manager_->NotifyAppsAccessCanceled();
  }

  void SetFeatureEnabledState(const std::string& pref_name, bool enabled) {
    pref_service_.SetBoolean(pref_name, enabled);
  }

  multidevice_setup::FakeMultiDeviceSetupClient*
  fake_multidevice_setup_client() {
    return &fake_multidevice_setup_client_;
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  FakeObserver fake_observer_;
  FakeOperationDelegate fake_delegate_;
  TestingPrefServiceSimple pref_service_;
  std::unique_ptr<AppsAccessManager> apps_access_manager_;
  std::unique_ptr<FakeEcheConnector> fake_eche_connector_;
  std::unique_ptr<FakeEcheMessageReceiver> fake_eche_message_receiver_;
  std::unique_ptr<FakeFeatureStatusProvider> fake_feature_status_provider_;
  secure_channel::FakeConnectionManager fake_connection_manager_;
  multidevice_setup::FakeMultiDeviceSetupClient fake_multidevice_setup_client_;
};

TEST_F(AppsAccessManagerImplTest, InitiallyGranted) {
  Initialize(AccessStatus::kAccessGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAccessGranted);

  // Cannot start the apps access setup flow if access has already been
  // granted.
  auto operation = StartSetupOperation();
  EXPECT_FALSE(operation);
}

TEST_F(AppsAccessManagerImplTest, OnFeatureStatusChanged) {
  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  // Set initial state to kIneligible.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kDisconnected);
  SetFeatureStatus(FeatureStatus::kIneligible);
  EXPECT_EQ(0u, GetAttemptNearbyConnectionCount());
  EXPECT_EQ(0u, GetAppsAccessStateRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::kConnecting,
            GetAppsAccessSetupOperationStatus());

  //  Simulate feature status to be enabled and disconnected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kDisconnected);
  SetFeatureStatus(FeatureStatus::kDisconnected);
  EXPECT_EQ(1u, GetAttemptNearbyConnectionCount());
  EXPECT_EQ(0u, GetAppsAccessStateRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::kConnecting,
            GetAppsAccessSetupOperationStatus());

  // Simulate feature status to be enabled and connected. SetupOperation is also
  // not in progress, so expect no new requests to be sent.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  SetFeatureStatus(FeatureStatus::kConnected);
  EXPECT_EQ(1u, GetAppsAccessStateRequestCount());
  EXPECT_EQ(0u, GetAppsSetupRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::kConnecting,
            GetAppsAccessSetupOperationStatus());

  // Simulate setup operation is in progress. This will trigger a sent request.
  auto operation = StartSetupOperation();
  EXPECT_TRUE(operation);
  EXPECT_EQ(1u, GetAppsSetupRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::
                kSentMessageToPhoneAndWaitingForResponse,
            GetAppsAccessSetupOperationStatus());
  EXPECT_TRUE(IsSetupOperationInProgress());

  // Set another feature status, expect status to be updated.
  SetFeatureStatus(FeatureStatus::kIneligible);
  SetFeatureStatus(FeatureStatus::kConnected);
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnecting);
  SetFeatureStatus(FeatureStatus::kConnecting);
  EXPECT_EQ(2u, GetAppsSetupRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::kConnectionDisconnected,
            GetAppsAccessSetupOperationStatus());
  EXPECT_FALSE(IsSetupOperationInProgress());

  auto operation1 = StartSetupOperation();
  EXPECT_TRUE(operation1);
  EXPECT_EQ(2u, GetAppsSetupRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::kConnecting,
            GetAppsAccessSetupOperationStatus());
  EXPECT_TRUE(IsSetupOperationInProgress());

  SetFeatureStatus(FeatureStatus::kDisconnected);
  EXPECT_EQ(2u, GetAppsSetupRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::kTimedOutConnecting,
            GetAppsAccessSetupOperationStatus());
  EXPECT_FALSE(IsSetupOperationInProgress());
}

TEST_F(AppsAccessManagerImplTest, StartDisconnectedAndNoAccess) {
  // Set initial state to disconnected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kDisconnected);
  SetFeatureStatus(FeatureStatus::kDisconnected);

  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  // Start a setup operation with enabled but disconnected status and access
  // not granted.
  auto operation = StartSetupOperation();
  EXPECT_TRUE(operation);
  EXPECT_EQ(1u, GetAttemptNearbyConnectionCount());

  // Simulate changing states from connecting to connected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnecting);
  SetFeatureStatus(FeatureStatus::kConnecting);
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  SetFeatureStatus(FeatureStatus::kConnected);

  // Verify that the request message has been sent and our operation status
  // is updated.
  EXPECT_EQ(1u, GetAppsSetupRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::
                kSentMessageToPhoneAndWaitingForResponse,
            GetAppsAccessSetupOperationStatus());
  EXPECT_TRUE(IsSetupOperationInProgress());

  // Simulate getting a response back from the phone.
  FakeSendAppsSetupResponse(eche_app::proto::Result::RESULT_NO_ERROR,
                            eche_app::proto::AppsAccessState::ACCESS_GRANTED);
  VerifyAppsAccessGrantedState(AccessStatus::kAccessGranted);
  EXPECT_EQ(AppsAccessSetupOperation::Status::kCompletedSuccessfully,
            GetAppsAccessSetupOperationStatus());
  EXPECT_FALSE(IsSetupOperationInProgress());
}

TEST_F(AppsAccessManagerImplTest, StartConnectingAndNoAccess) {
  base::HistogramTester histograms;

  // Set initial state to connecting.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnecting);
  SetFeatureStatus(FeatureStatus::kConnecting);

  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  // Start a setup operation with enabled and connecting status and access
  // not granted.
  auto operation = StartSetupOperation();
  EXPECT_TRUE(operation);

  // Simulate changing states from connecting to connected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  SetFeatureStatus(FeatureStatus::kConnected);

  // Verify that the request message has been sent and our operation status
  // is updated.
  EXPECT_EQ(1u, GetAppsSetupRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::
                kSentMessageToPhoneAndWaitingForResponse,
            GetAppsAccessSetupOperationStatus());
  EXPECT_TRUE(IsSetupOperationInProgress());

  // Simulate getting a response back from the phone.
  FakeSendAppsSetupResponse(eche_app::proto::Result::RESULT_NO_ERROR,
                            eche_app::proto::AppsAccessState::ACCESS_GRANTED);
  VerifyAppsAccessGrantedState(AccessStatus::kAccessGranted);
  EXPECT_EQ(AppsAccessSetupOperation::Status::kCompletedSuccessfully,
            GetAppsAccessSetupOperationStatus());
  EXPECT_FALSE(IsSetupOperationInProgress());
  histograms.ExpectBucketCount(
      kEcheOnboardingHistogramName,
      OnboardingUserActionMetric::kUserActionPermissionGranted, 1);
}

TEST_F(AppsAccessManagerImplTest, StartConnectedAndNoAccess) {
  // Set initial state to connected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  SetFeatureStatus(FeatureStatus::kConnected);

  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  // Start a setup operation with enabled and connected status and access
  // not granted.
  auto operation = StartSetupOperation();
  EXPECT_TRUE(operation);

  // Verify that the request message has been sent and our operation status
  // is updated.
  EXPECT_EQ(1u, GetAppsSetupRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::
                kSentMessageToPhoneAndWaitingForResponse,
            GetAppsAccessSetupOperationStatus());

  // Simulate getting a response back from the phone.
  FakeSendAppsSetupResponse(eche_app::proto::Result::RESULT_NO_ERROR,
                            eche_app::proto::AppsAccessState::ACCESS_GRANTED);
  VerifyAppsAccessGrantedState(AccessStatus::kAccessGranted);
  EXPECT_EQ(AppsAccessSetupOperation::Status::kCompletedSuccessfully,
            GetAppsAccessSetupOperationStatus());
  EXPECT_FALSE(IsSetupOperationInProgress());
}

TEST_F(AppsAccessManagerImplTest, SimulateUserRejectedError) {
  base::HistogramTester histograms;

  // Set initial state to connected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  SetFeatureStatus(FeatureStatus::kConnected);

  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  // Start a setup operation with enabled and connected status and access
  // not granted.
  auto operation = StartSetupOperation();
  EXPECT_TRUE(operation);

  // Verify that the request message has been sent and our operation status
  // is updated.
  EXPECT_EQ(1u, GetAppsSetupRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::
                kSentMessageToPhoneAndWaitingForResponse,
            GetAppsAccessSetupOperationStatus());

  // Simulate getting a response back from the phone.
  FakeSendAppsSetupResponse(
      eche_app::proto::Result::RESULT_ERROR_USER_REJECTED,
      eche_app::proto::AppsAccessState::ACCESS_NOT_GRANTED);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);
  EXPECT_EQ(AppsAccessSetupOperation::Status::kCompletedUserRejected,
            GetAppsAccessSetupOperationStatus());
  EXPECT_FALSE(IsSetupOperationInProgress());
  histograms.ExpectBucketCount(
      kEcheOnboardingHistogramName,
      OnboardingUserActionMetric::kUserActionPermissionRejected, 1);
}

TEST_F(AppsAccessManagerImplTest, SimulateReceivesAppsSetupAck) {
  base::HistogramTester histograms;

  // Set initial state to connected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  SetFeatureStatus(FeatureStatus::kConnected);

  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  // Start a setup operation with enabled and connected status and access
  // not granted.
  auto operation = StartSetupOperation();
  EXPECT_TRUE(operation);

  // Verify that the request message has been sent and our operation status
  // is updated.
  EXPECT_EQ(1u, GetAppsSetupRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::
                kSentMessageToPhoneAndWaitingForResponse,
            GetAppsAccessSetupOperationStatus());

  // Simulate getting a response back from the phone.
  FakeSendAppsSetupResponse(
      eche_app::proto::Result::RESULT_ACK_BY_EXO,
      eche_app::proto::AppsAccessState::ACCESS_NOT_GRANTED);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  EXPECT_TRUE(IsSetupOperationInProgress());
  histograms.ExpectBucketCount(kEcheOnboardingHistogramName,
                               OnboardingUserActionMetric::kAckByExo, 1);
}

TEST_F(AppsAccessManagerImplTest, SimulateOperationFailedOrCanceled) {
  base::HistogramTester histograms;

  // Set initial state to connected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  SetFeatureStatus(FeatureStatus::kConnected);

  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  // Start a setup operation with enabled and connected status and access
  // not granted.
  auto operation = StartSetupOperation();
  EXPECT_TRUE(operation);

  // Verify that the request message has been sent and our operation status
  // is updated.
  EXPECT_EQ(1u, GetAppsSetupRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::
                kSentMessageToPhoneAndWaitingForResponse,
            GetAppsAccessSetupOperationStatus());

  // Simulate getting a response back from the phone.
  FakeSendAppsSetupResponse(
      eche_app::proto::Result::RESULT_ERROR_ACTION_TIMEOUT,
      eche_app::proto::AppsAccessState::ACCESS_NOT_GRANTED);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);
  EXPECT_EQ(AppsAccessSetupOperation::Status::kOperationFailedOrCancelled,
            GetAppsAccessSetupOperationStatus());
  EXPECT_FALSE(IsSetupOperationInProgress());
  histograms.ExpectBucketCount(kEcheOnboardingHistogramName,
                               OnboardingUserActionMetric::kUserActionTimeout,
                               1);

  // Simulate flipping the access state to not granted.
  FakeGetAppsAccessStateResponse(
      eche_app::proto::Result::RESULT_NO_ERROR,
      eche_app::proto::AppsAccessState::ACCESS_NOT_GRANTED);

  // Start a setup operation with enabled and connected status and access
  // not granted.
  auto operation1 = StartSetupOperation();
  EXPECT_TRUE(operation1);

  // Verify that the request message has been sent and our operation status
  // is updated.
  EXPECT_EQ(2u, GetAppsSetupRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::
                kSentMessageToPhoneAndWaitingForResponse,
            GetAppsAccessSetupOperationStatus());

  // Simulate getting a response back from the phone.
  FakeSendAppsSetupResponse(
      eche_app::proto::Result::RESULT_ERROR_ACTION_CANCELED,
      eche_app::proto::AppsAccessState::ACCESS_NOT_GRANTED);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);
  EXPECT_EQ(AppsAccessSetupOperation::Status::kOperationFailedOrCancelled,
            GetAppsAccessSetupOperationStatus());
  EXPECT_FALSE(IsSetupOperationInProgress());
  histograms.ExpectBucketCount(
      kEcheOnboardingHistogramName,
      OnboardingUserActionMetric::kUserActionRemoteInterrupt, 1);

  // Simulate flipping the access state to not granted.
  FakeGetAppsAccessStateResponse(
      eche_app::proto::Result::RESULT_NO_ERROR,
      eche_app::proto::AppsAccessState::ACCESS_NOT_GRANTED);

  // Start a setup operation with enabled and connected status and access
  // not granted.
  auto operation2 = StartSetupOperation();
  EXPECT_TRUE(operation2);

  // Verify that the request message has been sent and our operation status
  // is updated.
  EXPECT_EQ(3u, GetAppsSetupRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::
                kSentMessageToPhoneAndWaitingForResponse,
            GetAppsAccessSetupOperationStatus());

  // Simulate getting a response back from the phone.
  FakeSendAppsSetupResponse(
      eche_app::proto::Result::RESULT_ERROR_SYSTEM,
      eche_app::proto::AppsAccessState::ACCESS_NOT_GRANTED);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);
  EXPECT_EQ(AppsAccessSetupOperation::Status::kOperationFailedOrCancelled,
            GetAppsAccessSetupOperationStatus());
  EXPECT_FALSE(IsSetupOperationInProgress());
  histograms.ExpectBucketCount(kEcheOnboardingHistogramName,
                               OnboardingUserActionMetric::kSystemError, 1);
}

TEST_F(AppsAccessManagerImplTest, SimulateConnectingToDisconnected) {
  // Set initial state to disconnected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kDisconnected);
  SetFeatureStatus(FeatureStatus::kDisconnected);
  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  auto operation = StartSetupOperation();
  EXPECT_TRUE(operation);

  // Simulate a disconnection and expect that status has been updated.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnecting);
  SetFeatureStatus(FeatureStatus::kConnecting);
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kDisconnected);
  SetFeatureStatus(FeatureStatus::kDisconnected);
  EXPECT_EQ(AppsAccessSetupOperation::Status::kTimedOutConnecting,
            GetAppsAccessSetupOperationStatus());
  EXPECT_FALSE(IsSetupOperationInProgress());
}

TEST_F(AppsAccessManagerImplTest, SimulateConnectedToDisconnected) {
  // Set initial state to disconnected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kDisconnected);
  SetFeatureStatus(FeatureStatus::kDisconnected);
  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  auto operation = StartSetupOperation();
  EXPECT_TRUE(operation);

  // Simulate connected state.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  SetFeatureStatus(FeatureStatus::kConnected);
  EXPECT_EQ(1u, GetAppsSetupRequestCount());

  // Simulate a disconnection, expect status update.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kDisconnected);
  SetFeatureStatus(FeatureStatus::kDisconnected);
  EXPECT_EQ(AppsAccessSetupOperation::Status::kConnectionDisconnected,
            GetAppsAccessSetupOperationStatus());
  EXPECT_FALSE(IsSetupOperationInProgress());
}

TEST_F(AppsAccessManagerImplTest, OnConnectionStatusChanged) {
  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  // Simulate disabling the feature and connection is disconnected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kDisconnected);
  SetFeatureStatus(FeatureStatus::kDisabled);

  EXPECT_EQ(1u, GetAttemptNearbyConnectionCount());
  EXPECT_EQ(0u, GetAppsAccessStateRequestCount());

  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  EXPECT_EQ(1u, GetAppsAccessStateRequestCount());

  // Simulate flipping the access state to granted.
  FakeGetAppsAccessStateResponse(
      eche_app::proto::Result::RESULT_NO_ERROR,
      eche_app::proto::AppsAccessState::ACCESS_GRANTED);

  SetConnectionStatus(secure_channel::ConnectionManager::Status::kDisconnected);
  EXPECT_EQ(1u, GetAttemptNearbyConnectionCount());
}

TEST_F(AppsAccessManagerImplTest,
       SimulateDisabledWithDifferentConnectionStatus) {
  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  // Simulate disabling the feature and connection is disconnected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kDisconnected);
  SetFeatureStatus(FeatureStatus::kDisabled);

  auto operation = StartSetupOperation();
  EXPECT_TRUE(operation);

  EXPECT_EQ(AppsAccessSetupOperation::Status::kConnecting,
            GetAppsAccessSetupOperationStatus());
  EXPECT_EQ(2u, GetAttemptNearbyConnectionCount());
  EXPECT_EQ(0u, GetAppsSetupRequestCount());

  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnecting);
  EXPECT_EQ(AppsAccessSetupOperation::Status::kConnecting,
            GetAppsAccessSetupOperationStatus());
  EXPECT_EQ(0u, GetAppsSetupRequestCount());

  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  EXPECT_EQ(AppsAccessSetupOperation::Status::
                kSentMessageToPhoneAndWaitingForResponse,
            GetAppsAccessSetupOperationStatus());
  EXPECT_EQ(1u, GetAppsSetupRequestCount());

  // Simulate getting a response back from the phone.
  FakeSendAppsSetupResponse(eche_app::proto::Result::RESULT_NO_ERROR,
                            eche_app::proto::AppsAccessState::ACCESS_GRANTED);
  VerifyAppsAccessGrantedState(AccessStatus::kAccessGranted);
  EXPECT_EQ(AppsAccessSetupOperation::Status::kCompletedSuccessfully,
            GetAppsAccessSetupOperationStatus());
}

TEST_F(AppsAccessManagerImplTest, SimulateConnectedToDependentFeature) {
  // Set initial state to disconnected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kDisconnected);
  SetFeatureStatus(FeatureStatus::kDisconnected);
  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  auto operation = StartSetupOperation();
  EXPECT_TRUE(operation);

  // Simulate connected state.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  SetFeatureStatus(FeatureStatus::kConnected);
  EXPECT_EQ(1u, GetAppsSetupRequestCount());

  // Simulate disabling the feature, expect status update.
  SetFeatureStatus(FeatureStatus::kDependentFeature);
  EXPECT_EQ(AppsAccessSetupOperation::Status::kConnectionDisconnected,
            GetAppsAccessSetupOperationStatus());
}

TEST_F(AppsAccessManagerImplTest, SimulateConnectedToDependentFeaturePending) {
  // Set initial state to disconnected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kDisconnected);
  SetFeatureStatus(FeatureStatus::kDisconnected);
  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  auto operation = StartSetupOperation();
  EXPECT_TRUE(operation);

  // Simulate connected state.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  SetFeatureStatus(FeatureStatus::kConnected);
  EXPECT_EQ(1u, GetAppsSetupRequestCount());

  // Simulate disabling the feature, expect status update.
  SetFeatureStatus(FeatureStatus::kDependentFeaturePending);
  EXPECT_EQ(AppsAccessSetupOperation::Status::kConnectionDisconnected,
            GetAppsAccessSetupOperationStatus());
}

TEST_F(AppsAccessManagerImplTest, FlipAccessNotGrantedToGranted) {
  SetFeatureState(Feature::kPhoneHub, FeatureState::kEnabledByUser);
  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  // Simulate flipping the access state to granted.
  FakeGetAppsAccessStateResponse(
      eche_app::proto::Result::RESULT_NO_ERROR,
      eche_app::proto::AppsAccessState::ACCESS_GRANTED);

  VerifyAppsAccessGrantedState(AccessStatus::kAccessGranted);
  EXPECT_EQ(1u, GetNumObserverCalls());

  fake_multidevice_setup_client()->InvokePendingSetFeatureEnabledStateCallback(
      /*expected_feature=*/Feature::kEche,
      /*expected_enabled=*/true, /*expected_auth_token=*/std::nullopt,
      /*success=*/true);
}

TEST_F(AppsAccessManagerImplTest, FlipAccessGrantedToNotGranted) {
  SetFeatureState(Feature::kPhoneHub, FeatureState::kEnabledByUser);
  SetFeatureState(Feature::kEche, FeatureState::kDisabledByUser);
  Initialize(AccessStatus::kAccessGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAccessGranted);

  // Simulate flipping the access state to not granted.
  FakeGetAppsAccessStateResponse(
      eche_app::proto::Result::RESULT_NO_ERROR,
      eche_app::proto::AppsAccessState::ACCESS_NOT_GRANTED);

  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);
  EXPECT_EQ(1u, GetNumObserverCalls());
}

TEST_F(AppsAccessManagerImplTest, AccessNotChanged) {
  SetFeatureState(Feature::kPhoneHub, FeatureState::kEnabledByUser);
  SetFeatureState(Feature::kEche, FeatureState::kEnabledByUser);
  Initialize(AccessStatus::kAccessGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAccessGranted);

  // Simulate flipping the access state granted.
  FakeGetAppsAccessStateResponse(
      eche_app::proto::Result::RESULT_NO_ERROR,
      eche_app::proto::AppsAccessState::ACCESS_GRANTED);

  VerifyAppsAccessGrantedState(AccessStatus::kAccessGranted);
  EXPECT_EQ(0u, GetNumObserverCalls());

  fake_multidevice_setup_client()->InvokePendingSetFeatureEnabledStateCallback(
      /*expected_feature=*/Feature::kEche,
      /*expected_enabled=*/true, /*expected_auth_token=*/std::nullopt,
      /*success=*/true);
}

TEST_F(AppsAccessManagerImplTest,
       ShouleNotEnableEcheFeatureWhenPhoneHubIsDisabled) {
  // Explicitly disable Phone Hub, all sub feature should be disabled
  SetFeatureState(Feature::kPhoneHub, FeatureState::kDisabledByUser);
  SetFeatureState(Feature::kEche, FeatureState::kDisabledByUser);
  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  // No action after access is granted
  // Simulate flipping the access state granted.
  FakeGetAppsAccessStateResponse(
      eche_app::proto::Result::RESULT_NO_ERROR,
      eche_app::proto::AppsAccessState::ACCESS_GRANTED);

  EXPECT_EQ(
      0u,
      fake_multidevice_setup_client()->NumPendingSetFeatureEnabledStateCalls());
}

TEST_F(AppsAccessManagerImplTest, InitiallyEnableApps) {
  SetFeatureState(Feature::kPhoneHub, FeatureState::kEnabledByUser);
  Initialize(AccessStatus::kAvailableButNotGranted);

  // Simulate flipping the access state granted.
  FakeGetAppsAccessStateResponse(
      eche_app::proto::Result::RESULT_NO_ERROR,
      eche_app::proto::AppsAccessState::ACCESS_GRANTED);

  // If the kEche feature has not been explicitly set yet, enable it
  // when Phone Hub is enabled and access has been granted.
  fake_multidevice_setup_client()->InvokePendingSetFeatureEnabledStateCallback(
      /*expected_feature=*/Feature::kEche,
      /*expected_enabled=*/true, /*expected_auth_token=*/std::nullopt,
      /*success=*/true);
}

TEST_F(AppsAccessManagerImplTest,
       SimulateAccessNotGrantedShouleDisableEcheFeature) {
  SetFeatureState(Feature::kPhoneHub, FeatureState::kEnabledByUser);
  SetFeatureState(Feature::kEche, FeatureState::kEnabledByUser);
  Initialize(AccessStatus::kAccessGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAccessGranted);

  // Test that there is a call to disable kEche when apps access has been
  // revoked. Simulate flipping the access state to not granted.
  FakeGetAppsAccessStateResponse(
      eche_app::proto::Result::RESULT_NO_ERROR,
      eche_app::proto::AppsAccessState::ACCESS_NOT_GRANTED);

  fake_multidevice_setup_client()->InvokePendingSetFeatureEnabledStateCallback(
      /*expected_feature=*/Feature::kEche,
      /*expected_enabled=*/false, /*expected_auth_token=*/std::nullopt,
      /*success=*/true);
}

TEST_F(AppsAccessManagerImplTest,
       InitiallyEnableEcheFeature_OnlyEnableFromDefaultState) {
  SetFeatureState(Feature::kPhoneHub, FeatureState::kEnabledByUser);
  Initialize(AccessStatus::kAccessGranted);

  // If the Eche feature has not been explicitly set yet, enable it
  // when Phone Hub is enabled and access has been granted.
  FakeGetAppsAccessStateResponse(
      eche_app::proto::Result::RESULT_NO_ERROR,
      eche_app::proto::AppsAccessState::ACCESS_GRANTED);

  fake_multidevice_setup_client()->InvokePendingSetFeatureEnabledStateCallback(
      /*expected_feature=*/Feature::kEche,
      /*expected_enabled=*/true, /*expected_auth_token=*/std::nullopt,
      /*success=*/true);
}

TEST_F(AppsAccessManagerImplTest,
       ShouldNotEnableEcheFeatureIfFeatureIsNotDefaultState) {
  SetFeatureState(Feature::kPhoneHub, FeatureState::kEnabledByUser);

  // Simulate the Eche feature has been changed by user.
  SetFeatureEnabledState(ash::multidevice_setup::kEcheEnabledPrefName, false);
  Initialize(AccessStatus::kAccessGranted);

  // We take no action after access is granted because the Eche feature
  // state was already explicitly set; we respect the user's choice.
  FakeGetAppsAccessStateResponse(
      eche_app::proto::Result::RESULT_NO_ERROR,
      eche_app::proto::AppsAccessState::ACCESS_GRANTED);

  EXPECT_EQ(
      0u,
      fake_multidevice_setup_client()->NumPendingSetFeatureEnabledStateCalls());
}

TEST_F(AppsAccessManagerImplTest,
       ShouleNotEnableEcheFeatureWhenAppsPolicyDisabled) {
  // Explicitly disable Phone Hub, all sub feature should be disabled
  SetFeatureState(Feature::kPhoneHub, FeatureState::kDisabledByUser);
  SetFeatureState(Feature::kEche, FeatureState::kDisabledByUser);
  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  // Simulate flipping the policy state is disabled.
  FakeSendAppsPolicyStateChange(
      eche_app::proto::AppStreamingPolicy::APP_POLICY_DISABLED);
  // No action after access is granted.
  // Simulate flipping the access state granted.
  FakeGetAppsAccessStateResponse(
      eche_app::proto::Result::RESULT_NO_ERROR,
      eche_app::proto::AppsAccessState::ACCESS_GRANTED);

  EXPECT_EQ(
      0u,
      fake_multidevice_setup_client()->NumPendingSetFeatureEnabledStateCalls());
}

TEST_F(AppsAccessManagerImplTest,
       SimulateAppsPolicyDisabledShouldDisableEcheFeature) {
  SetFeatureState(Feature::kPhoneHub, FeatureState::kEnabledByUser);
  SetFeatureState(Feature::kEche, FeatureState::kEnabledByUser);
  Initialize(AccessStatus::kAccessGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAccessGranted);

  // Test that there is a call to disable kEche when apps polcy state has been
  // changed. Simulate flipping the policy state is disabled.
  FakeSendAppsPolicyStateChange(
      eche_app::proto::AppStreamingPolicy::APP_POLICY_DISABLED);

  // No action is taken until get apps access state response is received.
  EXPECT_EQ(0u, GetNumObserverCalls());

  FakeGetAppsAccessStateResponse(
      eche_app::proto::Result::RESULT_NO_ERROR,
      eche_app::proto::AppsAccessState::ACCESS_GRANTED);

  fake_multidevice_setup_client()->InvokePendingSetFeatureEnabledStateCallback(
      /*expected_feature=*/Feature::kEche,
      /*expected_enabled=*/false, /*expected_auth_token=*/std::nullopt,
      /*success=*/true);
  EXPECT_EQ(1u, GetNumObserverCalls());
}

TEST_F(AppsAccessManagerImplTest, LogSetupCancelWhenAppsAccessCanceled) {
  base::HistogramTester histograms;

  // Set initial state to connecting.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnecting);
  SetFeatureStatus(FeatureStatus::kConnecting);

  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  // Start a setup operation with enabled and connecting status and access
  // not granted.
  auto operation = StartSetupOperation();
  EXPECT_TRUE(operation);

  // Simulate changing states from connecting to connected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  SetFeatureStatus(FeatureStatus::kConnected);

  // Verify that the request message has been sent and our operation status
  // is updated.
  EXPECT_EQ(1u, GetAppsSetupRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::
                kSentMessageToPhoneAndWaitingForResponse,
            GetAppsAccessSetupOperationStatus());
  EXPECT_TRUE(IsSetupOperationInProgress());

  // Notify the apps access setup operation is canceled.
  NotifyAppsAccessCanceled();

  // Verify the metric logs the expected event.
  histograms.ExpectBucketCount(kEcheOnboardingHistogramName,
                               OnboardingUserActionMetric::kUserActionCanceled,
                               1);
}

TEST_F(AppsAccessManagerImplTest,
       LogFailConnectionWhenCanceledAndDisconnected) {
  base::HistogramTester histograms;

  // Set initial state to connecting.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnecting);
  SetFeatureStatus(FeatureStatus::kConnecting);

  Initialize(AccessStatus::kAvailableButNotGranted);
  VerifyAppsAccessGrantedState(AccessStatus::kAvailableButNotGranted);

  // Start a setup operation with enabled and connecting status and access
  // not granted.
  auto operation = StartSetupOperation();
  EXPECT_TRUE(operation);

  // Simulate changing states from connecting to connected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  SetFeatureStatus(FeatureStatus::kConnected);

  // Verify that the request message has been sent and our operation status
  // is updated.
  EXPECT_EQ(1u, GetAppsSetupRequestCount());
  EXPECT_EQ(AppsAccessSetupOperation::Status::
                kSentMessageToPhoneAndWaitingForResponse,
            GetAppsAccessSetupOperationStatus());
  EXPECT_TRUE(IsSetupOperationInProgress());

  // Simulate changing states from connected to disconnected.
  SetConnectionStatus(secure_channel::ConnectionManager::Status::kDisconnected);
  SetFeatureStatus(FeatureStatus::kDisconnected);

  // Notify the apps access setup operation is canceled.
  NotifyAppsAccessCanceled();

  // Verify the metric logs the expected event.
  histograms.ExpectBucketCount(kEcheOnboardingHistogramName,
                               OnboardingUserActionMetric::kFailedConnection,
                               1);
}

}  // namespace eche_app
}  // namespace ash