chromium/chromeos/ash/services/hotspot_config/cros_hotspot_config_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 "chromeos/ash/services/hotspot_config/cros_hotspot_config.h"

#include "base/memory/ptr_util.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_handler_test_helper.h"
#include "chromeos/ash/services/hotspot_config/public/cpp/cros_hotspot_config_test_observer.h"
#include "chromeos/ash/services/hotspot_config/public/cpp/hotspot_enabled_state_test_observer.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace ash::hotspot_config {

namespace {

const char kHotspotConfigSSID[] = "hotspot_SSID";
const char kHotspotConfigPassphrase[] = "hotspot_passphrase";
const char kCellularServicePath[] = "/service/cellular0";
const char kCellularServiceGuid[] = "cellular_guid0";
const char kCellularServiceName[] = "cellular_name0";

mojom::HotspotConfigPtr GenerateTestConfig() {
  auto mojom_config = mojom::HotspotConfig::New();
  mojom_config->auto_disable = false;
  mojom_config->band = mojom::WiFiBand::kAutoChoose;
  mojom_config->security = mojom::WiFiSecurityMode::kWpa2;
  mojom_config->ssid = kHotspotConfigSSID;
  mojom_config->passphrase = kHotspotConfigPassphrase;
  return mojom_config;
}

}  // namespace

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

  // testing::Test:
  void SetUp() override {
    LoginState::Initialize();
    LoginState::Get()->set_always_logged_in(false);

    network_handler_test_helper_ = std::make_unique<NetworkHandlerTestHelper>();
    network_handler_test_helper_->AddDefaultProfiles();
    network_handler_test_helper_->ResetDevicesAndServices();
    NetworkHandler* network_handler = NetworkHandler::Get();
    // Use base::WrapUnique(new CrosHotspotConfig(...)) instead of
    // std::make_unique<CrosHotspotConfig> to access a private constructor.
    cros_hotspot_config_ = base::WrapUnique(new CrosHotspotConfig(
        network_handler->hotspot_capabilities_provider(),
        network_handler->hotspot_state_handler(),
        network_handler->hotspot_controller(),
        network_handler->hotspot_configuration_handler(),
        network_handler->hotspot_enabled_state_notifier()));
    base::RunLoop().RunUntilIdle();
  }

  void TearDown() override {
    cros_hotspot_config_.reset();
    network_handler_test_helper_.reset();
    LoginState::Shutdown();
  }

  void SetupObserver() {
    observer_ = std::make_unique<CrosHotspotConfigTestObserver>();
    cros_hotspot_config_->AddObserver(observer_->GenerateRemote());

    hotspot_enabled_state_observer_ =
        std::make_unique<HotspotEnabledStateTestObserver>();
    cros_hotspot_config_->ObserveEnabledStateChanges(
        hotspot_enabled_state_observer_->GenerateRemote());
    base::RunLoop().RunUntilIdle();
  }

  void SetValidHotspotCapabilities() {
    base::Value::Dict capabilities_dict;
    base::Value::List upstream_list;
    upstream_list.Append(shill::kTypeCellular);
    capabilities_dict.Set(shill::kTetheringCapUpstreamProperty,
                          std::move(upstream_list));
    // Add WiFi to the downstream technology list in Shill
    base::Value::List downstream_list;
    downstream_list.Append(shill::kTypeWifi);
    capabilities_dict.Set(shill::kTetheringCapDownstreamProperty,
                          std::move(downstream_list));
    // Add allowed WiFi security mode in Shill
    base::Value::List security_list;
    security_list.Append(shill::kSecurityWpa2);
    security_list.Append(shill::kSecurityWpa3);
    capabilities_dict.Set(shill::kTetheringCapSecurityProperty,
                          std::move(security_list));
    network_handler_test_helper_->manager_test()->SetManagerProperty(
        shill::kTetheringCapabilitiesProperty,
        base::Value(std::move(capabilities_dict)));
    base::RunLoop().RunUntilIdle();
  }

  void AddActiveCellularService() {
    network_handler_test_helper_->service_test()->AddService(
        kCellularServicePath, kCellularServiceGuid, kCellularServiceName,
        shill::kTypeCellular, shill::kStateOnline, /*visible=*/true);
    base::RunLoop().RunUntilIdle();
  }

  void SetHotspotStateInShill(const std::string& state) {
    // Update tethering status to active in Shill.
    base::Value::Dict status_dict;
    status_dict.Set(shill::kTetheringStatusStateProperty, state);
    network_handler_test_helper_->manager_test()->SetManagerProperty(
        shill::kTetheringStatusProperty, base::Value(std::move(status_dict)));
    base::RunLoop().RunUntilIdle();
  }

  void SetReadinessCheckResultReady() {
    network_handler_test_helper_->manager_test()
        ->SetSimulateCheckTetheringReadinessResult(
            FakeShillSimulatedResult::kSuccess,
            shill::kTetheringReadinessReady);
    base::RunLoop().RunUntilIdle();
  }

  mojom::HotspotInfoPtr GetHotspotInfo() {
    mojom::HotspotInfoPtr out_result;
    base::RunLoop run_loop;
    cros_hotspot_config_->GetHotspotInfo(
        base::BindLambdaForTesting([&](mojom::HotspotInfoPtr result) {
          out_result = std::move(result);
          run_loop.Quit();
        }));
    run_loop.Run();
    return out_result;
  }

  mojom::SetHotspotConfigResult SetHotspotConfig(
      mojom::HotspotConfigPtr mojom_config) {
    base::RunLoop run_loop;
    mojom::SetHotspotConfigResult out_result;
    cros_hotspot_config_->SetHotspotConfig(
        std::move(mojom_config),
        base::BindLambdaForTesting([&](mojom::SetHotspotConfigResult result) {
          out_result = result;
          run_loop.Quit();
        }));
    run_loop.Run();
    FlushMojoCalls();
    return out_result;
  }

  mojom::HotspotControlResult EnableHotspot() {
    base::RunLoop run_loop;
    mojom::HotspotControlResult out_result;
    cros_hotspot_config_->EnableHotspot(
        base::BindLambdaForTesting([&](mojom::HotspotControlResult result) {
          out_result = result;
          run_loop.Quit();
        }));
    run_loop.Run();
    FlushMojoCalls();
    return out_result;
  }

  mojom::HotspotControlResult DisableHotspot() {
    base::RunLoop run_loop;
    mojom::HotspotControlResult out_result;
    cros_hotspot_config_->DisableHotspot(
        base::BindLambdaForTesting([&](mojom::HotspotControlResult result) {
          out_result = result;
          run_loop.Quit();
        }));
    run_loop.Run();
    FlushMojoCalls();
    return out_result;
  }

  void LoginToRegularUser() {
    LoginState::Get()->SetLoggedInState(LoginState::LOGGED_IN_ACTIVE,
                                        LoginState::LOGGED_IN_USER_REGULAR);
    task_environment_.RunUntilIdle();
  }

  void FlushMojoCalls() { base::RunLoop().RunUntilIdle(); }

  NetworkHandlerTestHelper* helper() {
    return network_handler_test_helper_.get();
  }

  CrosHotspotConfigTestObserver* observer() { return observer_.get(); }

  HotspotEnabledStateTestObserver* hotspotStateObserver() {
    return hotspot_enabled_state_observer_.get();
  }

 private:
  base::test::SingleThreadTaskEnvironment task_environment_;
  std::unique_ptr<NetworkHandlerTestHelper> network_handler_test_helper_;
  std::unique_ptr<CrosHotspotConfig> cros_hotspot_config_;
  std::unique_ptr<CrosHotspotConfigTestObserver> observer_;
  std::unique_ptr<HotspotEnabledStateTestObserver>
      hotspot_enabled_state_observer_;
};

