chromium/chrome/browser/ui/ash/network/network_state_notifier_unittest.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/ash/network/network_state_notifier.h"

#include <memory>

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/test/test_system_tray_client.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/notifications/system_notification_helper.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chromeos/ash/components/carrier_lock/carrier_lock_manager.h"
#include "chromeos/ash/components/carrier_lock/common.h"
#include "chromeos/ash/components/carrier_lock/fake_fcm_topic_subscriber.h"
#include "chromeos/ash/components/carrier_lock/fake_provisioning_config_fetcher.h"
#include "chromeos/ash/components/carrier_lock/fake_psm_claim_verifier.h"
#include "chromeos/ash/components/dbus/hermes/hermes_clients.h"
#include "chromeos/ash/components/dbus/shill/shill_device_client.h"
#include "chromeos/ash/components/dbus/shill/shill_service_client.h"
#include "chromeos/ash/components/network/cellular_metrics_logger.h"
#include "chromeos/ash/components/network/fake_network_3gpp_handler.h"
#include "chromeos/ash/components/network/network_connect.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_handler_test_helper.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_state_test_helper.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/platform_test.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/strings/grit/ui_chromeos_strings.h"
#include "ui/message_center/public/cpp/notification.h"

namespace ash {

namespace {

const char kWiFi1Guid[] = "wifi1_guid";
const char kCellular1ServicePath[] = "/service/cellular1";
const char kCellular1Guid[] = "cellular1_guid";
const char kCellular1NetworkName[] = "cellular1";
const char16_t kCellular1NetworkName16[] = u"cellular1";
const char kTestEsimProfileName[] = "test_profile_name";
const char16_t kTestEsimProfileName16[] = u"test_profile_name";
const char kCellularEsimServicePath[] = "/service/cellular_esim1";
const char kCellularDevicePath[] = "/device/cellular1";
const char kTestTopic[] = "/topics/test";

class NetworkConnectTestDelegate : public NetworkConnect::Delegate {
 public:
  NetworkConnectTestDelegate(
      std::unique_ptr<NetworkStateNotifier> network_state_notifier)
      : network_state_notifier_(std::move(network_state_notifier)) {}

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

  ~NetworkConnectTestDelegate() override = default;

  // NetworkConnect::Delegate
  void ShowNetworkConfigure(const std::string& network_id) override {}
  void ShowNetworkSettings(const std::string& network_id) override {}
  bool ShowEnrollNetwork(const std::string& network_id) override {
    return false;
  }
  void ShowMobileSetupDialog(const std::string& service_path) override {}
  void ShowCarrierAccountDetail(const std::string& service_path) override {}
  void ShowCarrierUnlockNotification() override {
    network_state_notifier_->ShowCarrierUnlockNotification();
  }
  void ShowPortalSignin(const std::string& service_path,
                        NetworkConnect::Source source) override {}
  void ShowNetworkConnectError(const std::string& error_name,
                               const std::string& network_id) override {
    network_state_notifier_->ShowNetworkConnectErrorForGuid(error_name,
                                                            network_id);
  }
  void ShowMobileActivationError(const std::string& network_id) override {}

 private:
  std::unique_ptr<NetworkStateNotifier> network_state_notifier_;
};

}  // namespace

class NetworkStateNotifierTest : public BrowserWithTestWindowTest {
 public:
  NetworkStateNotifierTest()
      : BrowserWithTestWindowTest(
            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}

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

  ~NetworkStateNotifierTest() override = default;

  void SetUp() override {
    BrowserWithTestWindowTest::SetUp();
    network_handler_test_helper_ = std::make_unique<NetworkHandlerTestHelper>();
    network_handler_test_helper_->RegisterPrefs(user_prefs_.registry(),
                                                local_state_.registry());

    network_handler_test_helper_->InitializePrefs(&user_prefs_, &local_state_);
  }

  void TearDown() override {
    carrier_lock_manager_.reset();
    NetworkConnect::Shutdown();
    network_connect_delegate_.reset();
    network_handler_test_helper_.reset();
    BrowserWithTestWindowTest::TearDown();
  }

  void SetupCarrierLock() {
    ash::carrier_lock::CarrierLockManager::RegisterLocalPrefs(
        local_state_.registry());

    fake_modem_handler_ = std::make_unique<FakeNetwork3gppHandler>();
    fake_config_fetcher_ =
        std::make_unique<carrier_lock::FakeProvisioningConfigFetcher>();
    fake_psm_verifier_ = std::make_unique<carrier_lock::FakePsmClaimVerifier>();
    fake_fcm_subscriber_ =
        std::make_unique<carrier_lock::FakeFcmTopicSubscriber>();
  }

