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

// Copyright 2021 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/cellular_esim_uninstall_handler.h"

#include <memory>
#include <vector>

#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/dbus/hermes/hermes_clients.h"
#include "chromeos/ash/components/dbus/hermes/hermes_euicc_client.h"
#include "chromeos/ash/components/dbus/hermes/hermes_manager_client.h"
#include "chromeos/ash/components/dbus/shill/shill_clients.h"
#include "chromeos/ash/components/dbus/shill/shill_device_client.h"
#include "chromeos/ash/components/dbus/shill/shill_profile_client.h"
#include "chromeos/ash/components/dbus/shill/shill_service_client.h"
#include "chromeos/ash/components/network/cellular_inhibitor.h"
#include "chromeos/ash/components/network/fake_network_connection_handler.h"
#include "chromeos/ash/components/network/fake_stub_cellular_networks_provider.h"
#include "chromeos/ash/components/network/managed_cellular_pref_handler.h"
#include "chromeos/ash/components/network/network_configuration_handler.h"
#include "chromeos/ash/components/network/network_connection_handler.h"
#include "chromeos/ash/components/network/network_device_handler.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/policy_util.h"
#include "chromeos/ash/components/network/test_cellular_esim_profile_handler.h"
#include "components/prefs/testing_pref_service.h"
#include "dbus/object_path.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace ash {

namespace {

const char kDefaultCellularDevicePath[] = "test_cellular_device";
const char kDefaultEuiccPath[] = "/org/chromium/Hermes/Euicc/0";
const char kDefaultEid[] = "12345678901234567890123456789012";

const char kTestCarrierProfilePath0[] = "/org/chromium/Hermes/Profile/123";
const char kTestNetworkServicePath0[] = "/service/cellular123";
const char kTestCellularIccid0[] = "100000000000000001";
const char kTestCellularActivationCode0[] = "smdp_address0";
const char kTestCellularNetworkName[] = "cellular0";
const char kTestProfileName[] = "TestCellularNetwork";
const char kTestProfileNickname[] = "TestCellularNetworkNick";
const char kTestServiceProvider[] = "Test Wireless";

const char kTestCarrierProfilePath1[] = "/org/chromium/Hermes/Profile/124";
const char kTestNetworkServicePath1[] = "/service/cellular124";
const char kTestCellularIccid1[] = "100000000000000002";
const char kTestCellularActivationCode1[] = "smdp_address1";

}  // namespace

class CellularESimUninstallHandlerTest : public testing::Test {
 public:
  CellularESimUninstallHandlerTest(const CellularESimUninstallHandlerTest&) =
      delete;
  CellularESimUninstallHandlerTest& operator=(
      const CellularESimUninstallHandlerTest&) = delete;

 protected:
  CellularESimUninstallHandlerTest() = default;
  ~CellularESimUninstallHandlerTest() override = default;

  // testing::Test
  void SetUp() override {
    shill_clients::InitializeFakes();
    hermes_clients::InitializeFakes();

    network_state_handler_ = NetworkStateHandler::InitializeForTest();
    network_device_handler_ = NetworkDeviceHandler::InitializeForTesting(
        network_state_handler_.get());
    network_configuration_handler_ =
        NetworkConfigurationHandler::InitializeForTest(
            network_state_handler_.get(), network_device_handler_.get());
    network_connection_handler_ =
        std::make_unique<FakeNetworkConnectionHandler>();
    cellular_inhibitor_ = std::make_unique<CellularInhibitor>();
    cellular_inhibitor_->Init(network_state_handler_.get(),
                              network_device_handler_.get());
    cellular_esim_profile_handler_ =
        std::make_unique<TestCellularESimProfileHandler>();
    cellular_esim_profile_handler_->Init(network_state_handler_.get(),
                                         cellular_inhibitor_.get());
    managed_cellular_pref_handler_ =
        std::make_unique<ManagedCellularPrefHandler>();
    managed_cellular_pref_handler_->Init(network_state_handler_.get());
    ManagedCellularPrefHandler::RegisterLocalStatePrefs(
        device_prefs_.registry());
    managed_cellular_pref_handler_->SetDevicePrefs(&device_prefs_);

    cellular_esim_uninstall_handler_ =
        std::make_unique<CellularESimUninstallHandler>();
    cellular_esim_uninstall_handler_->Init(
        cellular_inhibitor_.get(), cellular_esim_profile_handler_.get(),
        managed_cellular_pref_handler_.get(),
        network_configuration_handler_.get(), network_connection_handler_.get(),
        network_state_handler_.get());

    stub_cellular_networks_provider_ =
        std::make_unique<FakeStubCellularNetworksProvider>();
    network_state_handler_->set_stub_cellular_networks_provider(
        stub_cellular_networks_provider_.get());
  }

