chromium/ash/webui/eche_app_ui/eche_connection_scheduler_impl_unittest.cc

// Copyright 2022 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/eche_connection_scheduler_impl.h"

#include <memory>

#include "ash/webui/eche_app_ui/fake_feature_status_provider.h"
#include "ash/webui/eche_app_ui/feature_status.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_connection_manager.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace eche_app {

using ConnectionStatus = secure_channel::ConnectionManager::Status;

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

  void SetUp() override {
    fake_connection_manager_ =
        std::make_unique<secure_channel::FakeConnectionManager>();
    fake_feature_status_provider_ =
        std::make_unique<FakeFeatureStatusProvider>();
  }

  void CreateConnectionScheduler() {
    connection_scheduler_ = std::make_unique<EcheConnectionSchedulerImpl>(
        fake_connection_manager_.get(), fake_feature_status_provider_.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<EcheConnectionSchedulerImpl> connection_scheduler_;
};

TEST_F(EcheConnectionSchedulerImplTest, SuccesssfullyAttemptConnection) {
  fake_connection_manager_->SetStatus(ConnectionStatus::kDisconnected);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kDisconnected);
  CreateConnectionScheduler();

  connection_scheduler_->ScheduleConnectionNow();
  // 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::kConnecting);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kConnected);
  // Verify only 1 call to AttemptConnection() was ever made.
  EXPECT_EQ(fake_connection_manager_->num_attempt_connection_calls(), 1u);
  // Verify that we did not attempt a backoff retry.
  EXPECT_EQ(GetBackoffFailureCount(), 0);
}

TEST_F(EcheConnectionSchedulerImplTest,
       FeatureDisabledCanEstablishConnectionProperly) {
  fake_connection_manager_->SetStatus(ConnectionStatus::kConnecting);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kDisabled);
  CreateConnectionScheduler();

  connection_scheduler_->ScheduleConnectionNow();
  // 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());

  fake_connection_manager_->SetStatus(ConnectionStatus::kConnected);
  CreateConnectionScheduler();
  // 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());

  fake_connection_manager_->SetStatus(ConnectionStatus::kDisconnected);
  connection_scheduler_->ScheduleConnectionNow();
  // Verify that the ConnectionManager has attempted to connect.
  EXPECT_EQ(fake_connection_manager_->num_attempt_connection_calls(), 1u);
  // Verify that we did not attempt a backoff retry.
  EXPECT_EQ(GetBackoffFailureCount(), 0);

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

TEST_F(EcheConnectionSchedulerImplTest, ShouldNotEstablishConnection) {
  fake_connection_manager_->SetStatus(ConnectionStatus::kDisconnected);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kIneligible);
  CreateConnectionScheduler();

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

  fake_feature_status_provider_->SetStatus(FeatureStatus::kDependentFeature);
  connection_scheduler_->ScheduleConnectionNow();
  EXPECT_EQ(fake_connection_manager_->num_attempt_connection_calls(), 0u);
  EXPECT_EQ(GetBackoffFailureCount(), 0);

  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kDependentFeaturePending);
  connection_scheduler_->ScheduleConnectionNow();
  EXPECT_EQ(fake_connection_manager_->num_attempt_connection_calls(), 0u);
  EXPECT_EQ(GetBackoffFailureCount(), 0);
}

TEST_F(EcheConnectionSchedulerImplTest, BackoffRetryWithUpdatedConnection) {
  fake_connection_manager_->SetStatus(ConnectionStatus::kDisconnected);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kDisconnected);
  CreateConnectionScheduler();

  connection_scheduler_->ScheduleConnectionNow();
  EXPECT_EQ(fake_connection_manager_->num_attempt_connection_calls(), 1u);
  // Simulate state changes with AttemptConnection().
  fake_connection_manager_->SetStatus(ConnectionStatus::kConnecting);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kConnecting);
  fake_connection_manager_->SetStatus(ConnectionStatus::kDisconnected);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kDisconnected);
  EXPECT_EQ(GetBackoffFailureCount(), 1);

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

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

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

TEST_F(EcheConnectionSchedulerImplTest,
       BackoffRetryWithUpdatedFeaturesAndConnection) {
  fake_connection_manager_->SetStatus(ConnectionStatus::kDisconnected);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kDisconnected);
  CreateConnectionScheduler();

  connection_scheduler_->ScheduleConnectionNow();
  EXPECT_EQ(fake_connection_manager_->num_attempt_connection_calls(), 1u);
  // Simulate state changes with AttemptConnection().
  fake_connection_manager_->SetStatus(ConnectionStatus::kConnecting);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kConnecting);
  fake_connection_manager_->SetStatus(ConnectionStatus::kDisconnected);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kDisconnected);
  EXPECT_EQ(1, GetBackoffFailureCount());

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

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

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

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

TEST_F(EcheConnectionSchedulerImplTest,
       DisconnectAndClearBackoffAttemptsSuccesssfully) {
  fake_connection_manager_->SetStatus(ConnectionStatus::kDisconnected);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kDisconnected);
  CreateConnectionScheduler();

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

  // Simulate state changes with AttemptConnection().
  fake_connection_manager_->SetStatus(ConnectionStatus::kConnecting);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kConnecting);
  fake_connection_manager_->SetStatus(ConnectionStatus::kDisconnected);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kDisconnected);
  EXPECT_EQ(1, GetBackoffFailureCount());

  connection_scheduler_->DisconnectAndClearBackoffAttempts();

  // Expect the backoff to reset and never attempt to kickoff another
  // connection.
  EXPECT_EQ(GetBackoffFailureCount(), 0);
  EXPECT_EQ(fake_connection_manager_->num_attempt_connection_calls(), 1u);
  // Expect that connection has been disconnected.
  EXPECT_EQ(fake_connection_manager_->num_disconnect_calls(), 1u);
}

TEST_F(EcheConnectionSchedulerImplTest, SimulateIneligibleToDisconnected) {
  fake_connection_manager_->SetStatus(ConnectionStatus::kDisconnected);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kIneligible);
  CreateConnectionScheduler();

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

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

TEST_F(EcheConnectionSchedulerImplTest,
       SimulateDependentFeatureToDisconnected) {
  fake_connection_manager_->SetStatus(ConnectionStatus::kDisconnected);
  fake_feature_status_provider_->SetStatus(FeatureStatus::kDependentFeature);
  CreateConnectionScheduler();

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

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

TEST_F(EcheConnectionSchedulerImplTest,
       SimulatekDependentFeaturePendingtoDisconnected) {
  fake_connection_manager_->SetStatus(ConnectionStatus::kDisconnected);
  fake_feature_status_provider_->SetStatus(
      FeatureStatus::kDependentFeaturePending);
  CreateConnectionScheduler();

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

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

}  // namespace eche_app
}  // namespace ash