chromium/chromeos/ash/services/cellular_setup/esim_profile_unittest.cc

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

#include <string>

#include "ash/constants/ash_features.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/ash/components/dbus/hermes/hermes_euicc_client.h"
#include "chromeos/ash/components/dbus/hermes/hermes_profile_client.h"
#include "chromeos/ash/components/network/fake_network_connection_handler.h"
#include "chromeos/ash/services/cellular_setup/esim_test_base.h"
#include "chromeos/ash/services/cellular_setup/esim_test_utils.h"
#include "chromeos/ash/services/cellular_setup/public/mojom/esim_manager.mojom-shared.h"
#include "components/user_manager/fake_user_manager.h"
#include "mojo/public/cpp/bindings/pending_remote.h"

namespace ash::cellular_setup {

namespace {

const char kProfileUninstallationResultHistogram[] =
    "Network.Cellular.ESim.ProfileUninstallationResult";
const char kProfileRenameResultHistogram[] =
    "Network.Cellular.ESim.ProfileRenameResult";
const char kPendingProfileLatencyHistogram[] =
    "Network.Cellular.ESim.ProfileDownload.PendingProfile.Latency";
const char kPendingProfileInstallHistogram[] =
    "Network.Cellular.ESim.InstallPendingProfile.Result";

class TestUserManager : public user_manager::FakeUserManager {
 public:
  explicit TestUserManager(bool is_guest) : is_guest_(is_guest) {
    user_manager::UserManager::SetInstance(this);
  }
  ~TestUserManager() override = default;

  // user_manager::UserManager:
  bool IsLoggedInAsGuest() const override { return is_guest_; }

 private:
  const bool is_guest_;
};

mojom::ESimOperationResult UninstallProfile(
    const mojo::Remote<mojom::ESimProfile>& esim_profile) {
  mojom::ESimOperationResult uninstall_result;

  base::RunLoop run_loop;
  esim_profile->UninstallProfile(base::BindOnce(
      [](mojom::ESimOperationResult* out_uninstall_result,
         base::OnceClosure quit_closure,
         mojom::ESimOperationResult uninstall_result) {
        *out_uninstall_result = uninstall_result;
        std::move(quit_closure).Run();
      },
      &uninstall_result, run_loop.QuitClosure()));
  run_loop.Run();
  return uninstall_result;
}

mojom::ESimOperationResult SetProfileNickname(
    const mojo::Remote<mojom::ESimProfile>& esim_profile,
    const std::u16string& nickname) {
  mojom::ESimOperationResult result;

  base::RunLoop run_loop;
  esim_profile->SetProfileNickname(
      nickname, base::BindOnce(
                    [](mojom::ESimOperationResult* out_result,
                       base::OnceClosure quit_closure,
                       mojom::ESimOperationResult result) {
                      *out_result = result;
                      std::move(quit_closure).Run();
                    },
                    &result, run_loop.QuitClosure()));
  run_loop.Run();
  return result;
}

}  // namespace

class ESimProfileTest : public ESimTestBase {
 public:
  ESimProfileTest() = default;
  ESimProfileTest(const ESimProfileTest&) = delete;
  ESimProfileTest& operator=(const ESimProfileTest&) = delete;

  void SetUp() override {
    ESimTestBase::SetUp();
    SetupEuicc();
  }

  void TearDown() override {
    HermesProfileClient::Get()->GetTestInterface()->SetEnableProfileBehavior(
        HermesProfileClient::TestInterface::EnableProfileBehavior::
            kConnectableButNotConnected);
  }

  void SetIsGuest(bool is_guest) {
    test_user_manager_ = std::make_unique<TestUserManager>(is_guest);
  }