  void RunCarrierLock() {
    local_state_.SetBoolean(carrier_lock::kDisableManagerPref, false);
    local_state_.SetString(carrier_lock::kFcmTopicPref, std::string());
    local_state_.SetString(carrier_lock::kLastImeiPref, std::string());
    local_state_.SetInteger(carrier_lock::kErrorCounterPref, 0);

    carrier_lock_manager_ = carrier_lock::CarrierLockManager::CreateForTesting(
        &local_state_, fake_modem_handler_.get(),
        std::move(fake_fcm_subscriber_), std::move(fake_psm_verifier_),
        std::move(fake_config_fetcher_));
  }

  void Init() {
    SetupDefaultShillState();
    base::RunLoop().RunUntilIdle();

    auto notifier = std::make_unique<NetworkStateNotifier>();
    notifier->set_system_tray_client(&test_system_tray_client_);

    network_connect_delegate_ =
        std::make_unique<NetworkConnectTestDelegate>(std::move(notifier));
    NetworkConnect::Initialize(network_connect_delegate_.get());
  }

 protected:
  void SetupESimNetwork() {
    const char kTestEuiccPath[] = "euicc_path";
    const char kTestEidName[] = "eid";
    const char kTestIccid[] = "iccid";

    // Disable stub cellular networks so that eSIM profile and service can both
    // be added at the same time without stub network interfering.
    NetworkHandler::Get()
        ->network_state_handler()
        ->set_stub_cellular_networks_provider(nullptr);
    ShillServiceClient::TestInterface* service_test =
        ShillServiceClient::Get()->GetTestInterface();
    service_test->ClearServices();

    hermes_manager_test_ = HermesManagerClient::Get()->GetTestInterface();
    hermes_euicc_test_ = HermesEuiccClient::Get()->GetTestInterface();

    hermes_manager_test_->AddEuicc(dbus::ObjectPath(kTestEuiccPath),
                                   kTestEidName, /*is_active=*/true,
                                   /*physical_slot=*/0);

    hermes_euicc_test_->AddCarrierProfile(
        dbus::ObjectPath(kCellularEsimServicePath),
        dbus::ObjectPath(kTestEuiccPath), kTestIccid, kTestEsimProfileName,
        kTestEsimProfileName, "service_provider", "activation_code",
        kCellularEsimServicePath, hermes::profile::State::kActive,
        hermes::profile::ProfileClass::kOperational,
        HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
            kAddProfileWithService);
    base::RunLoop().RunUntilIdle();
  }

  void SetCellularDeviceLocked(std::string lock_type) {
    ShillDeviceClient::TestInterface* device_test =
        network_handler_test_helper_->device_test();

    base::Value::Dict sim_lock_status;
    sim_lock_status.Set(shill::kSIMLockTypeProperty, lock_type);
    device_test->SetDeviceProperty(
        kCellularDevicePath, shill::kSIMLockStatusProperty,
        base::Value(std::move(sim_lock_status)), /*notify_changed=*/true);
    base::RunLoop().RunUntilIdle();
  }

