chromium/chromeos/ash/components/network/hotspot_enabled_state_notifier_unittest.cc

// Copyright 2023 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/network/hotspot_enabled_state_notifier.h"
#include "ash/constants/ash_features.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "chromeos/ash/components/dbus/shill/shill_clients.h"
#include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
#include "chromeos/ash/components/network/enterprise_managed_metadata_store.h"
#include "chromeos/ash/components/network/hotspot_allowed_flag_handler.h"
#include "chromeos/ash/components/network/hotspot_state_handler.h"
#include "chromeos/ash/components/network/metrics/hotspot_feature_usage_metrics.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_state_test_helper.h"
#include "chromeos/ash/services/hotspot_config/public/cpp/hotspot_enabled_state_test_observer.h"
#include "chromeos/ash/services/hotspot_config/public/mojom/cros_hotspot_config.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace ash {

namespace {

const char kCellularServicePath[] = "/service/cellular0";
const char kCellularServiceGuid[] = "cellular_guid0";
const char kCellularServiceName[] = "cellular_name0";

}  // namespace

class HotspotEnabledStateNotifierTest : public ::testing::Test {
 public:
  void SetUp() override {
    enterprise_managed_metadata_store_ =
        std::make_unique<EnterpriseManagedMetadataStore>();
    hotspot_state_handler_ = std::make_unique<HotspotStateHandler>();
    hotspot_state_handler_->Init();
    hotspot_capabilities_provider_ =
        std::make_unique<HotspotCapabilitiesProvider>();
    hotspot_allowed_flag_handler_ =
        std::make_unique<HotspotAllowedFlagHandler>();
    hotspot_capabilities_provider_->Init(
        network_state_test_helper_.network_state_handler(),
        hotspot_allowed_flag_handler_.get());
    hotspot_feature_usage_metrics_ =
        std::make_unique<HotspotFeatureUsageMetrics>();
    hotspot_feature_usage_metrics_->Init(
        enterprise_managed_metadata_store_.get(),
        hotspot_capabilities_provider_.get());
    technology_state_controller_ =
        std::make_unique<TechnologyStateController>();
    technology_state_controller_->Init(
        network_state_test_helper_.network_state_handler());
    hotspot_controller_ = std::make_unique<HotspotController>();
    hotspot_controller_->Init(hotspot_capabilities_provider_.get(),
                              hotspot_feature_usage_metrics_.get(),
                              hotspot_state_handler_.get(),
                              technology_state_controller_.get());
    hotspot_enabled_state_notifier_ =
        std::make_unique<HotspotEnabledStateNotifier>();
    hotspot_enabled_state_notifier_->Init(hotspot_state_handler_.get(),
                                          hotspot_controller_.get());
    SetReadinessCheckResultReady();
  }

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

  void AddActiveCellularServivce() {
    ShillServiceClient::TestInterface* service_test =
        network_state_test_helper_.service_test();
    service_test->AddService(kCellularServicePath, kCellularServiceGuid,
                             kCellularServiceName, shill::kTypeCellular,
                             shill::kStateOnline, /*visible=*/true);
  }

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

  void SetHotspotStateInShill(const std::string& state) {
    auto status_dict =
        base::Value::Dict().Set(shill::kTetheringStatusStateProperty, state);
    network_state_test_helper_.manager_test()->SetManagerProperty(
        shill::kTetheringStatusProperty, base::Value(std::move(status_dict)));
    base::RunLoop().RunUntilIdle();
  }

  void SetPolicyAllowHotspot(bool allow_hotspot) {
    base::RunLoop run_loop;
    hotspot_controller_->SetPolicyAllowHotspot(allow_hotspot);
    run_loop.RunUntilIdle();
  }

  bool PrepareEnableWifi() {
    base::RunLoop run_loop;
    bool prepare_success;
    hotspot_controller_->PrepareEnableWifi(
        base::BindLambdaForTesting([&](bool result) {
          prepare_success = result;
          run_loop.Quit();
        }));
    run_loop.Run();
    FlushMojoCalls();
    return prepare_success;
  }

