chromium/chromeos/ash/components/tether/connection_preserver_impl_unittest.cc

// Copyright 2018 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/tether/connection_preserver_impl.h"

#include <memory>

#include "base/base64.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/test/task_environment.h"
#include "base/timer/mock_timer.h"
#include "chromeos/ash/components/multidevice/remote_device_test_util.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_state_test_helper.h"
#include "chromeos/ash/components/tether/fake_active_host.h"
#include "chromeos/ash/components/tether/fake_host_connection.h"
#include "chromeos/ash/components/tether/mock_tether_host_response_recorder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

using testing::_;
using testing::NiceMock;
using testing::Return;

namespace ash::tether {

namespace {

const char kWifiNetworkGuid[] = "wifiNetworkGuid";
const char kTetherNetworkGuid[] = "tetherNetworkGuid";

std::string CreateConfigurationJsonString(const std::string& guid,
                                          const std::string& type) {
  std::stringstream ss;
  ss << "{"
     << "  \"GUID\": \"" << guid << "\","
     << "  \"Type\": \"" << type << "\","
     << "  \"State\": \"" << shill::kStateReady << "\""
     << "}";
  return ss.str();
}

}  // namespace

class ConnectionPreserverImplTest : public testing::Test {
 public:
  ConnectionPreserverImplTest(const ConnectionPreserverImplTest&) = delete;
  ConnectionPreserverImplTest& operator=(const ConnectionPreserverImplTest&) =
      delete;

 protected:
  ConnectionPreserverImplTest()
      : test_remote_devices_(multidevice::CreateRemoteDeviceRefListForTest(3)) {
    base::ranges::transform(test_remote_devices_,
                            std::back_inserter(test_remote_device_ids_),
                            &multidevice::RemoteDeviceRef::GetDeviceId);
  }

  void SetUp() override {
    fake_host_connection_factory_ =
        std::make_unique<FakeHostConnection::Factory>();
    fake_active_host_ = std::make_unique<FakeActiveHost>();

    previously_connected_host_ids_.clear();
    mock_tether_host_response_recorder_ =
        std::make_unique<NiceMock<MockTetherHostResponseRecorder>>();
    ON_CALL(*mock_tether_host_response_recorder_,
            GetPreviouslyConnectedHostIds())
        .WillByDefault(Invoke(
            this, &ConnectionPreserverImplTest::GetPreviouslyConnectedHostIds));

    connection_preserver_ = std::make_unique<ConnectionPreserverImpl>(
        fake_host_connection_factory_.get(), helper_.network_state_handler(),
        fake_active_host_.get(), mock_tether_host_response_recorder_.get());

    mock_timer_ = new base::MockOneShotTimer();
    connection_preserver_->SetTimerForTesting(
        base::WrapUnique(mock_timer_.get()));
  }

  void TearDown() override { connection_preserver_.reset(); }

  void SimulateSuccessfulHostScan(multidevice::RemoteDeviceRef remote_device,
                                  bool should_remain_registered) {
    fake_host_connection_factory_->SetupConnectionAttempt(
        TetherHost(remote_device));

    connection_preserver_->HandleSuccessfulTetherAvailabilityResponse(
        remote_device.GetDeviceId());

    if (should_remain_registered) {
      EXPECT_EQ(fake_host_connection_factory_->GetPendingConnectionAttempt(
                    remote_device.GetDeviceId()),
                nullptr);
    } else {
      EXPECT_TRUE(fake_host_connection_factory_->GetPendingConnectionAttempt(
          remote_device.GetDeviceId()));
      return;
    }

    // Expect that |connection_preserver_| continues to hold on to the
    // ClientChannel until it is destroyed or the active host becomes connected.
    VerifyChannelForRemoteDeviceDestroyed(remote_device,
                                          false /* expect_destroyed */);
  }

  void VerifyChannelForRemoteDeviceDestroyed(
      multidevice::RemoteDeviceRef remote_device,
      bool expect_destroyed) {
    if (expect_destroyed) {
      EXPECT_EQ(fake_host_connection_factory_->GetActiveConnection(
                    remote_device.GetDeviceId()),
                nullptr);
    } else {
      EXPECT_NE(fake_host_connection_factory_->GetActiveConnection(
                    remote_device.GetDeviceId()),
                nullptr);
    }
  }

  void ConnectToWifi() {
    std::string wifi_service_path = helper_.ConfigureService(
        CreateConfigurationJsonString(kWifiNetworkGuid, shill::kTypeWifi));
  }

  std::vector<std::string> GetPreviouslyConnectedHostIds() {
    return previously_connected_host_ids_;
  }

  base::test::TaskEnvironment task_environment_;

  NetworkStateTestHelper helper_{/*use_default_devices_and_services=*/true};

  const multidevice::RemoteDeviceRefList test_remote_devices_;
  std::vector<std::string> test_remote_device_ids_;

  std::unique_ptr<FakeHostConnection::Factory> fake_host_connection_factory_;
  std::unique_ptr<FakeActiveHost> fake_active_host_;
  std::unique_ptr<NiceMock<MockTetherHostResponseRecorder>>
      mock_tether_host_response_recorder_;
  raw_ptr<base::MockOneShotTimer, DanglingUntriaged> mock_timer_;