  void TearDown() override {
    stub_cellular_networks_provider_.reset();
    cellular_esim_uninstall_handler_.reset();
    cellular_esim_profile_handler_.reset();
    cellular_inhibitor_.reset();
    managed_cellular_pref_handler_.reset();
    network_device_handler_.reset();
    network_state_handler_.reset();
    network_configuration_handler_.reset();
    network_connection_handler_.reset();
    hermes_clients::Shutdown();
    shill_clients::Shutdown();
  }

  void Init(bool is_first_profile_active = true) {
    auto first_profile_state = is_first_profile_active
                                   ? hermes::profile::State::kActive
                                   : hermes::profile::State::kInactive;

    ShillDeviceClient::Get()->GetTestInterface()->AddDevice(
        kDefaultCellularDevicePath, shill::kTypeCellular, "cellular1");
    HermesManagerClient::Get()->GetTestInterface()->AddEuicc(
        dbus::ObjectPath(kDefaultEuiccPath), kDefaultEid, /*is_active=*/true,
        /*physical_slot=*/0);
    HermesEuiccClient::Get()->GetTestInterface()->AddCarrierProfile(
        dbus::ObjectPath(kTestCarrierProfilePath0),
        dbus::ObjectPath(kDefaultEuiccPath), kTestCellularIccid0,
        kTestProfileName, kTestProfileNickname, kTestServiceProvider,
        kTestCellularActivationCode0, kTestNetworkServicePath0,
        first_profile_state, hermes::profile::ProfileClass::kOperational,
        HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
            kAddProfileWithService);

    // Setup as a managed profile by adding eSIM metadata to device prefs.
    managed_cellular_pref_handler_->AddESimMetadata(
        kTestCellularIccid0, kTestCellularNetworkName,
        policy_util::SmdxActivationCode(
            policy_util::SmdxActivationCode::Type::SMDP,
            kTestCellularActivationCode0));

    HermesEuiccClient::Get()->GetTestInterface()->AddCarrierProfile(
        dbus::ObjectPath(kTestCarrierProfilePath1),
        dbus::ObjectPath(kDefaultEuiccPath), kTestCellularIccid1,
        kTestProfileName, kTestProfileNickname, kTestServiceProvider,
        kTestCellularActivationCode1, kTestNetworkServicePath1,
        hermes::profile::State::kInactive,
        hermes::profile::ProfileClass::kOperational,
        HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
            kAddProfileWithService);
    base::RunLoop().RunUntilIdle();

    ShillServiceClient::Get()->GetTestInterface()->SetServiceProperty(
        kTestNetworkServicePath0, shill::kStateProperty,
        base::Value(shill::kStateOnline));
    base::RunLoop().RunUntilIdle();
  }

  void UninstallESim(base::RunLoop& run_loop,
                     const std::string& iccid,
                     const std::string& carrier_profile_path,
                     bool& status) {
    cellular_esim_uninstall_handler_->UninstallESim(
        iccid, dbus::ObjectPath(carrier_profile_path),
        dbus::ObjectPath(kDefaultEuiccPath),
        base::BindLambdaForTesting([&](bool status_result) {
          status = status_result;
          std::move(run_loop.QuitClosure()).Run();
        }));
  }