TEST_F(CrosHotspotConfigTest, GetHotspotInfo) {
  SetupObserver();
  EXPECT_EQ(observer()->hotspot_info_changed_count(), 1u);

  auto hotspot_info = GetHotspotInfo();
  EXPECT_EQ(hotspot_info->state, mojom::HotspotState::kDisabled);
  EXPECT_EQ(hotspot_info->client_count, 0u);
  EXPECT_EQ(hotspot_info->allow_status,
            mojom::HotspotAllowStatus::kDisallowedNoMobileData);
  EXPECT_EQ(hotspot_info->allowed_wifi_security_modes.size(), 1u);
  EXPECT_TRUE(hotspot_info->config);

  SetReadinessCheckResultReady();
  SetValidHotspotCapabilities();
  base::RunLoop().RunUntilIdle();
  hotspot_info = GetHotspotInfo();
  EXPECT_EQ(hotspot_info->state, mojom::HotspotState::kDisabled);
  EXPECT_EQ(hotspot_info->client_count, 0u);
  EXPECT_EQ(hotspot_info->allow_status,
            mojom::HotspotAllowStatus::kDisallowedNoMobileData);
  EXPECT_EQ(hotspot_info->allowed_wifi_security_modes.size(), 2u);
  EXPECT_TRUE(hotspot_info->config);
  EXPECT_EQ(observer()->hotspot_info_changed_count(), 2u);

  AddActiveCellularService();
  base::RunLoop().RunUntilIdle();
  hotspot_info = GetHotspotInfo();
  EXPECT_EQ(hotspot_info->allow_status, mojom::HotspotAllowStatus::kAllowed);
  EXPECT_EQ(observer()->hotspot_info_changed_count(), 3u);

  SetHotspotStateInShill(shill::kTetheringStateActive);
  EXPECT_EQ(GetHotspotInfo()->state, mojom::HotspotState::kEnabled);
  EXPECT_EQ(observer()->hotspot_info_changed_count(), 4u);

  SetHotspotStateInShill(shill::kTetheringStateIdle);
  EXPECT_EQ(GetHotspotInfo()->state, mojom::HotspotState::kDisabled);
  EXPECT_EQ(observer()->hotspot_info_changed_count(), 5u);

  // Simulate user starting tethering
  SetHotspotStateInShill(shill::kTetheringStateStarting);
  EXPECT_EQ(GetHotspotInfo()->state, mojom::HotspotState::kEnabling);
  EXPECT_EQ(observer()->hotspot_info_changed_count(), 6u);
}