  std::unique_ptr<ConnectionPreserverImpl> connection_preserver_;

  std::vector<std::string> previously_connected_host_ids_;
};

TEST_F(ConnectionPreserverImplTest,
       TestHandleSuccessfulTetherAvailabilityResponse_NoPreservedConnection) {
  SimulateSuccessfulHostScan(test_remote_devices_[0],
                             true /* should_remain_registered */);
}

TEST_F(ConnectionPreserverImplTest,
       TestHandleSuccessfulTetherAvailabilityResponse_HasInternet) {
  ConnectToWifi();

  SimulateSuccessfulHostScan(test_remote_devices_[0],
                             false /* should_remain_registered */);
}

TEST_F(
    ConnectionPreserverImplTest,
    TestHandleSuccessfulTetherAvailabilityResponse_PreservedConnectionExists_NoPreviouslyConnectedHosts) {
  SimulateSuccessfulHostScan(test_remote_devices_[0],
                             true /* should_remain_registered */);
  VerifyChannelForRemoteDeviceDestroyed(test_remote_devices_[0],
                                        false /* expect_destroyed */);

  SimulateSuccessfulHostScan(test_remote_devices_[1],
                             true /* should_remain_registered */);
  VerifyChannelForRemoteDeviceDestroyed(test_remote_devices_[0],
                                        true /* expect_destroyed */);
  VerifyChannelForRemoteDeviceDestroyed(test_remote_devices_[1],
                                        false /* expect_destroyed */);
}

TEST_F(ConnectionPreserverImplTest,
       TestHandleSuccessfulTetherAvailabilityResponse_TimesOut) {
  SimulateSuccessfulHostScan(test_remote_devices_[0],
                             true /* should_remain_registered */);

  mock_timer_->Fire();
  VerifyChannelForRemoteDeviceDestroyed(test_remote_devices_[0],
                                        true /* expect_destroyed */);
}

TEST_F(ConnectionPreserverImplTest,
       TestHandleSuccessfulTetherAvailabilityResponse_PreserverDestroyed) {
  SimulateSuccessfulHostScan(test_remote_devices_[0],
                             true /* should_remain_registered */);

  connection_preserver_.reset();
  VerifyChannelForRemoteDeviceDestroyed(test_remote_devices_[0],
                                        true /* expect_destroyed */);
}

TEST_F(
    ConnectionPreserverImplTest,
    TestHandleSuccessfulTetherAvailabilityResponse_ActiveHostBecomesConnected) {
  SimulateSuccessfulHostScan(test_remote_devices_[0],
                             true /* should_remain_registered */);

  fake_active_host_->SetActiveHostConnecting(test_remote_device_ids_[0],
                                             kTetherNetworkGuid);
  fake_active_host_->SetActiveHostConnected(
      test_remote_device_ids_[0], kTetherNetworkGuid, kWifiNetworkGuid);

  VerifyChannelForRemoteDeviceDestroyed(test_remote_devices_[0],
                                        true /* expect_destroyed */);
}

TEST_F(
    ConnectionPreserverImplTest,
    TestHandleSuccessfulTetherAvailabilityResponse_PreviouslyConnectedHostsExist) {
  // |test_remote_device_ids_[0]| is the most recently connected device, and
  // should be preferred over any other device.
  previously_connected_host_ids_.push_back(test_remote_device_ids_[0]);
  previously_connected_host_ids_.push_back(test_remote_device_ids_[1]);

  SimulateSuccessfulHostScan(test_remote_devices_[2],
                             true /* should_remain_registered */);
  VerifyChannelForRemoteDeviceDestroyed(test_remote_devices_[2],
                                        false /* expect_destroyed */);

  SimulateSuccessfulHostScan(test_remote_devices_[1],
                             true /* should_remain_registered */);
  VerifyChannelForRemoteDeviceDestroyed(test_remote_devices_[2],
                                        true /* expect_destroyed */);
  VerifyChannelForRemoteDeviceDestroyed(test_remote_devices_[1],
                                        false /* expect_destroyed */);

  SimulateSuccessfulHostScan(test_remote_devices_[0],
                             true /* should_remain_registered */);
  VerifyChannelForRemoteDeviceDestroyed(test_remote_devices_[1],
                                        true /* expect_destroyed */);
  VerifyChannelForRemoteDeviceDestroyed(test_remote_devices_[0],
                                        false /* expect_destroyed */);

  SimulateSuccessfulHostScan(test_remote_devices_[1],
                             false /* should_remain_registered */);
  VerifyChannelForRemoteDeviceDestroyed(test_remote_devices_[0],
                                        false /* expect_destroyed */);

  SimulateSuccessfulHostScan(test_remote_devices_[2],
                             false /* should_remain_registered */);
  VerifyChannelForRemoteDeviceDestroyed(test_remote_devices_[0],
                                        false /* expect_destroyed */);
}

}  // namespace ash::tether