  void ResetEuiccMemory(base::RunLoop& run_loop, bool& status) {
    cellular_esim_uninstall_handler_->ResetEuiccMemory(
        dbus::ObjectPath(kDefaultEuiccPath),
        base::BindLambdaForTesting([&](bool status_result) {
          status = status_result;
          std::move(run_loop.QuitClosure()).Run();
        }));
  }

  void HandleNetworkDisconnect(bool should_fail) {
    // Run until the uninstallation state hits the disconnect on the
    // FakeNetworkConnectionHandler.
    base::RunLoop().RunUntilIdle();
    EXPECT_LT(0u, network_connection_handler_->disconnect_calls().size());
    if (should_fail) {
      network_connection_handler_->disconnect_calls()
          .front()
          .InvokeErrorCallback("disconnect_error");
    } else {
      network_connection_handler_->disconnect_calls()
          .front()
          .InvokeSuccessCallback();
    }
  }

  bool ESimServiceConfigExists(const std::string& service_path) {
    std::vector<std::string> profile_paths;
    ShillProfileClient::Get()
        ->GetTestInterface()
        ->GetProfilePathsContainingService(service_path, &profile_paths);
    return !profile_paths.empty();
  }

  bool HasESimMetadata(const std::string& iccid) {
    return managed_cellular_pref_handler_->GetESimMetadata(iccid) != nullptr;
  }

  void AddStub(const std::string& stub_iccid, const std::string& eid) {
    stub_cellular_networks_provider_->AddStub(stub_iccid, eid);
    network_state_handler_->SyncStubCellularNetworks();
  }

  void SetHasRefreshedProfiles(bool has_refreshed) {
    cellular_esim_profile_handler_->SetHasRefreshedProfilesForEuicc(
        kDefaultEid, dbus::ObjectPath(kDefaultEuiccPath), has_refreshed);
  }

  void ExpectResult(CellularESimUninstallHandler::UninstallESimResult result,
                    int expected_count = 1) {
    histogram_tester_.ExpectBucketCount(
        "Network.Cellular.ESim.UninstallProfile.OperationResult", result,
        expected_count);
  }

  void FastForward(base::TimeDelta time) {
    task_environment_.FastForwardBy(time);
  }

  uint8_t GetLastServiceCountRemovalForTesting() {
    return cellular_esim_uninstall_handler_
        ->last_service_count_removal_for_testing_;
  }

  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::SingleThreadTaskEnvironment::MainThreadType::UI,
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};

  base::HistogramTester histogram_tester_;
  std::unique_ptr<NetworkStateHandler> network_state_handler_;
  std::unique_ptr<NetworkDeviceHandler> network_device_handler_;
  std::unique_ptr<CellularInhibitor> cellular_inhibitor_;
  std::unique_ptr<TestCellularESimProfileHandler>
      cellular_esim_profile_handler_;
  std::unique_ptr<ManagedCellularPrefHandler> managed_cellular_pref_handler_;
  std::unique_ptr<NetworkConfigurationHandler> network_configuration_handler_;
  std::unique_ptr<FakeNetworkConnectionHandler> network_connection_handler_;
  std::unique_ptr<CellularESimUninstallHandler>
      cellular_esim_uninstall_handler_;
  std::unique_ptr<FakeStubCellularNetworksProvider>
      stub_cellular_networks_provider_;
  TestingPrefServiceSimple device_prefs_;
};