  void SetupObserver() {
    hotspot_enabled_state_observer_ =
        std::make_unique<hotspot_config::HotspotEnabledStateTestObserver>();
    hotspot_enabled_state_notifier_->ObserveEnabledStateChanges(
        hotspot_enabled_state_observer_->GenerateRemote());
  }

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

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

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

 protected:
  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  NetworkStateTestHelper network_state_test_helper_{
      /*use_default_devices_and_services=*/false};
  std::unique_ptr<EnterpriseManagedMetadataStore>
      enterprise_managed_metadata_store_;
  std::unique_ptr<HotspotStateHandler> hotspot_state_handler_;
  std::unique_ptr<HotspotAllowedFlagHandler> hotspot_allowed_flag_handler_;
  std::unique_ptr<HotspotCapabilitiesProvider> hotspot_capabilities_provider_;
  std::unique_ptr<HotspotFeatureUsageMetrics> hotspot_feature_usage_metrics_;
  std::unique_ptr<TechnologyStateController> technology_state_controller_;
  std::unique_ptr<HotspotController> hotspot_controller_;
  std::unique_ptr<HotspotEnabledStateNotifier> hotspot_enabled_state_notifier_;
  std::unique_ptr<hotspot_config::HotspotEnabledStateTestObserver>
      hotspot_enabled_state_observer_;
};

TEST_F(HotspotEnabledStateNotifierTest, HotspotTurnedOn) {
  SetupObserver();
  SetValidTetheringCapabilities();
  AddActiveCellularServivce();

  network_state_test_helper_.manager_test()->SetSimulateTetheringEnableResult(
      FakeShillSimulatedResult::kSuccess, shill::kTetheringEnableResultSuccess);
  base::RunLoop().RunUntilIdle();

  EnableHotspot();
  EXPECT_EQ(1u, hotspotStateObserver()->hotspot_turned_on_count());
}

TEST_F(HotspotEnabledStateNotifierTest, HotspotTurnedOff) {
  SetupObserver();
  SetValidTetheringCapabilities();
  AddActiveCellularServivce();

  network_state_test_helper_.manager_test()->SetSimulateTetheringEnableResult(
      FakeShillSimulatedResult::kSuccess, shill::kTetheringEnableResultSuccess);
  base::RunLoop().RunUntilIdle();

  SetHotspotStateInShill(shill::kTetheringStateActive);

  SetPolicyAllowHotspot(/*allow_hotspot=*/false);
  EXPECT_EQ(1u, hotspotStateObserver()->hotspot_turned_off_count());
  EXPECT_EQ(hotspot_config::mojom::DisableReason::kProhibitedByPolicy,
            hotspotStateObserver()->last_disable_reason());
}

TEST_F(HotspotEnabledStateNotifierTest, WifiTurnedOn) {
  SetupObserver();
  SetValidTetheringCapabilities();
  AddActiveCellularServivce();

  network_state_test_helper_.manager_test()->SetSimulateTetheringEnableResult(
      FakeShillSimulatedResult::kSuccess, shill::kTetheringEnableResultSuccess);
  base::RunLoop().RunUntilIdle();

  SetHotspotStateInShill(shill::kTetheringStateActive);

  PrepareEnableWifi();
  EXPECT_EQ(1u, hotspotStateObserver()->hotspot_turned_off_count());
  EXPECT_EQ(hotspot_config::mojom::DisableReason::kWifiEnabled,
            hotspotStateObserver()->last_disable_reason());
}