  mojo::Remote<mojom::ESimProfile> GetESimProfileForIccid(
      const std::string& eid,
      const std::string& iccid) {
    mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(eid);
    if (!euicc.is_bound()) {
      return mojo::Remote<mojom::ESimProfile>();
    }
    std::vector<mojo::PendingRemote<mojom::ESimProfile>>
        profile_pending_remotes = GetProfileList(euicc);
    for (auto& profile_pending_remote : profile_pending_remotes) {
      mojo::Remote<mojom::ESimProfile> esim_profile(
          std::move(profile_pending_remote));
      mojom::ESimProfilePropertiesPtr profile_properties =
          GetESimProfileProperties(esim_profile);
      if (profile_properties->iccid == iccid) {
        return esim_profile;
      }
    }
    return mojo::Remote<mojom::ESimProfile>();
  }

  mojom::ProfileInstallResult InstallProfile(
      const mojo::Remote<mojom::ESimProfile>& esim_profile,
      bool wait_for_connect,
      bool fail_connect) {
    mojom::ProfileInstallResult out_install_result;

    base::RunLoop run_loop;
    esim_profile->InstallProfile(
        /*confirmation_code=*/std::string(),
        base::BindLambdaForTesting(
            [&](mojom::ProfileInstallResult install_result) {
              out_install_result = install_result;
              run_loop.Quit();
            }));

    FastForwardProfileRefreshDelay();
    FastForwardAutoConnectWaiting();

    if (wait_for_connect) {
      base::RunLoop().RunUntilIdle();
      EXPECT_LE(1u, network_connection_handler()->connect_calls().size());
      if (fail_connect) {
        network_connection_handler()
            ->connect_calls()
            .back()
            .InvokeErrorCallback("fake_error_name");
      } else {
        network_connection_handler()
            ->connect_calls()
            .back()
            .InvokeSuccessCallback();
      }
    }

    run_loop.Run();
    return out_install_result;
  }

