chromium/chromeos/ash/components/phonehub/cros_state_sender_unittest.cc

// Copyright 2020 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/components/phonehub/cros_state_sender.h"

#include <memory>

#include "ash/constants/ash_features.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "base/timer/mock_timer.h"
#include "chromeos/ash/components/phonehub/fake_attestation_certificate_generator.h"
#include "chromeos/ash/components/phonehub/fake_message_sender.h"
#include "chromeos/ash/components/phonehub/mutable_phone_model.h"
#include "chromeos/ash/components/phonehub/phone_model_test_util.h"
#include "chromeos/ash/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_connection_manager.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace phonehub {

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

class CrosStateSenderTest : public testing::Test {
 protected:
  CrosStateSenderTest() = default;
  ~CrosStateSenderTest() override = default;

  CrosStateSenderTest(const CrosStateSender&) = delete;
  CrosStateSenderTest& operator=(const CrosStateSender&) = delete;

  // testing::Test:
  void SetUp() override {
    auto timer = std::make_unique<base::MockOneShotTimer>();
    mock_timer_ = timer.get();

    fake_message_sender_ = std::make_unique<FakeMessageSender>();
    fake_connection_manager_ =
        std::make_unique<secure_channel::FakeConnectionManager>();
    fake_multidevice_setup_client_ =
        std::make_unique<multidevice_setup::FakeMultiDeviceSetupClient>();
    phone_model_ = std::make_unique<MutablePhoneModel>();
    auto fake_attestation_certificate_generator =
        std::make_unique<FakeAttestationCertificateGenerator>();
    fake_attestation_certificate_generator_ =
        fake_attestation_certificate_generator.get();
    cros_state_sender_ = base::WrapUnique(new CrosStateSender(
        fake_message_sender_.get(), fake_connection_manager_.get(),
        fake_multidevice_setup_client_.get(), phone_model_.get(),
        std::move(timer), std::move(fake_attestation_certificate_generator)));
  }

  base::TimeDelta GetRetryDelay() { return cros_state_sender_->retry_delay_; }
  base::test::ScopedFeatureList feature_list_;
  std::unique_ptr<FakeMessageSender> fake_message_sender_;
  std::unique_ptr<secure_channel::FakeConnectionManager>
      fake_connection_manager_;
  std::unique_ptr<multidevice_setup::FakeMultiDeviceSetupClient>
      fake_multidevice_setup_client_;
  std::unique_ptr<MutablePhoneModel> phone_model_;
  raw_ptr<FakeAttestationCertificateGenerator, DanglingUntriaged>
      fake_attestation_certificate_generator_;
  raw_ptr<base::MockOneShotTimer, DanglingUntriaged> mock_timer_;

 private:
  std::unique_ptr<CrosStateSender> cros_state_sender_;
};

TEST_F(CrosStateSenderTest, PerformUpdateCrosStateRetrySequence) {
  fake_connection_manager_->SetStatus(
      secure_channel::ConnectionManager::Status::kConnected);
  EXPECT_EQ(1u, fake_message_sender_->GetCrosStateCallCount());

  // The retry time follows a doubling sequence.
  EXPECT_EQ(base::Seconds(15u), GetRetryDelay());
  mock_timer_->Fire();
  EXPECT_TRUE(mock_timer_->IsRunning());
  EXPECT_EQ(2u, fake_message_sender_->GetCrosStateCallCount());

  EXPECT_EQ(base::Seconds(30u), GetRetryDelay());
  mock_timer_->Fire();
  EXPECT_TRUE(mock_timer_->IsRunning());
  EXPECT_EQ(3u, fake_message_sender_->GetCrosStateCallCount());

  EXPECT_EQ(base::Seconds(60u), GetRetryDelay());
  mock_timer_->Fire();
  EXPECT_TRUE(mock_timer_->IsRunning());
  EXPECT_EQ(4u, fake_message_sender_->GetCrosStateCallCount());

  // The phone model becomes populated, stops retrying.
  EXPECT_EQ(base::Seconds(120u), GetRetryDelay());
  phone_model_->SetPhoneStatusModel(CreateFakePhoneStatusModel());
  mock_timer_->Fire();
  EXPECT_FALSE(mock_timer_->IsRunning());
  EXPECT_EQ(4u, fake_message_sender_->GetCrosStateCallCount());
  EXPECT_EQ(base::Seconds(120u), GetRetryDelay());

  fake_connection_manager_->SetStatus(
      secure_channel::ConnectionManager::Status::kConnecting);
  EXPECT_FALSE(mock_timer_->IsRunning());

  // Cancellation of the retry timer occurs properly when an attempt is
  // reinitiated but the status is not connecting.
  fake_connection_manager_->SetStatus(
      secure_channel::ConnectionManager::Status::kConnected);
  EXPECT_TRUE(mock_timer_->IsRunning());
  fake_connection_manager_->SetStatus(
      secure_channel::ConnectionManager::Status::kConnecting);
  EXPECT_FALSE(mock_timer_->IsRunning());
}

