chromium/chromeos/ash/components/phonehub/connection_scheduler_impl_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/connection_scheduler_impl.h"

#include <memory>

#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chromeos/ash/components/phonehub/fake_feature_status_provider.h"
#include "chromeos/ash/components/phonehub/feature_status.h"
#include "chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_connection_manager.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace phonehub {

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

  void SetUp() override {
    PhoneHubStructuredMetricsLogger::RegisterPrefs(pref_service_.registry());
    phone_hub_structured_metrics_logger_ =
        std::make_unique<PhoneHubStructuredMetricsLogger>(&pref_service_);
    fake_connection_manager_ =
        std::make_unique<secure_channel::FakeConnectionManager>();
    fake_feature_status_provider_ =
        std::make_unique<FakeFeatureStatusProvider>();
  }

  void CreateConnectionScheduler() {
    connection_scheduler_ = std::make_unique<ConnectionSchedulerImpl>(
        fake_connection_manager_.get(), fake_feature_status_provider_.get(),
        phone_hub_structured_metrics_logger_.get());
  }

  base::TimeDelta GetCurrentBackoffDelay() {
    return connection_scheduler_->GetCurrentBackoffDelayTimeForTesting();
  }

  int GetBackoffFailureCount() {
    return connection_scheduler_->GetBackoffFailureCountForTesting();
  }

  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  std::unique_ptr<secure_channel::FakeConnectionManager>
      fake_connection_manager_;
  std::unique_ptr<FakeFeatureStatusProvider> fake_feature_status_provider_;
  std::unique_ptr<PhoneHubStructuredMetricsLogger>
      phone_hub_structured_metrics_logger_;
  std::unique_ptr<ConnectionSchedulerImpl> connection_scheduler_;
  TestingPrefServiceSimple pref_service_;
};

TEST_F(ConnectionSchedulerImplTest, SuccesssfullyAttemptConnection) {
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledButDisconnected);
  CreateConnectionScheduler();

  connection_scheduler_->ScheduleConnectionNow(
      DiscoveryEntryPoint::kUserSignIn);
  // Verify that the ConnectionManager has attempted to connect.
  EXPECT_EQ(1u, fake_connection_manager_->num_attempt_connection_calls());

  // Simulate state changes with AttemptConnection().
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledAndConnecting);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kEnabledAndConnected);
  // Verify only 1 call to AttemptConnection() was ever made.
  EXPECT_EQ(1u, fake_connection_manager_->num_attempt_connection_calls());
  // Verify that we did not attempt a backoff retry.
  EXPECT_EQ(0, GetBackoffFailureCount());
}

TEST_F(ConnectionSchedulerImplTest, FeatureDisabledDoesNotEstablishConnection) {
  fake_feature_status_provider_->SetStatus(FeatureStatus::kDisabled);
  CreateConnectionScheduler();

  connection_scheduler_->ScheduleConnectionNow(
      DiscoveryEntryPoint::kUserSignIn);
  // Verify that the ConnectionManager did not attempt connection.
  EXPECT_EQ(0u, fake_connection_manager_->num_attempt_connection_calls());
  // Verify that we did not attempt a backoff retry.
  EXPECT_EQ(0, GetBackoffFailureCount());
}

TEST_F(ConnectionSchedulerImplTest, BackoffRetryWithUpdatedConnection) {
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledButDisconnected);
  CreateConnectionScheduler();

  connection_scheduler_->ScheduleConnectionNow(
      DiscoveryEntryPoint::kUserSignIn);
  EXPECT_EQ(1u, fake_connection_manager_->num_attempt_connection_calls());
  // Simulate state changes with AttemptConnection().
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledAndConnecting);
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledButDisconnected);
  EXPECT_EQ(1, GetBackoffFailureCount());

  // Move forward time to the next backoff retry with disconnected status.
  task_environment_.FastForwardBy(GetCurrentBackoffDelay());
  EXPECT_EQ(2u, fake_connection_manager_->num_attempt_connection_calls());
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledAndConnecting);
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledButDisconnected);
  EXPECT_EQ(2, GetBackoffFailureCount());

  // Move forward time to the next backoff retry, this time with connected
  // status.
  task_environment_.FastForwardBy(GetCurrentBackoffDelay());
  EXPECT_EQ(3u, fake_connection_manager_->num_attempt_connection_calls());
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledAndConnecting);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kEnabledAndConnected);
  // Expected no more backoff failures since connection is now established.
  EXPECT_EQ(0, GetBackoffFailureCount());

  // Fast forward time and confirm no other retries have been made.
  task_environment_.FastForwardBy(base::Seconds(100));
  EXPECT_EQ(3u, fake_connection_manager_->num_attempt_connection_calls());
  EXPECT_EQ(0, GetBackoffFailureCount());
  EXPECT_EQ(FeatureStatus::kEnabledAndConnected,
            fake_feature_status_provider_->GetStatus());
}