 private:
  base::test::ScopedFeatureList feature_list_;
  std::unique_ptr<TestUserManager> test_user_manager_;
};

TEST_F(ESimProfileTest, GetProperties) {
  SetIsGuest(false);

  HermesEuiccClient::TestInterface* euicc_test =
      HermesEuiccClient::Get()->GetTestInterface();
  dbus::ObjectPath profile_path = euicc_test->AddFakeCarrierProfile(
      dbus::ObjectPath(ESimTestBase::kTestEuiccPath),
      hermes::profile::State::kPending, "",
      HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
          kAddProfileWithService);
  base::RunLoop().RunUntilIdle();
  HermesProfileClient::Properties* dbus_properties =
      HermesProfileClient::Get()->GetProperties(profile_path);

  mojo::Remote<mojom::ESimProfile> esim_profile = GetESimProfileForIccid(
      ESimTestBase::kTestEid, dbus_properties->iccid().value());
  ASSERT_TRUE(esim_profile.is_bound());
  mojom::ESimProfilePropertiesPtr mojo_properties =
      GetESimProfileProperties(esim_profile);
  EXPECT_EQ(dbus_properties->iccid().value(), mojo_properties->iccid);
}

TEST_F(ESimProfileTest, InstallProfile) {
  SetIsGuest(false);
  base::HistogramTester histogram_tester;

  HermesEuiccClient::TestInterface* euicc_test =
      HermesEuiccClient::Get()->GetTestInterface();
  dbus::ObjectPath profile_path = euicc_test->AddFakeCarrierProfile(
      dbus::ObjectPath(ESimTestBase::kTestEuiccPath),
      hermes::profile::State::kPending, "",
      HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
          kAddProfileWithService);
  base::RunLoop().RunUntilIdle();
  HermesProfileClient::Properties* dbus_properties =
      HermesProfileClient::Get()->GetProperties(profile_path);

  // Verify that install errors return error code properly.
  euicc_test->QueueHermesErrorStatus(
      HermesResponseStatus::kErrorNeedConfirmationCode);
  mojo::Remote<mojom::ESimProfile> esim_profile = GetESimProfileForIccid(
      ESimTestBase::kTestEid, dbus_properties->iccid().value());
  ASSERT_TRUE(esim_profile.is_bound());
  mojom::ProfileInstallResult install_result = InstallProfile(
      esim_profile, /*wait_for_connect=*/false, /*fail_connect=*/false);
  EXPECT_EQ(mojom::ProfileInstallResult::kErrorNeedsConfirmationCode,
            install_result);

  histogram_tester.ExpectTotalCount(kPendingProfileLatencyHistogram, 0);
  histogram_tester.ExpectBucketCount(
      kPendingProfileInstallHistogram,
      HermesResponseStatus::kErrorNeedConfirmationCode,
      /*expected_count=*/1);

  // Adding a pending profile causes a list change.
  EXPECT_EQ(1u, observer()->profile_list_change_calls().size());

  // Verify that installing pending profile returns proper results
  // and updates esim_profile properties.
  install_result = InstallProfile(esim_profile, /*wait_for_connect=*/true,
                                  /*fail_connect=*/false);
  // Wait for property changes to propagate.
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(mojom::ProfileInstallResult::kSuccess, install_result);
  mojom::ESimProfilePropertiesPtr mojo_properties =
      GetESimProfileProperties(esim_profile);
  EXPECT_EQ(dbus_properties->iccid().value(), mojo_properties->iccid);
  EXPECT_NE(mojo_properties->state, mojom::ProfileState::kPending);

  // Installing a profile causes a list change.
  EXPECT_EQ(2u, observer()->profile_list_change_calls().size());

  histogram_tester.ExpectTotalCount(kPendingProfileLatencyHistogram, 1);
  histogram_tester.ExpectBucketCount(kPendingProfileInstallHistogram,
                                     HermesResponseStatus::kSuccess,
                                     /*expected_count=*/1);
}

TEST_F(ESimProfileTest, InstallProfileAlreadyConnected) {
  SetIsGuest(false);

  dbus::ObjectPath profile_path =
      HermesEuiccClient::Get()->GetTestInterface()->AddFakeCarrierProfile(
          dbus::ObjectPath(ESimTestBase::kTestEuiccPath),
          hermes::profile::State::kPending, /*activation_code=*/std::string(),
          HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
              kAddProfileWithService);
  HermesProfileClient::Properties* dbus_properties =
      HermesProfileClient::Get()->GetProperties(profile_path);
  mojo::Remote<mojom::ESimProfile> esim_profile = GetESimProfileForIccid(
      ESimTestBase::kTestEid, dbus_properties->iccid().value());

  HermesProfileClient::Get()->GetTestInterface()->SetEnableProfileBehavior(
      HermesProfileClient::TestInterface::EnableProfileBehavior::
          kConnectableAndConnected);

  mojom::ProfileInstallResult install_result =
      InstallProfile(esim_profile, /*wait_for_connect=*/false,
                     /*fail_connect=*/false);
  EXPECT_EQ(mojom::ProfileInstallResult::kSuccess, install_result);
}

TEST_F(ESimProfileTest, InstallConnectFailure) {
  SetIsGuest(false);

  HermesEuiccClient::TestInterface* euicc_test =
      HermesEuiccClient::Get()->GetTestInterface();
  dbus::ObjectPath profile_path = euicc_test->AddFakeCarrierProfile(
      dbus::ObjectPath(ESimTestBase::kTestEuiccPath),
      hermes::profile::State::kPending, "",
      HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
          kAddProfileWithService);
  base::RunLoop().RunUntilIdle();
  HermesProfileClient::Properties* dbus_properties =
      HermesProfileClient::Get()->GetProperties(profile_path);
  mojo::Remote<mojom::ESimProfile> esim_profile = GetESimProfileForIccid(
      ESimTestBase::kTestEid, dbus_properties->iccid().value());

  // Verify that connect failures still return success code.
  mojom::ProfileInstallResult install_result =
      InstallProfile(esim_profile, /*wait_for_connect=*/true,
                     /*fail_connect=*/true);
  EXPECT_EQ(mojom::ProfileInstallResult::kSuccess, install_result);
}

TEST_F(ESimProfileTest, UninstallProfile) {
  SetIsGuest(false);

  base::HistogramTester histogram_tester;

  HermesEuiccClient::TestInterface* euicc_test =
      HermesEuiccClient::Get()->GetTestInterface();
  dbus::ObjectPath active_profile_path = euicc_test->AddFakeCarrierProfile(
      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kActive, "",
      HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
          kAddProfileWithService);
  dbus::ObjectPath pending_profile_path = euicc_test->AddFakeCarrierProfile(
      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kPending, "",
      HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
          kAddProfileWithService);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1u, observer()->profile_list_change_calls().size());
  observer()->Reset();
  HermesProfileClient::Properties* pending_profile_dbus_properties =
      HermesProfileClient::Get()->GetProperties(pending_profile_path);
  HermesProfileClient::Properties* active_profile_dbus_properties =
      HermesProfileClient::Get()->GetProperties(active_profile_path);
  histogram_tester.ExpectTotalCount(kProfileUninstallationResultHistogram, 0);