  void SetupDefaultShillState() {
    ShillDeviceClient::TestInterface* device_test =
        network_handler_test_helper_->device_test();
    device_test->ClearDevices();

    ShillServiceClient::TestInterface* service_test =
        network_handler_test_helper_->service_test();
    service_test->ClearServices();

    // Set up Wi-Fi device, and add a single network with a passphrase failure.
    const char kWiFi1ServicePath[] = "/service/wifi1";
    device_test->AddDevice("/device/stub_wifi_device1", shill::kTypeWifi,
                           "stub_wifi_device1");
    service_test->AddService(kWiFi1ServicePath, kWiFi1Guid, "wifi1",
                             shill::kTypeWifi, shill::kStateIdle, true);
    service_test->SetServiceProperty(kWiFi1ServicePath,
                                     shill::kSecurityClassProperty,
                                     base::Value(shill::kSecurityClassWep));
    service_test->SetServiceProperty(
        kWiFi1ServicePath, shill::kConnectableProperty, base::Value(true));
    service_test->SetServiceProperty(
        kWiFi1ServicePath, shill::kPassphraseProperty, base::Value("failure"));

    // Set up Cellular device, and add a single locked network.
    const char kCellular1Iccid[] = "iccid";
    device_test->AddDevice(kCellularDevicePath, shill::kTypeCellular,
                           "stub_cellular_device1");
    service_test->AddService(kCellular1ServicePath, kCellular1Guid,
                             kCellular1NetworkName, shill::kTypeCellular,
                             shill::kStateIdle, true);
    service_test->SetServiceProperty(kCellular1ServicePath,
                                     shill::kIccidProperty,
                                     base::Value(kCellular1Iccid));
    service_test->SetServiceProperty(
        kCellular1ServicePath, shill::kActivationStateProperty,
        base::Value(shill::kActivationStateActivated));

    base::Value::List sim_slot_infos;
    base::Value::Dict slot_info_item;
    slot_info_item.Set(shill::kSIMSlotInfoICCID, kCellular1Iccid);
    slot_info_item.Set(shill::kSIMSlotInfoPrimary, true);
    sim_slot_infos.Append(std::move(slot_info_item));
    device_test->SetDeviceProperty(
        kCellularDevicePath, shill::kSIMSlotInfoProperty,
        base::Value(std::move(sim_slot_infos)), /*notify_changed=*/true);

    base::RunLoop().RunUntilIdle();
  }