TEST_F(HotspotEnabledStateNotifierTest, DisabledBySystem) {
  SetupObserver();
  SetValidTetheringCapabilities();
  AddActiveCellularServivce();

  network_state_test_helper_.manager_test()->SetSimulateTetheringEnableResult(
      FakeShillSimulatedResult::kSuccess, shill::kTetheringEnableResultSuccess);
  base::RunLoop().RunUntilIdle();

  base::Value::Dict status_dict;
  status_dict.Set(shill::kTetheringStatusStateProperty,
                  shill::kTetheringStateIdle);

  SetHotspotStateInShill(shill::kTetheringStateActive);

  status_dict.Set(shill::kTetheringStatusIdleReasonProperty,
                  shill::kTetheringIdleReasonInactive);
  network_state_test_helper_.manager_test()->SetManagerProperty(
      shill::kTetheringStatusProperty, base::Value(status_dict.Clone()));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1u, hotspotStateObserver()->hotspot_turned_off_count());
  EXPECT_EQ(hotspot_config::mojom::DisableReason::kAutoDisabled,
            hotspotStateObserver()->last_disable_reason());

  SetHotspotStateInShill(shill::kTetheringStateActive);

  status_dict.Set(shill::kTetheringStatusIdleReasonProperty,
                  shill::kTetheringIdleReasonError);
  network_state_test_helper_.manager_test()->SetManagerProperty(
      shill::kTetheringStatusProperty, base::Value(status_dict.Clone()));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(hotspot_config::mojom::DisableReason::kInternalError,
            hotspotStateObserver()->last_disable_reason());

  SetHotspotStateInShill(shill::kTetheringStateActive);

  status_dict.Set(shill::kTetheringStatusIdleReasonProperty,
                  shill::kTetheringIdleReasonUpstreamDisconnect);
  network_state_test_helper_.manager_test()->SetManagerProperty(
      shill::kTetheringStatusProperty, base::Value(status_dict.Clone()));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(hotspot_config::mojom::DisableReason::kUpstreamNetworkNotAvailable,
            hotspotStateObserver()->last_disable_reason());

  SetHotspotStateInShill(shill::kTetheringStateActive);

  status_dict.Set(shill::kTetheringStatusIdleReasonProperty,
                  shill::kTetheringIdleReasonSuspend);
  network_state_test_helper_.manager_test()->SetManagerProperty(
      shill::kTetheringStatusProperty, base::Value(status_dict.Clone()));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(hotspot_config::mojom::DisableReason::kSuspended,
            hotspotStateObserver()->last_disable_reason());

  SetHotspotStateInShill(shill::kTetheringStateActive);
  status_dict.Set(shill::kTetheringStatusIdleReasonProperty,
                  shill::kTetheringIdleReasonUpstreamNoInternet);
  network_state_test_helper_.manager_test()->SetManagerProperty(
      shill::kTetheringStatusProperty, base::Value(status_dict.Clone()));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(hotspot_config::mojom::DisableReason::kUpstreamNoInternet,
            hotspotStateObserver()->last_disable_reason());

  SetHotspotStateInShill(shill::kTetheringStateActive);
  status_dict.Set(shill::kTetheringStatusIdleReasonProperty,
                  shill::kTetheringIdleReasonDownstreamLinkDisconnect);
  network_state_test_helper_.manager_test()->SetManagerProperty(
      shill::kTetheringStatusProperty, base::Value(status_dict.Clone()));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(hotspot_config::mojom::DisableReason::kDownstreamLinkDisconnect,
            hotspotStateObserver()->last_disable_reason());

  SetHotspotStateInShill(shill::kTetheringStateActive);
  status_dict.Set(shill::kTetheringStatusIdleReasonProperty,
                  shill::kTetheringIdleReasonDownstreamNetworkDisconnect);
  network_state_test_helper_.manager_test()->SetManagerProperty(
      shill::kTetheringStatusProperty, base::Value(status_dict.Clone()));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(hotspot_config::mojom::DisableReason::kDownstreamNetworkDisconnect,
            hotspotStateObserver()->last_disable_reason());

  SetHotspotStateInShill(shill::kTetheringStateActive);
  status_dict.Set(shill::kTetheringStatusIdleReasonProperty,
                  shill::kTetheringIdleReasonUpstreamNotAvailable);
  network_state_test_helper_.manager_test()->SetManagerProperty(
      shill::kTetheringStatusProperty, base::Value(status_dict.Clone()));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(hotspot_config::mojom::DisableReason::kUpstreamNotAvailable,
            hotspotStateObserver()->last_disable_reason());

  SetHotspotStateInShill(shill::kTetheringStateActive);
  status_dict.Set(shill::kTetheringStatusIdleReasonProperty,
                  shill::kTetheringIdleReasonStartTimeout);
  network_state_test_helper_.manager_test()->SetManagerProperty(
      shill::kTetheringStatusProperty, base::Value(status_dict.Clone()));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(hotspot_config::mojom::DisableReason::kStartTimeout,
            hotspotStateObserver()->last_disable_reason());
}

}  // namespace ash