TEST_F(ConnectionSchedulerImplTest, BackoffRetryWithUpdatedFeatures) {
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledButDisconnected);
  CreateConnectionScheduler();

  connection_scheduler_->ScheduleConnectionNow(
      DiscoveryEntryPoint::kUserSignIn);
  EXPECT_EQ(1u, fake_connection_manager_->num_attempt_connection_calls());
  // Simulate state changes with AttemptConnection().
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledAndConnecting);
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledButDisconnected);
  EXPECT_EQ(1, GetBackoffFailureCount());

  // Simulate the feature status switched to disabled.
  fake_feature_status_provider_->SetStatus(FeatureStatus::kDisabled);
  // Expect the backoff to reset and never attempt to kickoff another
  // connection.
  EXPECT_EQ(0, GetBackoffFailureCount());
  EXPECT_EQ(1u, fake_connection_manager_->num_attempt_connection_calls());
  // Expect that connection has been disconnected.
  EXPECT_EQ(1u, fake_connection_manager_->num_disconnect_calls());

  // Fast forward time and confirm no other retries have been made.
  task_environment_.FastForwardBy(base::Seconds(100));
  EXPECT_EQ(0, GetBackoffFailureCount());
  EXPECT_EQ(1u, fake_connection_manager_->num_attempt_connection_calls());

  // Simulate the feature re-enabled and the connection kickoff should start.
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledButDisconnected);
  // The next ScheduleConnection() was not caused by a previous failure, expect
  // backoff failure count to not increase.
  EXPECT_EQ(0, GetBackoffFailureCount());

  // Move forward in time and confirm backoff attempted another retry.
  task_environment_.FastForwardBy(GetCurrentBackoffDelay());
  EXPECT_EQ(2u, fake_connection_manager_->num_attempt_connection_calls());
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledAndConnecting);
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledButDisconnected);
  // The next ScheduleConnection() was caused by a previous failure, expect 1
  // failure count.
  EXPECT_EQ(1, GetBackoffFailureCount());
}

TEST_F(ConnectionSchedulerImplTest, ScheduleConnectionSuspended) {
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledButDisconnected);
  CreateConnectionScheduler();

  // Simulate screen locked and expect no scheduled connections.
  fake_feature_status_provider_->SetStatus(FeatureStatus::kLockOrSuspended);
  // Expect no scheduled connections on screen lock.
  EXPECT_EQ(0, GetBackoffFailureCount());
  EXPECT_EQ(0u, fake_connection_manager_->num_attempt_connection_calls());
  EXPECT_EQ(1u, fake_connection_manager_->num_disconnect_calls());

  // Simulate screen unlocked and expect a scheduled connection.
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledButDisconnected);
  EXPECT_EQ(0, GetBackoffFailureCount());
  EXPECT_EQ(1u, fake_connection_manager_->num_attempt_connection_calls());
}

TEST_F(ConnectionSchedulerImplTest, HostsNotEligible) {
  // Simulate no eligible hosts available. Expect no scheduled connections.
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kNotEligibleForFeature);
  CreateConnectionScheduler();

  EXPECT_EQ(0, GetBackoffFailureCount());
  EXPECT_EQ(0u, fake_connection_manager_->num_attempt_connection_calls());

  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kEnabledButDisconnected);
  // Flip to have eligble hosts available. Expect a scheduled connection.
  EXPECT_EQ(0, GetBackoffFailureCount());
  EXPECT_EQ(1u, fake_connection_manager_->num_attempt_connection_calls());
}

}  // namespace phonehub
}  // namespace ash