TEST_F(CellularESimUninstallHandlerTest, Success) {
  Init();
  EXPECT_TRUE(ESimServiceConfigExists(kTestNetworkServicePath0));

  base::RunLoop run_loop;
  bool status;
  UninstallESim(run_loop, kTestCellularIccid0, kTestCarrierProfilePath0,
                status);
  HandleNetworkDisconnect(/*should_fail=*/false);
  run_loop.Run();

  // Verify that the esim profile and shill service configuration are removed
  // properly.
  HermesEuiccClient::Properties* euicc_properties =
      HermesEuiccClient::Get()->GetProperties(
          dbus::ObjectPath(kDefaultEuiccPath));
  ASSERT_TRUE(euicc_properties);
  EXPECT_EQ(1u, euicc_properties->profiles().value().size());
  EXPECT_FALSE(ESimServiceConfigExists(kTestNetworkServicePath0));
  EXPECT_TRUE(status);
  EXPECT_FALSE(HasESimMetadata(kTestCellularIccid0));

  ExpectResult(CellularESimUninstallHandler::UninstallESimResult::kSuccess);
}

TEST_F(CellularESimUninstallHandlerTest, Success_AlreadyDisabled) {
  Init(/*is_first_profile_active=*/false);
  EXPECT_TRUE(ESimServiceConfigExists(kTestNetworkServicePath0));

  base::RunLoop run_loop;
  bool status;
  UninstallESim(run_loop, kTestCellularIccid0, kTestCarrierProfilePath0,
                status);
  HandleNetworkDisconnect(/*should_fail=*/false);
  run_loop.Run();

  // Verify that the esim profile and shill service configuration are removed
  // properly.
  HermesEuiccClient::Properties* euicc_properties =
      HermesEuiccClient::Get()->GetProperties(
          dbus::ObjectPath(kDefaultEuiccPath));
  ASSERT_TRUE(euicc_properties);
  EXPECT_EQ(1u, euicc_properties->profiles().value().size());
  EXPECT_FALSE(ESimServiceConfigExists(kTestNetworkServicePath0));
  EXPECT_TRUE(status);
  EXPECT_FALSE(HasESimMetadata(kTestCellularIccid0));

  ExpectResult(CellularESimUninstallHandler::UninstallESimResult::kSuccess);
}

TEST_F(CellularESimUninstallHandlerTest, DisconnectFailure) {
  Init();
  EXPECT_TRUE(ESimServiceConfigExists(kTestNetworkServicePath0));

  bool status;
  base::RunLoop run_loop;
  UninstallESim(run_loop, kTestCellularIccid0, kTestCarrierProfilePath0,
                status);
  HandleNetworkDisconnect(/*should_fail=*/true);
  run_loop.Run();
  EXPECT_FALSE(status);
  EXPECT_TRUE(ESimServiceConfigExists(kTestNetworkServicePath0));
  EXPECT_TRUE(HasESimMetadata(kTestCellularIccid0));

  ExpectResult(
      CellularESimUninstallHandler::UninstallESimResult::kDisconnectFailed);
}

TEST_F(CellularESimUninstallHandlerTest, HermesFailure) {
  Init();
  EXPECT_TRUE(ESimServiceConfigExists(kTestNetworkServicePath0));

  HermesEuiccClient::Get()->GetTestInterface()->QueueHermesErrorStatus(
      HermesResponseStatus::kErrorUnknown);
  bool status;
  base::RunLoop run_loop;
  UninstallESim(run_loop, kTestCellularIccid0, kTestCarrierProfilePath0,
                status);
  HandleNetworkDisconnect(/*should_fail=*/false);
  run_loop.Run();
  EXPECT_FALSE(status);
  EXPECT_TRUE(ESimServiceConfigExists(kTestNetworkServicePath0));
  EXPECT_TRUE(HasESimMetadata(kTestCellularIccid0));

  ExpectResult(CellularESimUninstallHandler::UninstallESimResult::
                   kRefreshProfilesFailed);
}

