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

// Copyright 2017 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/wifi_hotspot_disconnector_impl.h"

#include <memory>

#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/network/network_connection_handler.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_network_configuration_remover.h"
#include "chromeos/ash/components/tether/pref_names.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace ash {

namespace tether {

namespace {

const char kSuccessResult[] = "success";
const char kWifiNetworkGuid[] = "wifiNetworkGuid";

std::string CreateConnectedWifiConfigurationJsonString(
    const std::string& wifi_network_guid) {
  std::stringstream ss;
  ss << "{"
     << "  \"GUID\": \"" << wifi_network_guid << "\","
     << "  \"Type\": \"" << shill::kTypeWifi << "\","
     << "  \"State\": \"" << shill::kStateOnline << "\""
     << "}";
  return ss.str();
}

class TestNetworkConnectionHandler : public NetworkConnectionHandler {
 public:
  explicit TestNetworkConnectionHandler(base::OnceClosure disconnect_callback) {
    disconnect_callback_ = std::move(disconnect_callback);
  }
  ~TestNetworkConnectionHandler() override = default;

  std::string last_disconnect_service_path() {
    return last_disconnect_service_path_;
  }

  base::OnceClosure& last_disconnect_success_callback() {
    return last_disconnect_success_callback_;
  }

  network_handler::ErrorCallback& last_disconnect_error_callback() {
    return last_disconnect_error_callback_;
  }

  // NetworkConnectionHandler:
  void DisconnectNetwork(
      const std::string& service_path,
      base::OnceClosure success_callback,
      network_handler::ErrorCallback error_callback) override {
    last_disconnect_service_path_ = service_path;
    last_disconnect_success_callback_ = std::move(success_callback);
    last_disconnect_error_callback_ = std::move(error_callback);

    std::move(disconnect_callback_).Run();
  }
  void ConnectToNetwork(const std::string& service_path,
                        base::OnceClosure success_callback,
                        network_handler::ErrorCallback error_callback,
                        bool check_error_state,
                        ConnectCallbackMode mode) override {}
  void Init(
      NetworkStateHandler* network_state_handler,
      NetworkConfigurationHandler* network_configuration_handler,
      ManagedNetworkConfigurationHandler* managed_network_configuration_handler,
      CellularConnectionHandler* cellular_connection_handler) override {}

  void OnAutoConnectedInitiated(int reason) override {}

 private:
  base::OnceClosure disconnect_callback_;

  std::string last_disconnect_service_path_;
  base::OnceClosure last_disconnect_success_callback_;
  network_handler::ErrorCallback last_disconnect_error_callback_;
};

}  // namespace

class WifiHotspotDisconnectorImplTest : public testing::Test {
 public:
  WifiHotspotDisconnectorImplTest() = default;

  WifiHotspotDisconnectorImplTest(const WifiHotspotDisconnectorImplTest&) =
      delete;
  WifiHotspotDisconnectorImplTest& operator=(
      const WifiHotspotDisconnectorImplTest&) = delete;

  ~WifiHotspotDisconnectorImplTest() override = default;

  void SetUp() override {
    should_disconnect_successfully_ = true;

    test_network_connection_handler_ =
        base::WrapUnique(new TestNetworkConnectionHandler(
            base::BindOnce(&WifiHotspotDisconnectorImplTest::
                               OnNetworkConnectionManagerDisconnect,
                           base::Unretained(this))));
    fake_configuration_remover_ =
        std::make_unique<FakeNetworkConfigurationRemover>();
    test_pref_service_ =
        std::make_unique<sync_preferences::TestingPrefServiceSyncable>();

    WifiHotspotDisconnectorImpl::RegisterPrefs(test_pref_service_->registry());
    wifi_hotspot_disconnector_ = std::make_unique<WifiHotspotDisconnectorImpl>(
        test_network_connection_handler_.get(), helper_.network_state_handler(),
        test_pref_service_.get(), fake_configuration_remover_.get());
  }

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

  void SimulateConnectionToWifiNetwork() {
    wifi_service_path_ = helper_.ConfigureService(
        CreateConnectedWifiConfigurationJsonString(kWifiNetworkGuid));
    EXPECT_FALSE(wifi_service_path_.empty());
  }

  void SetWifiNetworkToDisconnected() {
    EXPECT_FALSE(wifi_service_path_.empty());
    helper_.SetServiceProperty(wifi_service_path_, shill::kStateProperty,
                               base::Value(shill::kStateIdle));
  }

  void SuccessCallback() { disconnection_result_ = kSuccessResult; }

  void ErrorCallback(const std::string& error_name) {
    disconnection_result_ = error_name;
  }

  void CallDisconnect(const std::string& wifi_network_guid) {
    wifi_hotspot_disconnector_->DisconnectFromWifiHotspot(
        wifi_network_guid,
        base::BindOnce(&WifiHotspotDisconnectorImplTest::SuccessCallback,
                       base::Unretained(this)),
        base::BindOnce(&WifiHotspotDisconnectorImplTest::ErrorCallback,
                       base::Unretained(this)));
  }