TEST_F(CrosStateSenderTest, UpdatesOnConnected) {
  feature_list_.InitWithFeatures(/* enabled_features= */ {},
                                 /* disabled_features= */ {features::kEcheSWA});
  // Set notification feature to be enabled.
  fake_multidevice_setup_client_->SetFeatureState(
      Feature::kPhoneHubNotifications, FeatureState::kEnabledByUser);
  // Set camera roll feature to be enabled.
  fake_multidevice_setup_client_->SetFeatureState(Feature::kPhoneHubCameraRoll,
                                                  FeatureState::kEnabledByUser);
  // Expect no new messages since connection has not been established.
  EXPECT_EQ(0u, fake_message_sender_->GetCrosStateCallCount());
  EXPECT_FALSE(mock_timer_->IsRunning());

  // Update connection state to connecting.
  fake_connection_manager_->SetStatus(
      secure_channel::ConnectionManager::Status::kConnecting);
  // Connecting state does not trigger a request message.
  EXPECT_EQ(0u, fake_message_sender_->GetCrosStateCallCount());
  EXPECT_FALSE(mock_timer_->IsRunning());

  // Simulate connected state. Expect a new message to be sent.
  fake_connection_manager_->SetStatus(
      secure_channel::ConnectionManager::Status::kConnected);
  EXPECT_TRUE(std::get<0>(fake_message_sender_->GetRecentCrosState()));
  EXPECT_TRUE(std::get<1>(fake_message_sender_->GetRecentCrosState()));
  EXPECT_EQ(nullptr, std::get<2>(fake_message_sender_->GetRecentCrosState()));
  EXPECT_EQ(1u, fake_message_sender_->GetCrosStateCallCount());

  // Phone model is populated.
  phone_model_->SetPhoneStatusModel(CreateFakePhoneStatusModel());
  mock_timer_->Fire();
  EXPECT_EQ(1u, fake_message_sender_->GetCrosStateCallCount());

  // Simulate disconnected state, this should not trigger a new request.
  fake_connection_manager_->SetStatus(
      secure_channel::ConnectionManager::Status::kDisconnected);
  EXPECT_TRUE(std::get<0>(fake_message_sender_->GetRecentCrosState()));
  EXPECT_TRUE(std::get<1>(fake_message_sender_->GetRecentCrosState()));
  EXPECT_EQ(1u, fake_message_sender_->GetCrosStateCallCount());
  EXPECT_FALSE(mock_timer_->IsRunning());
}

TEST_F(CrosStateSenderTest, CrosStateMessageIncludesAttestationIfEcheEnabled) {
  feature_list_.InitWithFeatures(/* enabled_features= */ {features::kEcheSWA},
                                 /* disabled_features= */ {});
  // Set notification feature to be enabled.
  fake_multidevice_setup_client_->SetFeatureState(
      Feature::kPhoneHubNotifications, FeatureState::kEnabledByUser);
  // Set camera roll feature to be enabled.
  fake_multidevice_setup_client_->SetFeatureState(Feature::kPhoneHubCameraRoll,
                                                  FeatureState::kEnabledByUser);
  fake_connection_manager_->SetStatus(
      secure_channel::ConnectionManager::Status::kConnected);
  EXPECT_TRUE(std::get<0>(fake_message_sender_->GetRecentCrosState()));
  EXPECT_TRUE(std::get<1>(fake_message_sender_->GetRecentCrosState()));
  EXPECT_EQ(&(fake_attestation_certificate_generator_->CERTS),
            std::get<2>(fake_message_sender_->GetRecentCrosState()));
  EXPECT_EQ(1u, fake_message_sender_->GetCrosStateCallCount());
}