TEST_F(CellularESimUninstallHandlerTest, MultipleRequests) {
  Init();
  EXPECT_TRUE(ESimServiceConfigExists(kTestNetworkServicePath0));
  EXPECT_TRUE(ESimServiceConfigExists(kTestNetworkServicePath1));

  // Make two uninstall requests back to back.
  bool status1, status2;
  base::RunLoop run_loop1, run_loop2;

  UninstallESim(run_loop1, kTestCellularIccid0, kTestCarrierProfilePath0,
                status1);
  UninstallESim(run_loop2, kTestCellularIccid1, kTestCarrierProfilePath1,
                status2);

  // Only the first profile is connected, so only one disconnect handler is
  // needed.
  HandleNetworkDisconnect(/*should_fail=*/false);

  run_loop1.Run();
  EXPECT_EQ(GetLastServiceCountRemovalForTesting(), 1);

  run_loop2.Run();
  EXPECT_EQ(GetLastServiceCountRemovalForTesting(), 1);

  // Verify that both requests succeeded.
  EXPECT_TRUE(status1);
  EXPECT_TRUE(status2);
  HermesEuiccClient::Properties* euicc_properties =
      HermesEuiccClient::Get()->GetProperties(
          dbus::ObjectPath(kDefaultEuiccPath));
  ASSERT_TRUE(euicc_properties);
  EXPECT_TRUE(euicc_properties->profiles().value().empty());
  EXPECT_FALSE(ESimServiceConfigExists(kTestNetworkServicePath0));
  EXPECT_FALSE(ESimServiceConfigExists(kTestNetworkServicePath1));
  EXPECT_FALSE(HasESimMetadata(kTestCellularIccid0));
  EXPECT_FALSE(HasESimMetadata(kTestCellularIccid1));

  ExpectResult(CellularESimUninstallHandler::UninstallESimResult::kSuccess,
               /*expected_count=*/2);
}

TEST_F(CellularESimUninstallHandlerTest, ResetEuiccMemory) {
  Init();
  EXPECT_TRUE(ESimServiceConfigExists(kTestNetworkServicePath0));
  EXPECT_TRUE(ESimServiceConfigExists(kTestNetworkServicePath1));

  bool status;
  base::RunLoop run_loop;
  ResetEuiccMemory(run_loop, status);

  HandleNetworkDisconnect(/*should_fail=*/false);
  FastForward(CellularESimUninstallHandler::kNetworkListWaitTimeout);

  run_loop.Run();

  // Verify that both profiles were removed successfully.
  EXPECT_TRUE(status);
  HermesEuiccClient::Properties* euicc_properties =
      HermesEuiccClient::Get()->GetProperties(
          dbus::ObjectPath(kDefaultEuiccPath));
  ASSERT_TRUE(euicc_properties);
  EXPECT_TRUE(euicc_properties->profiles().value().empty());
  EXPECT_FALSE(ESimServiceConfigExists(kTestNetworkServicePath0));
  EXPECT_FALSE(ESimServiceConfigExists(kTestNetworkServicePath1));
  EXPECT_FALSE(HasESimMetadata(kTestCellularIccid0));
  EXPECT_FALSE(HasESimMetadata(kTestCellularIccid1));
  EXPECT_EQ(GetLastServiceCountRemovalForTesting(), 2);

  ExpectResult(CellularESimUninstallHandler::UninstallESimResult::kSuccess,
               /*expected_count=*/1);
}

TEST_F(CellularESimUninstallHandlerTest, StubCellularNetwork) {
  Init();

  // Remove shill eSIM service and add a corresponding stub service.
  ShillServiceClient::Get()->GetTestInterface()->RemoveService(
      kTestNetworkServicePath0);
  base::RunLoop().RunUntilIdle();
  AddStub(kTestCellularIccid0, kDefaultEid);

  // Verify that removing the eSIM profile succeeds.
  base::RunLoop run_loop;
  bool success;
  UninstallESim(run_loop, kTestCellularIccid0, kTestCarrierProfilePath0,
                success);
  run_loop.Run();
  EXPECT_TRUE(success);

  ExpectResult(CellularESimUninstallHandler::UninstallESimResult::kSuccess);
}

}  // namespace ash