  void OnNetworkConnectionManagerDisconnect() {
    EXPECT_EQ(wifi_service_path_,
              test_network_connection_handler_->last_disconnect_service_path());

    if (should_disconnect_successfully_) {
      SetWifiNetworkToDisconnected();
    }

    // Before the callbacks are invoked, the network configuration should not
    // yet have been cleared, and the disconnecting GUID should still be in
    // prefs.
    EXPECT_TRUE(
        fake_configuration_remover_->last_removed_wifi_network_path().empty());
    EXPECT_FALSE(GetDisconnectingWifiPathFromPrefs().empty());

    if (should_disconnect_successfully_) {
      EXPECT_FALSE(
          test_network_connection_handler_->last_disconnect_success_callback()
              .is_null());
      std::move(
          test_network_connection_handler_->last_disconnect_success_callback())
          .Run();
    } else {
      EXPECT_FALSE(
          test_network_connection_handler_->last_disconnect_error_callback()
              .is_null());
      network_handler::RunErrorCallback(
          std::move(test_network_connection_handler_
                        ->last_disconnect_error_callback()),
          NetworkConnectionHandler::kErrorDisconnectFailed);
    }

    // Now that the callbacks have been invoked, both the network
    // configuration and the disconnecting GUID should have cleared.
    EXPECT_FALSE(
        fake_configuration_remover_->last_removed_wifi_network_path().empty());
    EXPECT_TRUE(GetDisconnectingWifiPathFromPrefs().empty());
  }

  std::string GetResultAndReset() {
    std::string result;
    result.swap(disconnection_result_);
    return result;
  }

  std::string GetDisconnectingWifiPathFromPrefs() {
    return test_pref_service_->GetString(prefs::kDisconnectingWifiNetworkPath);
  }

  std::string GetServiceStringProperty(const std::string& service_path,
                                       const std::string& key) {
    return helper_.GetServiceStringProperty(service_path, key);
  }

  base::test::TaskEnvironment task_environment_;
  NetworkStateTestHelper helper_{/*use_default_devices_and_services=*/true};

  std::unique_ptr<TestNetworkConnectionHandler>
      test_network_connection_handler_;
  std::unique_ptr<FakeNetworkConfigurationRemover> fake_configuration_remover_;
  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable>
      test_pref_service_;

  std::string wifi_service_path_;
  std::string disconnection_result_;
  bool should_disconnect_successfully_;

  std::unique_ptr<WifiHotspotDisconnectorImpl> wifi_hotspot_disconnector_;
};

TEST_F(WifiHotspotDisconnectorImplTest, NetworkDoesNotExist) {
  CallDisconnect("nonexistentWifiGuid");
  EXPECT_EQ(NetworkConnectionHandler::kErrorNotFound, GetResultAndReset());

  // Configuration should not have been removed.
  EXPECT_TRUE(
      fake_configuration_remover_->last_removed_wifi_network_path().empty());
}

TEST_F(WifiHotspotDisconnectorImplTest, NetworkNotActuallyConnected) {
  // Start with the network disconnected.
  SimulateConnectionToWifiNetwork();
  SetWifiNetworkToDisconnected();

  CallDisconnect(kWifiNetworkGuid);
  EXPECT_EQ(NetworkConnectionHandler::kErrorNotConnected, GetResultAndReset());

  // Configuration should not have been removed.
  EXPECT_TRUE(
      fake_configuration_remover_->last_removed_wifi_network_path().empty());
}

TEST_F(WifiHotspotDisconnectorImplTest, WifiDisconnectionFails) {
  SimulateConnectionToWifiNetwork();

  should_disconnect_successfully_ = false;

  CallDisconnect(kWifiNetworkGuid);
  EXPECT_EQ(NetworkConnectionHandler::kErrorDisconnectFailed,
            GetResultAndReset());

  // The Wi-Fi network should still be connected since disconnection failed.
  EXPECT_EQ(
      shill::kStateOnline,
      GetServiceStringProperty(wifi_service_path_, shill::kStateProperty));

  // Configuration should have been removed despite the failure.
  EXPECT_FALSE(
      fake_configuration_remover_->last_removed_wifi_network_path().empty());
}

TEST_F(WifiHotspotDisconnectorImplTest, WifiDisconnectionSucceeds) {
  SimulateConnectionToWifiNetwork();

  CallDisconnect(kWifiNetworkGuid);
  EXPECT_EQ(kSuccessResult, GetResultAndReset());

  // The Wi-Fi network should be disconnected.
  EXPECT_EQ(shill::kStateIdle, GetServiceStringProperty(wifi_service_path_,
                                                        shill::kStateProperty));

  // Configuration should have been removed.
  EXPECT_FALSE(
      fake_configuration_remover_->last_removed_wifi_network_path().empty());
}

}  // namespace tether

}  // namespace ash