TEST_F(CrosHotspotConfigTest, SetHotspotConfig) {
  SetupObserver();
  EXPECT_EQ(observer()->hotspot_info_changed_count(), 1u);
  // Verifies that set hotspot config return failed when the user is not login.
  EXPECT_EQ(mojom::SetHotspotConfigResult::kFailedNotLogin,
            SetHotspotConfig(GenerateTestConfig()));
  // FakeShillManager return valid hotspot config regardless login or not.
  EXPECT_TRUE(GetHotspotInfo()->config);

  LoginToRegularUser();
  EXPECT_EQ(observer()->hotspot_info_changed_count(), 2u);
  EXPECT_EQ(mojom::SetHotspotConfigResult::kSuccess,
            SetHotspotConfig(GenerateTestConfig()));
  auto hotspot_info = GetHotspotInfo();
  EXPECT_TRUE(hotspot_info->config);
  EXPECT_FALSE(hotspot_info->config->auto_disable);
  EXPECT_EQ(hotspot_info->config->band, mojom::WiFiBand::kAutoChoose);
  EXPECT_EQ(hotspot_info->config->security, mojom::WiFiSecurityMode::kWpa2);
  EXPECT_EQ(hotspot_info->config->ssid, kHotspotConfigSSID);
  EXPECT_EQ(hotspot_info->config->passphrase, kHotspotConfigPassphrase);
  EXPECT_EQ(observer()->hotspot_info_changed_count(), 3u);
}

TEST_F(CrosHotspotConfigTest, EnableHotspot) {
  SetupObserver();
  EXPECT_EQ(mojom::HotspotControlResult::kReadinessCheckFailed,
            EnableHotspot());

  SetReadinessCheckResultReady();
  SetValidHotspotCapabilities();
  AddActiveCellularService();
  helper()->manager_test()->SetSimulateTetheringEnableResult(
      FakeShillSimulatedResult::kSuccess, shill::kTetheringEnableResultSuccess);
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(mojom::HotspotControlResult::kSuccess, EnableHotspot());
  EXPECT_EQ(hotspotStateObserver()->hotspot_turned_on_count(), 1u);

  // Simulate check tethering readiness operation fail.
  helper()->manager_test()->SetSimulateCheckTetheringReadinessResult(
      FakeShillSimulatedResult::kFailure,
      /*readiness_status=*/std::string());
  base::RunLoop().RunUntilIdle();

  SetHotspotStateInShill(shill::kTetheringStateIdle);
  EXPECT_EQ(mojom::HotspotControlResult::kReadinessCheckFailed,
            EnableHotspot());
}

TEST_F(CrosHotspotConfigTest, DisableHotspot) {
  SetupObserver();
  SetHotspotStateInShill(shill::kTetheringStateActive);
  helper()->manager_test()->SetSimulateTetheringEnableResult(
      FakeShillSimulatedResult::kSuccess, shill::kTetheringEnableResultSuccess);
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(mojom::HotspotControlResult::kSuccess, DisableHotspot());
  EXPECT_EQ(hotspotStateObserver()->hotspot_turned_off_count(), 1u);
  EXPECT_EQ(hotspotStateObserver()->last_disable_reason(),
            hotspot_config::mojom::DisableReason::kUserInitiated);
}

}  // namespace ash::hotspot_config