  // Verify that uninstall error codes are returned properly.
  euicc_test->QueueHermesErrorStatus(
      HermesResponseStatus::kErrorInvalidResponse);
  mojo::Remote<mojom::ESimProfile> active_esim_profile = GetESimProfileForIccid(
      ESimTestBase::kTestEid, active_profile_dbus_properties->iccid().value());
  ASSERT_TRUE(active_esim_profile.is_bound());
  mojom::ESimOperationResult result = UninstallProfile(active_esim_profile);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(mojom::ESimOperationResult::kFailure, result);
  EXPECT_EQ(0u, observer()->profile_list_change_calls().size());
  histogram_tester.ExpectTotalCount(kProfileUninstallationResultHistogram, 1);
  histogram_tester.ExpectBucketCount(kProfileUninstallationResultHistogram,
                                     false, 1);

  // Verify that pending profiles cannot be uninstalled
  observer()->Reset();
  mojo::Remote<mojom::ESimProfile> pending_esim_profile =
      GetESimProfileForIccid(ESimTestBase::kTestEid,
                             pending_profile_dbus_properties->iccid().value());
  ASSERT_TRUE(pending_esim_profile.is_bound());
  result = UninstallProfile(pending_esim_profile);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(mojom::ESimOperationResult::kFailure, result);
  EXPECT_EQ(0u, observer()->profile_list_change_calls().size());
  histogram_tester.ExpectTotalCount(kProfileUninstallationResultHistogram, 1);
  histogram_tester.ExpectBucketCount(kProfileUninstallationResultHistogram,
                                     false, 1);

  // Verify that uninstall removes the profile and notifies observers properly.
  observer()->Reset();
  result = UninstallProfile(active_esim_profile);

  // The state change of the ESim profile from kActive to kInactive causes a
  // list change observer event.
  ASSERT_EQ(1u, observer()->profile_list_change_calls().size());

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(mojom::ESimOperationResult::kSuccess, result);

  // The removal of the ESim profile causes a list change observer event.
  ASSERT_EQ(2u, observer()->profile_list_change_calls().size());

  EXPECT_EQ(1u, GetProfileList(GetEuiccForEid(ESimTestBase::kTestEid)).size());
  histogram_tester.ExpectTotalCount(kProfileUninstallationResultHistogram, 2);
  histogram_tester.ExpectBucketCount(kProfileUninstallationResultHistogram,
                                     true, 1);
}