TEST_F(CrosStateSenderTest, ResendOnNewAttestationCertificate) {
  feature_list_.InitWithFeatures(/* enabled_features= */ {features::kEcheSWA},
                                 /* disabled_features= */ {});
  // Set notification feature to be enabled.
  fake_multidevice_setup_client_->SetFeatureState(
      Feature::kPhoneHubNotifications, FeatureState::kEnabledByUser);
  // Set camera roll feature to be enabled.
  fake_multidevice_setup_client_->SetFeatureState(Feature::kPhoneHubCameraRoll,
                                                  FeatureState::kEnabledByUser);
  fake_connection_manager_->SetStatus(
      secure_channel::ConnectionManager::Status::kConnected);
  EXPECT_EQ(1u, fake_message_sender_->GetCrosStateCallCount());

  fake_attestation_certificate_generator_->RetrieveCertificate();
  EXPECT_EQ(2u, fake_message_sender_->GetCrosStateCallCount());
}

TEST_F(CrosStateSenderTest, NotificationFeatureStateChanged) {
  // Set connection state to be connected.
  fake_connection_manager_->SetStatus(
      secure_channel::ConnectionManager::Status::kConnected);

  // Phone model is populated.
  phone_model_->SetPhoneStatusModel(CreateFakePhoneStatusModel());
  EXPECT_TRUE(mock_timer_->IsRunning());

  // Expect new messages to be sent when connection state is connected.
  EXPECT_FALSE(std::get<0>(fake_message_sender_->GetRecentCrosState()));
  EXPECT_FALSE(std::get<1>(fake_message_sender_->GetRecentCrosState()));
  EXPECT_EQ(1u, fake_message_sender_->GetCrosStateCallCount());
  mock_timer_->Fire();

  // Simulate enabling notification feature state and expect cros state to be
  // enabled.
  fake_multidevice_setup_client_->SetFeatureState(
      Feature::kPhoneHubNotifications, FeatureState::kEnabledByUser);
  EXPECT_TRUE(std::get<0>(fake_message_sender_->GetRecentCrosState()));
  EXPECT_EQ(2u, fake_message_sender_->GetCrosStateCallCount());
  mock_timer_->Fire();

  // Update a different feature state and expect that it did not affect the
  // cros state.
  fake_multidevice_setup_client_->SetFeatureState(
      Feature::kSmartLock, FeatureState::kDisabledByUser);
  EXPECT_TRUE(std::get<0>(fake_message_sender_->GetRecentCrosState()));
  EXPECT_EQ(3u, fake_message_sender_->GetCrosStateCallCount());
  mock_timer_->Fire();

  // Simulate disabling notification feature state and expect cros state to be
  // disabled.
  fake_multidevice_setup_client_->SetFeatureState(
      Feature::kPhoneHubNotifications, FeatureState::kDisabledByUser);
  EXPECT_FALSE(std::get<0>(fake_message_sender_->GetRecentCrosState()));
  EXPECT_EQ(4u, fake_message_sender_->GetCrosStateCallCount());

  // Simulate enabling camera roll feature state and expect cros state to be
  // updated.
  fake_multidevice_setup_client_->SetFeatureState(Feature::kPhoneHubCameraRoll,
                                                  FeatureState::kEnabledByUser);
  EXPECT_TRUE(std::get<1>(fake_message_sender_->GetRecentCrosState()));
  EXPECT_EQ(5u, fake_message_sender_->GetCrosStateCallCount());

  // Firing the timer does not cause the cros state to be sent again.
  mock_timer_->Fire();
  EXPECT_EQ(5u, fake_message_sender_->GetCrosStateCallCount());
}

}  // namespace phonehub
}  // namespace ash