  raw_ptr<HermesManagerClient::TestInterface, DanglingUntriaged>
      hermes_manager_test_;
  raw_ptr<HermesEuiccClient::TestInterface, DanglingUntriaged>
      hermes_euicc_test_;
  TestSystemTrayClient test_system_tray_client_;
  std::unique_ptr<NetworkHandlerTestHelper> network_handler_test_helper_;
  std::unique_ptr<NetworkConnectTestDelegate> network_connect_delegate_;
  base::test::ScopedFeatureList scoped_feature_list_;
  TestingPrefServiceSimple user_prefs_;
  TestingPrefServiceSimple local_state_;
  std::unique_ptr<carrier_lock::CarrierLockManager> carrier_lock_manager_;
  std::unique_ptr<FakeNetwork3gppHandler> fake_modem_handler_;
  std::unique_ptr<carrier_lock::FakeFcmTopicSubscriber> fake_fcm_subscriber_;
  std::unique_ptr<carrier_lock::FakePsmClaimVerifier> fake_psm_verifier_;
  std::unique_ptr<carrier_lock::FakeProvisioningConfigFetcher>
      fake_config_fetcher_;
};

TEST_F(NetworkStateNotifierTest, WiFiConnectionFailure) {
  Init();
  TestingBrowserProcess::GetGlobal()->SetSystemNotificationHelper(
      std::make_unique<SystemNotificationHelper>());
  NotificationDisplayServiceTester tester(nullptr /* profile */);
  NetworkConnect::Get()->ConnectToNetworkId(kWiFi1Guid);
  base::RunLoop().RunUntilIdle();
  // Failure should spawn a notification.
  EXPECT_TRUE(tester.GetNotification(
      NetworkStateNotifier::kNetworkConnectNotificationId));
}

TEST_F(NetworkStateNotifierTest, CellularLockedSimConnectionFailure) {
  Init();
  SetCellularDeviceLocked(shill::kSIMLockPin);
  TestingBrowserProcess::GetGlobal()->SetSystemNotificationHelper(
      std::make_unique<SystemNotificationHelper>());
  NotificationDisplayServiceTester tester(nullptr /* profile */);
  NetworkConnect::Get()->ConnectToNetworkId(kCellular1Guid);
  base::RunLoop().RunUntilIdle();

  // Failure should spawn a notification.
  std::optional<message_center::Notification> notification =
      tester.GetNotification(
          NetworkStateNotifier::kNetworkConnectNotificationId);
  EXPECT_TRUE(notification);

  EXPECT_EQ(notification->message(),
            l10n_util::GetStringFUTF16(
                IDS_NETWORK_CONNECTION_ERROR_MESSAGE, kCellular1NetworkName16,
                l10n_util::GetStringUTF16(IDS_NETWORK_LIST_SIM_CARD_LOCKED)));

  // Clicking the notification should open SIM unlock settings.
  notification->delegate()->Click(/*button_index=*/std::nullopt,
                                  /*reply=*/std::nullopt);
  EXPECT_EQ(1, test_system_tray_client_.show_sim_unlock_settings_count());
}

TEST_F(NetworkStateNotifierTest, CellularEsimConnectionFailure) {
  Init();
  SetCellularDeviceLocked(shill::kSIMLockPin);
  SetupESimNetwork();
  TestingBrowserProcess::GetGlobal()->SetSystemNotificationHelper(
      std::make_unique<SystemNotificationHelper>());
  NotificationDisplayServiceTester tester(nullptr /* profile */);
  NetworkConnect::Get()->ConnectToNetworkId("esim_guidiccid");
  base::RunLoop().RunUntilIdle();

  // Failure should spawn a notification.
  std::optional<message_center::Notification> notification =
      tester.GetNotification(
          NetworkStateNotifier::kNetworkConnectNotificationId);
  EXPECT_TRUE(notification);

  EXPECT_EQ(notification->message(),
            l10n_util::GetStringFUTF16(
                IDS_NETWORK_CONNECTION_ERROR_MESSAGE, kTestEsimProfileName16,
                l10n_util::GetStringUTF16(IDS_NETWORK_LIST_SIM_CARD_LOCKED)));

  ShillServiceClient::TestInterface* service_test =
      ShillServiceClient::Get()->GetTestInterface();
  service_test->SetServiceProperty(
      kCellularEsimServicePath, shill::kConnectableProperty, base::Value(true));

  // Set device locked status to false, this will allow for network connection
  // to succeed.
  SetCellularDeviceLocked(/*lock_type=*/"");
  NetworkConnect::Get()->ConnectToNetworkId("esim_guidiccid");
  base::RunLoop().RunUntilIdle();

  // Notification is removed.
  notification = tester.GetNotification(
      NetworkStateNotifier::kNetworkConnectNotificationId);
  EXPECT_FALSE(notification);
}

TEST_F(NetworkStateNotifierTest,
       CellularInvalidApnConnectionFailureApnRevampEnabled) {
  scoped_feature_list_.InitAndEnableFeature(ash::features::kApnRevamp);
  Init();
  TestingBrowserProcess::GetGlobal()->SetSystemNotificationHelper(
      std::make_unique<SystemNotificationHelper>());
  NotificationDisplayServiceTester tester(nullptr /* profile */);
  network_handler_test_helper_->service_test()
      ->SetErrorForNextConnectionAttempt(shill::kErrorInvalidAPN);
  network_handler_test_helper_->service_test()->SetServiceProperty(
      kCellular1ServicePath, shill::kConnectableProperty, base::Value(true));
  network_handler_test_helper_->service_test()->SetServiceProperty(
      kCellular1ServicePath, shill::kErrorProperty,
      base::Value(shill::kErrorInvalidAPN));
  NetworkConnect::Get()->ConnectToNetworkId(kCellular1Guid);
  base::RunLoop().RunUntilIdle();

  // Failure should spawn a notification.
  std::optional<message_center::Notification> notification =
      tester.GetNotification(
          NetworkStateNotifier::kNetworkConnectNotificationId);
  EXPECT_TRUE(notification);

  // Clicking the notification should open the APN subpage.
  notification->delegate()->Click(/*button_index=*/std::nullopt,
                                  /*reply=*/std::nullopt);
  EXPECT_EQ(1, test_system_tray_client_.show_apn_subpage_count());
  EXPECT_EQ(kCellular1Guid,
            test_system_tray_client_.last_apn_subpage_network_id());
}

TEST_F(NetworkStateNotifierTest,
       CellularInvalidApnConnectionFailureApnRevampDisabled) {
  scoped_feature_list_.InitAndDisableFeature(features::kApnRevamp);
  Init();
  TestingBrowserProcess::GetGlobal()->SetSystemNotificationHelper(
      std::make_unique<SystemNotificationHelper>());
  NotificationDisplayServiceTester tester(nullptr /* profile */);
  network_handler_test_helper_->service_test()
      ->SetErrorForNextConnectionAttempt(shill::kErrorInvalidAPN);
  network_handler_test_helper_->service_test()->SetServiceProperty(
      kCellular1ServicePath, shill::kConnectableProperty, base::Value(true));
  network_handler_test_helper_->service_test()->SetServiceProperty(
      kCellular1ServicePath, shill::kErrorProperty,
      base::Value(shill::kErrorInvalidAPN));
  NetworkConnect::Get()->ConnectToNetworkId(kCellular1Guid);
  base::RunLoop().RunUntilIdle();

  // Failure should spawn a notification.
  std::optional<message_center::Notification> notification =
      tester.GetNotification(
          NetworkStateNotifier::kNetworkConnectNotificationId);
  EXPECT_TRUE(notification);

  // Clicking the notification should open the network settings page.
  notification->delegate()->Click(/*button_index=*/std::nullopt,
                                  /*reply=*/std::nullopt);
  EXPECT_EQ(0, test_system_tray_client_.show_apn_subpage_count());
  EXPECT_EQ(1, test_system_tray_client_.show_network_settings_count());
  EXPECT_EQ(kCellular1Guid,
            test_system_tray_client_.last_network_settings_network_id());
}

TEST_F(NetworkStateNotifierTest, CellularCarrierLockedSimConnectionFailure) {
  Init();
  SetCellularDeviceLocked(shill::kSIMLockNetworkPin);
  TestingBrowserProcess::GetGlobal()->SetSystemNotificationHelper(
      std::make_unique<SystemNotificationHelper>());
  NotificationDisplayServiceTester tester(nullptr /* profile */);
  NetworkConnect::Get()->ConnectToNetworkId(kCellular1Guid);
  base::RunLoop().RunUntilIdle();

  // Failure should spawn a notification.
  std::optional<message_center::Notification> notification =
      tester.GetNotification(
          NetworkStateNotifier::kNetworkConnectNotificationId);
  EXPECT_TRUE(notification);
  EXPECT_EQ(
      notification->message(),
      l10n_util::GetStringFUTF16(
          IDS_NETWORK_CONNECTION_ERROR_MESSAGE, kCellular1NetworkName16,
          l10n_util::GetStringUTF16(IDS_NETWORK_LIST_SIM_CARD_CARRIER_LOCKED)));

  // Clicking the notification should open network settings page.
  notification->delegate()->Click(/*button_index=*/std::nullopt,
                                  /*reply=*/std::nullopt);
  EXPECT_EQ(1, test_system_tray_client_.show_network_settings_count());
}

TEST_F(NetworkStateNotifierTest, CellularCarrierUnlockNotification) {
  Init();
  SetupCarrierLock();
  carrier_lock::FakeProvisioningConfigFetcher* config =
      fake_config_fetcher_.get();
  carrier_lock::FakeFcmTopicSubscriber* fcm = fake_fcm_subscriber_.get();
  TestingBrowserProcess::GetGlobal()->SetSystemNotificationHelper(
      std::make_unique<SystemNotificationHelper>());
  NotificationDisplayServiceTester tester(nullptr /* profile */);

  // Set return values for fake auxiliary classes
  fake_modem_handler_->set_carrier_lock_result(CarrierLockResult::kSuccess);
  fake_fcm_subscriber_->SetTokenAndResult(
      /*token*/ std::string("Token"),
      /*result*/ carrier_lock::Result::kSuccess);
  fake_psm_verifier_->SetMemberAndResult(
      /*membership*/ true,
      /*result*/ carrier_lock::Result::kSuccess);
  fake_config_fetcher_->SetConfigTopicAndResult(
      /*configuration*/ std::string("LockedConfig"),
      /*restriction mode*/ ::carrier_lock::DEFAULT_DISALLOW,
      /*fcm topic*/ std::string(kTestTopic),
      /*result*/ carrier_lock::Result::kSuccess);

  // Run Carrier Lock Manager
  RunCarrierLock();
  base::RunLoop().RunUntilIdle();

  // Trigger FCM notification to unlock device
  config->SetConfigTopicAndResult(
      /*configuration*/ std::string("UnlockedConfig"),
      /*restriction mode*/ ::carrier_lock::DEFAULT_ALLOW,
      /*fcm topic*/ std::string(),
      /*result*/ carrier_lock::Result::kSuccess);
  fcm->SendNotification();
  task_environment()->FastForwardBy(carrier_lock::kConfigDelay);
  base::RunLoop().RunUntilIdle();

  // unlock should spawn a notification.
  std::optional<message_center::Notification> notification =
      tester.GetNotification(
          NetworkStateNotifier::kNetworkCarrierUnlockNotificationId);
  EXPECT_TRUE(notification);
  EXPECT_EQ(notification->message(),
            l10n_util::GetStringUTF16(IDS_NETWORK_CARRIER_UNLOCK_BODY));
  // Clicking the notification should open mobile network sub page.
  notification->delegate()->Click(/*button_index=*/std::nullopt,
                                  /*reply=*/std::nullopt);
  EXPECT_EQ(1, test_system_tray_client_.show_mobile_data_subpage_count());
}
}  // namespace ash