// 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