TEST_F(ESimProfileTest, CannotUninstallProfileAsGuest) {
  SetIsGuest(true);

  HermesEuiccClient::TestInterface* euicc_test =
      HermesEuiccClient::Get()->GetTestInterface();
  dbus::ObjectPath active_profile_path = euicc_test->AddFakeCarrierProfile(
      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kActive, "",
      HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
          kAddProfileWithService);
  HermesProfileClient::Properties* active_profile_dbus_properties =
      HermesProfileClient::Get()->GetProperties(active_profile_path);
  mojo::Remote<mojom::ESimProfile> active_esim_profile = GetESimProfileForIccid(
      ESimTestBase::kTestEid, active_profile_dbus_properties->iccid().value());

  mojom::ESimOperationResult result = UninstallProfile(active_esim_profile);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(mojom::ESimOperationResult::kFailure, result);
}

TEST_F(ESimProfileTest, SetProfileNickName) {
  SetIsGuest(false);

  const std::u16string test_nickname = u"Test nickname";
  base::HistogramTester histogram_tester;

  HermesEuiccClient::TestInterface* euicc_test =
      HermesEuiccClient::Get()->GetTestInterface();
  dbus::ObjectPath active_profile_path = euicc_test->AddFakeCarrierProfile(
      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kActive, "",
      HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
          kAddProfileWithService);
  dbus::ObjectPath pending_profile_path = euicc_test->AddFakeCarrierProfile(
      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kPending, "",
      HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
          kAddProfileWithService);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1u, observer()->profile_list_change_calls().size());
  observer()->Reset();
  HermesProfileClient::Properties* pending_profile_dbus_properties =
      HermesProfileClient::Get()->GetProperties(pending_profile_path);
  HermesProfileClient::Properties* active_profile_dbus_properties =
      HermesProfileClient::Get()->GetProperties(active_profile_path);

  // Verify that pending profiles cannot be modified.
  mojo::Remote<mojom::ESimProfile> pending_esim_profile =
      GetESimProfileForIccid(ESimTestBase::kTestEid,
                             pending_profile_dbus_properties->iccid().value());
  ASSERT_TRUE(pending_esim_profile.is_bound());
  mojom::ESimOperationResult result =
      SetProfileNickname(pending_esim_profile, test_nickname);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(mojom::ESimOperationResult::kFailure, result);
  EXPECT_EQ(0u, observer()->profile_change_calls().size());

  // Verify that nickname can be set on active profiles.
  histogram_tester.ExpectTotalCount(kProfileRenameResultHistogram, 0);
  mojo::Remote<mojom::ESimProfile> active_esim_profile = GetESimProfileForIccid(
      ESimTestBase::kTestEid, active_profile_dbus_properties->iccid().value());
  ASSERT_TRUE(active_esim_profile.is_bound());
  result = SetProfileNickname(active_esim_profile, test_nickname);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(mojom::ESimOperationResult::kSuccess, result);
  histogram_tester.ExpectTotalCount(kProfileRenameResultHistogram, 1);
  histogram_tester.ExpectBucketCount(kProfileRenameResultHistogram, true, 1);

  mojom::ESimProfilePropertiesPtr active_profile_mojo_properties =
      GetESimProfileProperties(active_esim_profile);
  EXPECT_EQ(test_nickname, active_profile_mojo_properties->nickname);
}

TEST_F(ESimProfileTest, CannotSetProfileNickNameAsGuest) {
  SetIsGuest(true);

  HermesEuiccClient::TestInterface* euicc_test =
      HermesEuiccClient::Get()->GetTestInterface();
  dbus::ObjectPath active_profile_path = euicc_test->AddFakeCarrierProfile(
      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kActive, "",
      HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
          kAddProfileWithService);
  HermesProfileClient::Properties* active_profile_dbus_properties =
      HermesProfileClient::Get()->GetProperties(active_profile_path);
  mojo::Remote<mojom::ESimProfile> active_esim_profile = GetESimProfileForIccid(
      ESimTestBase::kTestEid, active_profile_dbus_properties->iccid().value());

  mojom::ESimOperationResult result =
      SetProfileNickname(active_esim_profile, u"Nickname");
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(mojom::ESimOperationResult::kFailure, result);
}

}  // namespace ash::cellular_setup