chromium/chromeos/ash/components/dbus/hermes/hermes_euicc_client_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 "chromeos/ash/components/dbus/hermes/hermes_euicc_client.h"

#include <deque>

#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/dbus/hermes/hermes_client_test_base.h"
#include "chromeos/ash/components/dbus/hermes/hermes_test_utils.h"
#include "dbus/bus.h"
#include "dbus/mock_bus.h"
#include "dbus/mock_object_proxy.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/hermes/dbus-constants.h"

using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;

namespace ash {

namespace {

const char kInvalidPath[] = "/test/invalid/path";
const char kTestActivationCode[] = "abc123";
const char kTestActivationCodeWithSpacing[] = "   abc123   ";
const char kTestConfirmationCode[] = "def456";
const char kTestEuiccPath[] = "/org/chromium/hermes/Euicc/1";
const char kTestCarrierProfilePath[] = "/org/chromium/hermes/Profile/1";
const char kDbusNoResponse[] = "org.freedesktop.DBus.Error.NoReply";

// Matches dbus::MethodCall for UninstallProfile call with given path.
MATCHER_P(MatchUninstallProfileCall, expected_profile_path, "") {
  dbus::MessageReader reader(arg);
  dbus::ObjectPath carrier_profile_path;
  if (arg->GetMember() != hermes::euicc::kUninstallProfile ||
      !reader.PopObjectPath(&carrier_profile_path) ||
      carrier_profile_path != expected_profile_path) {
    *result_listener << "has method_name=" << arg->GetMember()
                     << " carrier_profile_path="
                     << carrier_profile_path.value();
    return false;
  }
  return true;
}

MATCHER_P2(MatchRefreshSmdxProfilesCall,
           expected_activation_code,
           expected_restore_slot,
           "") {
  dbus::MessageReader reader(arg);
  std::string activation_code;
  bool restore_slot;
  if (arg->GetMember() != hermes::euicc::kRefreshSmdxProfiles ||
      !reader.PopString(&activation_code) ||
      activation_code != expected_activation_code ||
      !reader.PopBool(&restore_slot) || restore_slot != expected_restore_slot) {
    *result_listener << "has method_name=" << arg->GetMember()
                     << " activation_code=" << activation_code
                     << " restore_slot=" << restore_slot;
    return false;
  }
  return true;
}

MATCHER_P(MatchRequestPendingProfilesCall, expected_root_smds, "") {
  dbus::MessageReader reader(arg);
  std::string root_smds;
  if (arg->GetMember() != hermes::euicc::kRequestPendingProfiles ||
      !reader.PopString(&root_smds) || root_smds != expected_root_smds) {
    *result_listener << "has method_name=" << arg->GetMember()
                     << " root_smds=" << root_smds;
    return false;
  }
  return true;
}

MATCHER_P(MatchRefreshPendingProfilesCall, expected_restore_slot, "") {
  dbus::MessageReader reader(arg);
  bool restore_slot;
  if (arg->GetMember() != hermes::euicc::kRefreshInstalledProfiles ||
      !reader.PopBool(&restore_slot) || restore_slot != expected_restore_slot) {
    *result_listener << "has method_name=" << arg->GetMember()
                     << " restore_slot=" << restore_slot;
    return false;
  }
  return true;
}

// Matches dbus::MethodCall for ResetMemory call with given
// |expected_reset_option|.
MATCHER_P(MatchResetMemoryCall, expected_reset_option, "") {
  dbus::MessageReader reader(arg);
  int32_t reset_option;
  if (arg->GetMember() != hermes::euicc::kResetMemory ||
      !reader.PopInt32(&reset_option) ||
      reset_option != static_cast<int32_t>(expected_reset_option)) {
    *result_listener << "has method_name=" << arg->GetMember()
                     << " reset_option=" << reset_option;
    return false;
  }
  return true;
}

// Matches dbus::MethodCall for InstrallProfileFromActivationCode call with
// given activation code and confirmation code.
MATCHER_P2(MatchInstallFromActivationCodeCall,
           expected_activation_code,
           expected_confirmation_code,
           "") {
  dbus::MessageReader reader(arg);
  std::string activation_code;
  std::string confirmation_code;
  if (arg->GetMember() != hermes::euicc::kInstallProfileFromActivationCode ||
      !reader.PopString(&activation_code) ||
      activation_code != expected_activation_code ||
      !reader.PopString(&confirmation_code) ||
      confirmation_code != expected_confirmation_code) {
    *result_listener << "has method_name=" << arg->GetMember()
                     << " activation_code=" << activation_code
                     << " confirmation_code=" << confirmation_code;
    return false;
  }
  return true;
}

// Matches dbus::MethodCall for InstallPendingProfile call with given profile
// path and confirmation code.
MATCHER_P2(MatchInstallPendingProfileCall,
           expected_profile_path,
           expected_confirmation_code,
           "") {
  dbus::MessageReader reader(arg);
  dbus::ObjectPath carrier_profile_path;
  std::string confirmation_code;
  if (arg->GetMember() != hermes::euicc::kInstallPendingProfile ||
      !reader.PopObjectPath(&carrier_profile_path) ||
      carrier_profile_path != expected_profile_path ||
      !reader.PopString(&confirmation_code) ||
      confirmation_code != expected_confirmation_code) {
    *result_listener << "has method_name=" << arg->GetMember()
                     << " carrier_profile_path=" << carrier_profile_path.value()
                     << " confirmation_code=" << confirmation_code;
    return false;
  }
  return true;
}

void CopyRefreshSmdxProfilesResult(
    HermesResponseStatus* dest_status,
    std::vector<dbus::ObjectPath>* dest_profiles,
    HermesResponseStatus status,
    const std::vector<dbus::ObjectPath>& profiles) {
  *dest_status = status;
  *dest_profiles = profiles;
}

void CopyInstallResult(HermesResponseStatus* dest_status,
                       dbus::DBusResult* dest_dbus_result,
                       dbus::ObjectPath* dest_path,
                       HermesResponseStatus status,
                       dbus::DBusResult dbus_result,
                       const dbus::ObjectPath* carrier_profile_path) {
  *dest_status = status;
  *dest_dbus_result = dbus_result;
  if (carrier_profile_path) {
    *dest_path = *carrier_profile_path;
  }
}

// Test observer for HermesEuiccClient.
class TestHermesEuiccClientObserver : public HermesEuiccClient::Observer {
 public:
  TestHermesEuiccClientObserver() = default;
  TestHermesEuiccClientObserver(const TestHermesEuiccClientObserver&) = delete;
  ~TestHermesEuiccClientObserver() override = default;

  void OnEuiccReset(const dbus::ObjectPath& euicc_path) override {
    on_euicc_reset_calls_.push_back(euicc_path);
  }

  const std::vector<dbus::ObjectPath>& on_euicc_reset_calls() {
    return on_euicc_reset_calls_;
  }

 private:
  std::vector<dbus::ObjectPath> on_euicc_reset_calls_;
};

}  // namespace

class HermesEuiccClientTest : public HermesClientTestBase {
 public:
  struct HistogramState {
    size_t installation_requested_count = 0u;
    size_t hermes_unavailable_count = 0u;
    size_t installation_started_count = 0u;
    size_t installation_succeeded_count = 0u;
    size_t installation_no_response_count = 0u;
    size_t installation_failed_count = 0u;
  };

  HermesEuiccClientTest() = default;
  HermesEuiccClientTest(const HermesEuiccClientTest&) = delete;
  HermesEuiccClientTest& operator=(const HermesEuiccClientTest&) = delete;
  ~HermesEuiccClientTest() override = default;

  void SetUp() override {
    InitMockBus();

    dbus::ObjectPath euicc_path(kTestEuiccPath);
    proxy_ = new dbus::MockObjectProxy(GetMockBus(), hermes::kHermesServiceName,
                                       euicc_path);
    EXPECT_CALL(*GetMockBus(),
                GetObjectProxy(hermes::kHermesServiceName, euicc_path))
        .WillRepeatedly(Return(proxy_.get()));

    HermesEuiccClient::Initialize(GetMockBus());
    client_ = HermesEuiccClient::Get();
    client_->AddObserver(&test_observer_);

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

  void TearDown() override { HermesEuiccClient::Shutdown(); }

  void CheckHistogramState(const HistogramState& histogram_state) {
    histogram_tester_.ExpectBucketCount(
        HermesEuiccClient::kHermesInstallationAttemptStepsHistogram,
        HermesEuiccClient::InstallationAttemptStep::kInstallationRequested,
        histogram_state.installation_requested_count);
    histogram_tester_.ExpectBucketCount(
        HermesEuiccClient::kHermesInstallationAttemptStepsHistogram,
        HermesEuiccClient::InstallationAttemptStep::kHermesUnavailable,
        histogram_state.hermes_unavailable_count);
    histogram_tester_.ExpectBucketCount(
        HermesEuiccClient::kHermesInstallationAttemptStepsHistogram,
        HermesEuiccClient::InstallationAttemptStep::kInstallationStarted,
        histogram_state.installation_started_count);
    histogram_tester_.ExpectBucketCount(
        HermesEuiccClient::kHermesInstallationAttemptStepsHistogram,
        HermesEuiccClient::InstallationAttemptStep::kInstallationSucceeded,
        histogram_state.installation_succeeded_count);
    histogram_tester_.ExpectBucketCount(
        HermesEuiccClient::kHermesInstallationAttemptStepsHistogram,
        HermesEuiccClient::InstallationAttemptStep::kInstallationNoResponse,
        histogram_state.installation_no_response_count);
    histogram_tester_.ExpectBucketCount(
        HermesEuiccClient::kHermesInstallationAttemptStepsHistogram,
        HermesEuiccClient::InstallationAttemptStep::kInstallationFailed,
        histogram_state.installation_failed_count);
  }

  int MaxInstallAttempts() { return HermesEuiccClient::kMaxInstallAttempts; }

  base::TimeDelta InstallRetryDelay() {
    return HermesEuiccClient::kInstallRetryDelay;
  }

 protected:
  scoped_refptr<dbus::MockObjectProxy> proxy_;

  raw_ptr<HermesEuiccClient, DanglingUntriaged> client_;
  TestHermesEuiccClientObserver test_observer_;
  base::HistogramTester histogram_tester_;
};

TEST_F(HermesEuiccClientTest, TestInstallProfileWhenHermesIsDown) {
  dbus::ObjectPath test_euicc_path(kTestEuiccPath);
  dbus::ObjectPath test_carrier_path(kTestCarrierProfilePath);
  HermesResponseStatus install_status;
  dbus::DBusResult dbus_result;
  dbus::ObjectPath installed_profile_path(kInvalidPath);

  EXPECT_CALL(*proxy_.get(), DoWaitForServiceToBeAvailable(_))
      .WillOnce(testing::WithArg<0>(
          [=](dbus::ObjectProxy::WaitForServiceToBeAvailableCallback*
                  callback) {
            std::move(*callback).Run(/*service_is_available=*/false);
          }));

  HistogramState histogram_state;
  CheckHistogramState(histogram_state);

  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
  dbus::MessageWriter response_writer(response.get());
  response_writer.AppendObjectPath(test_carrier_path);
  AddPendingMethodCallResult(std::move(response), nullptr);
  client_->InstallProfileFromActivationCode(
      test_euicc_path, kTestActivationCode, kTestConfirmationCode,
      base::BindOnce(&CopyInstallResult, &install_status, &dbus_result,
                     &installed_profile_path));

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(install_status, HermesResponseStatus::kErrorWrongState);
  EXPECT_EQ(dbus_result, dbus::DBusResult::kErrorServiceUnknown);

  histogram_state.installation_requested_count++;
  histogram_state.hermes_unavailable_count++;
  CheckHistogramState(histogram_state);
}

TEST_F(HermesEuiccClientTest, TestInstallProfileFromActivationCode) {
  dbus::ObjectPath test_euicc_path(kTestEuiccPath);
  dbus::ObjectPath test_carrier_path(kTestCarrierProfilePath);
  dbus::MethodCall method_call(
      hermes::kHermesEuiccInterface,
      hermes::euicc::kInstallProfileFromActivationCode);
  method_call.SetSerial(123);
  EXPECT_CALL(*proxy_.get(),
              DoCallMethodWithErrorResponse(
                  MatchInstallFromActivationCodeCall(
                      base::TrimWhitespaceASCII(kTestActivationCode,
                                                base::TrimPositions::TRIM_ALL),
                      kTestConfirmationCode),
                  _, _))
      .Times(7)
      .WillRepeatedly(Invoke(this, &HermesEuiccClientTest::OnMethodCalled));

  HermesResponseStatus install_status;
  dbus::DBusResult dbus_result;
  dbus::ObjectPath installed_profile_path(kInvalidPath);

  EXPECT_CALL(*proxy_.get(), DoWaitForServiceToBeAvailable(_))
      .Times(3)
      .WillRepeatedly(testing::WithArg<0>(
          [=](dbus::ObjectProxy::WaitForServiceToBeAvailableCallback*
                  callback) {
            std::move(*callback).Run(/*service_is_available=*/true);
          }));

  HistogramState histogram_state;
  CheckHistogramState(histogram_state);

  // Verify that client makes corresponding dbus method call with
  // correct arguments.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
  dbus::MessageWriter response_writer(response.get());
  response_writer.AppendObjectPath(test_carrier_path);
  AddPendingMethodCallResult(std::move(response), nullptr);
  client_->InstallProfileFromActivationCode(
      test_euicc_path, kTestActivationCode, kTestConfirmationCode,
      base::BindOnce(&CopyInstallResult, &install_status, &dbus_result,
                     &installed_profile_path));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(install_status, HermesResponseStatus::kSuccess);
  EXPECT_EQ(dbus_result, dbus::DBusResult::kSuccess);
  EXPECT_EQ(installed_profile_path, test_carrier_path);

  histogram_state.installation_requested_count++;
  histogram_state.installation_started_count++;
  histogram_state.installation_succeeded_count++;
  CheckHistogramState(histogram_state);

  // Verify that error responses are returned properly.
  installed_profile_path = dbus::ObjectPath(kInvalidPath);
  std::unique_ptr<dbus::ErrorResponse> error_response =
      dbus::ErrorResponse::FromMethodCall(
          &method_call, hermes::kErrorInvalidActivationCode, "");
  AddPendingMethodCallResult(nullptr, std::move(error_response));
  client_->InstallProfileFromActivationCode(
      test_euicc_path, kTestActivationCode, kTestConfirmationCode,
      base::BindOnce(&CopyInstallResult, &install_status, &dbus_result,
                     &installed_profile_path));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(install_status, HermesResponseStatus::kErrorInvalidActivationCode);
  EXPECT_EQ(dbus_result, dbus::DBusResult::kErrorUnknown);
  EXPECT_EQ(installed_profile_path.value(), kInvalidPath);

  histogram_state.installation_requested_count++;
  histogram_state.installation_started_count++;
  histogram_state.installation_failed_count++;
  CheckHistogramState(histogram_state);

  // Verify that dbus errors are captured properly.
  installed_profile_path = dbus::ObjectPath(kInvalidPath);
  for (int i = 0; i <= MaxInstallAttempts(); i++) {
    error_response = dbus::ErrorResponse::FromMethodCall(
        &method_call, DBUS_ERROR_LIMITS_EXCEEDED, "");
    AddPendingMethodCallResult(nullptr, std::move(error_response));
  }
  client_->InstallProfileFromActivationCode(
      test_euicc_path, kTestActivationCode, kTestConfirmationCode,
      base::BindOnce(&CopyInstallResult, &install_status, &dbus_result,
                     &installed_profile_path));
  base::RunLoop loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE, loop.QuitClosure(), InstallRetryDelay() * 5);
  loop.Run();
  EXPECT_EQ(install_status, HermesResponseStatus::kErrorUnknownResponse);
  EXPECT_EQ(dbus_result, dbus::DBusResult::kErrorLimitsExceeded);

  histogram_state.installation_requested_count++;
  histogram_state.installation_started_count++;
  histogram_state.installation_failed_count++;
  CheckHistogramState(histogram_state);
}

// Hermes does not allow leading and trailing whitespace in activation codes; if provided,
// these spaces can result in unexpected behavior e.g. failing to discover available eSIM
// profiles that would have been found had the spaces been removed. All activation codes that
// are provided to Hermes should be sanitized, and this test ensures we correctly handle any
// activation codes with leading and/or trailing whitespace.
TEST_F(HermesEuiccClientTest, TestInstallProfileFromActivationCodeWithSpacing) {
  dbus::ObjectPath test_euicc_path(kTestEuiccPath);
  dbus::ObjectPath test_carrier_path(kTestCarrierProfilePath);
  dbus::MethodCall method_call(
      hermes::kHermesEuiccInterface,
      hermes::euicc::kInstallProfileFromActivationCode);
  method_call.SetSerial(123);
  EXPECT_CALL(*proxy_.get(),
              DoCallMethodWithErrorResponse(
                  MatchInstallFromActivationCodeCall(
                      base::TrimWhitespaceASCII(kTestActivationCodeWithSpacing,
                                                base::TrimPositions::TRIM_ALL),
                      kTestConfirmationCode),
                  _, _))
      .WillOnce(Invoke(this, &HermesEuiccClientTest::OnMethodCalled));

  HermesResponseStatus install_status;
  dbus::DBusResult dbus_result;
  dbus::ObjectPath installed_profile_path(kInvalidPath);

  EXPECT_CALL(*proxy_.get(), DoWaitForServiceToBeAvailable(_))
      .WillOnce(testing::WithArg<0>(
          [=](dbus::ObjectProxy::WaitForServiceToBeAvailableCallback*
                  callback) {
            std::move(*callback).Run(/*service_is_available=*/true);
          }));

  // Verify that client makes corresponding dbus method call with
  // correct arguments.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
  dbus::MessageWriter response_writer(response.get());
  response_writer.AppendObjectPath(test_carrier_path);
  AddPendingMethodCallResult(std::move(response), nullptr);
  client_->InstallProfileFromActivationCode(
      test_euicc_path, kTestActivationCodeWithSpacing, kTestConfirmationCode,
      base::BindOnce(&CopyInstallResult, &install_status, &dbus_result,
                     &installed_profile_path));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(install_status, HermesResponseStatus::kSuccess);
  EXPECT_EQ(dbus_result, dbus::DBusResult::kSuccess);
  EXPECT_EQ(installed_profile_path, test_carrier_path);
}

TEST_F(HermesEuiccClientTest, TestInstallPendingProfile) {
  dbus::ObjectPath test_euicc_path(kTestEuiccPath);
  dbus::ObjectPath test_carrier_path(kTestCarrierProfilePath);
  dbus::MethodCall method_call(hermes::kHermesEuiccInterface,
                               hermes::euicc::kInstallPendingProfile);
  method_call.SetSerial(123);
  EXPECT_CALL(*proxy_.get(), DoCallMethodWithErrorResponse(
                                 MatchInstallPendingProfileCall(
                                     test_carrier_path, kTestConfirmationCode),
                                 _, _))
      .Times(2)
      .WillRepeatedly(Invoke(this, &HermesEuiccClientTest::OnMethodCalled));

  HermesResponseStatus install_status;

  // Verify that client makes corresponding dbus method call with
  // correct arguments.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
  dbus::MessageWriter response_writer(response.get());
  response_writer.AppendObjectPath(test_carrier_path);
  AddPendingMethodCallResult(std::move(response), nullptr);
  client_->InstallPendingProfile(
      test_euicc_path, test_carrier_path, kTestConfirmationCode,
      base::BindOnce(&hermes_test_utils::CopyHermesStatus, &install_status));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(install_status, HermesResponseStatus::kSuccess);

  // Verify that error responses are returned properly.
  std::unique_ptr<dbus::ErrorResponse> error_response =
      dbus::ErrorResponse::FromMethodCall(&method_call,
                                          hermes::kErrorInvalidParameter, "");
  AddPendingMethodCallResult(nullptr, std::move(error_response));
  client_->InstallPendingProfile(
      test_euicc_path, test_carrier_path, kTestConfirmationCode,
      base::BindOnce(&hermes_test_utils::CopyHermesStatus, &install_status));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(install_status, HermesResponseStatus::kErrorInvalidParameter);
}

TEST_F(HermesEuiccClientTest, TestRefreshInstalledProfiles) {
  dbus::ObjectPath test_euicc_path(kTestEuiccPath);
  dbus::MethodCall method_call(hermes::kHermesEuiccInterface,
                               hermes::euicc::kRefreshInstalledProfiles);
  method_call.SetSerial(123);
  EXPECT_CALL(*proxy_.get(), DoCallMethodWithErrorResponse(
                                 MatchRefreshPendingProfilesCall(
                                     /*expected_restore_slot=*/false),
                                 _, _))
      .Times(2)
      .WillRepeatedly(Invoke(this, &HermesEuiccClientTest::OnMethodCalled));

  HermesResponseStatus status;

  // Verify that client makes corresponding dbus method call with
  // correct arguments.
  AddPendingMethodCallResult(dbus::Response::CreateEmpty(), nullptr);
  client_->RefreshInstalledProfiles(
      test_euicc_path,
      /*restore_slot=*/false,
      base::BindOnce(&hermes_test_utils::CopyHermesStatus, &status));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(status, HermesResponseStatus::kSuccess);

  // Verify that error responses are returned properly.
  std::unique_ptr<dbus::ErrorResponse> error_response =
      dbus::ErrorResponse::FromMethodCall(&method_call, hermes::kErrorUnknown,
                                          "");
  AddPendingMethodCallResult(nullptr, std::move(error_response));
  client_->RefreshInstalledProfiles(
      test_euicc_path,
      /*restore_slot=*/false,
      base::BindOnce(&hermes_test_utils::CopyHermesStatus, &status));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(status, HermesResponseStatus::kErrorUnknown);
}

TEST_F(HermesEuiccClientTest, TestRefreshSmdxProfiles) {

  dbus::ObjectPath test_euicc_path(kTestEuiccPath);
  dbus::MethodCall method_call(hermes::kHermesEuiccInterface,
                               hermes::euicc::kRefreshSmdxProfiles);
  method_call.SetSerial(123);
  EXPECT_CALL(*proxy_.get(),
              DoCallMethodWithErrorResponse(
                  MatchRefreshSmdxProfilesCall(
                      base::TrimWhitespaceASCII(kTestActivationCode,
                                                base::TrimPositions::TRIM_ALL),
                      /*expected_restore_slot=*/true),
                  _, _))
      .Times(2)
      .WillRepeatedly(Invoke(this, &HermesEuiccClientTest::OnMethodCalled));

  HermesResponseStatus status;
  std::vector<dbus::ObjectPath> profiles;

  const std::vector<dbus::ObjectPath> kProfilesToReturn{
      {dbus::ObjectPath(kTestCarrierProfilePath)}};

  // Verify that client makes corresponding dbus method call with
  // correct arguments.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
  dbus::MessageWriter response_writer(response.get());
  response_writer.AppendArrayOfObjectPaths(kProfilesToReturn);
  AddPendingMethodCallResult(std::move(response), nullptr);
  client_->RefreshSmdxProfiles(
      test_euicc_path, kTestActivationCode, /*restore_slot=*/true,
      base::BindOnce(CopyRefreshSmdxProfilesResult, &status, &profiles));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(status, HermesResponseStatus::kSuccess);
  ASSERT_EQ(profiles.size(), 1u);
  EXPECT_EQ(profiles.front(), kProfilesToReturn.front());

  // Verify that error responses are returned properly.
  std::unique_ptr<dbus::ErrorResponse> error_response =
      dbus::ErrorResponse::FromMethodCall(&method_call,
                                          hermes::kErrorNoResponse, "");
  AddPendingMethodCallResult(nullptr, std::move(error_response));
  client_->RefreshSmdxProfiles(
      test_euicc_path, kTestActivationCode, /*restore_slot=*/true,
      base::BindOnce(CopyRefreshSmdxProfilesResult, &status, &profiles));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(status, HermesResponseStatus::kErrorNoResponse);
  EXPECT_TRUE(profiles.empty());
}

TEST_F(HermesEuiccClientTest, TestRequestPendingProfiles) {
  dbus::ObjectPath test_euicc_path(kTestEuiccPath);
  dbus::MethodCall method_call(hermes::kHermesEuiccInterface,
                               hermes::euicc::kRequestPendingProfiles);
  method_call.SetSerial(123);
  EXPECT_CALL(*proxy_.get(),
              DoCallMethodWithErrorResponse(
                  MatchRequestPendingProfilesCall(kTestActivationCode), _, _))
      .Times(2)
      .WillRepeatedly(Invoke(this, &HermesEuiccClientTest::OnMethodCalled));

  HermesResponseStatus status;

  // Verify that client makes corresponding dbus method call with
  // correct arguments.
  AddPendingMethodCallResult(dbus::Response::CreateEmpty(), nullptr);
  client_->RequestPendingProfiles(
      test_euicc_path, kTestActivationCode,
      base::BindOnce(&hermes_test_utils::CopyHermesStatus, &status));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(status, HermesResponseStatus::kSuccess);

  // Verify that error responses are returned properly.
  std::unique_ptr<dbus::ErrorResponse> error_response =
      dbus::ErrorResponse::FromMethodCall(&method_call, hermes::kErrorUnknown,
                                          "");
  AddPendingMethodCallResult(nullptr, std::move(error_response));
  client_->RequestPendingProfiles(
      test_euicc_path, kTestActivationCode,
      base::BindOnce(&hermes_test_utils::CopyHermesStatus, &status));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(status, HermesResponseStatus::kErrorUnknown);
}

TEST_F(HermesEuiccClientTest, TestUninstallProfile) {
  dbus::ObjectPath test_euicc_path(kTestEuiccPath);
  dbus::ObjectPath test_carrier_path(kTestCarrierProfilePath);
  dbus::MethodCall method_call(hermes::kHermesEuiccInterface,
                               hermes::euicc::kUninstallProfile);
  method_call.SetSerial(123);
  EXPECT_CALL(*proxy_.get(),
              DoCallMethodWithErrorResponse(
                  MatchUninstallProfileCall(test_carrier_path), _, _))
      .Times(2)
      .WillRepeatedly(Invoke(this, &HermesEuiccClientTest::OnMethodCalled));

  HermesResponseStatus status;

  // Verify that client makes corresponding dbus method call with
  // correct arguments.
  AddPendingMethodCallResult(dbus::Response::CreateEmpty(), nullptr);
  client_->UninstallProfile(
      test_euicc_path, test_carrier_path,
      base::BindOnce(&hermes_test_utils::CopyHermesStatus, &status));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(status, HermesResponseStatus::kSuccess);

  // Verify that error responses are returned properly.
  std::unique_ptr<dbus::ErrorResponse> error_response =
      dbus::ErrorResponse::FromMethodCall(&method_call, hermes::kErrorUnknown,
                                          "");
  AddPendingMethodCallResult(nullptr, std::move(error_response));
  client_->UninstallProfile(
      test_euicc_path, test_carrier_path,
      base::BindOnce(&hermes_test_utils::CopyHermesStatus, &status));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(status, HermesResponseStatus::kErrorUnknown);
}

TEST_F(HermesEuiccClientTest, TestResetMemory) {
  const hermes::euicc::ResetOptions kTestResetOption =
      hermes::euicc::ResetOptions::kDeleteOperationalProfiles;
  dbus::ObjectPath test_euicc_path(kTestEuiccPath);
  dbus::MethodCall method_call(hermes::kHermesEuiccInterface,
                               hermes::euicc::kResetMemory);

  method_call.SetSerial(123);
  EXPECT_CALL(*proxy_.get(), DoCallMethodWithErrorResponse(
                                 MatchResetMemoryCall(kTestResetOption), _, _))
      .Times(3)
      .WillRepeatedly(Invoke(this, &HermesEuiccClientTest::OnMethodCalled));

  HermesResponseStatus status;
  const std::vector<dbus::ObjectPath>& on_euicc_reset_calls =
      test_observer_.on_euicc_reset_calls();

  // Verify that client makes corresponding dbus method call with
  // correct arguments.
  AddPendingMethodCallResult(dbus::Response::CreateEmpty(), nullptr);
  client_->ResetMemory(
      test_euicc_path, kTestResetOption,
      base::BindOnce(&hermes_test_utils::CopyHermesStatus, &status));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(status, HermesResponseStatus::kSuccess);
  EXPECT_EQ(1u, on_euicc_reset_calls.size());
  EXPECT_EQ(test_euicc_path, on_euicc_reset_calls.front());

  // Verify that error responses are returned properly.
  std::unique_ptr<dbus::ErrorResponse> error_response =
      dbus::ErrorResponse::FromMethodCall(&method_call, hermes::kErrorUnknown,
                                          "");
  AddPendingMethodCallResult(nullptr, std::move(error_response));
  client_->ResetMemory(
      test_euicc_path, kTestResetOption,
      base::BindOnce(&hermes_test_utils::CopyHermesStatus, &status));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(status, HermesResponseStatus::kErrorUnknown);
  EXPECT_EQ(1u, on_euicc_reset_calls.size());

  error_response =
      dbus::ErrorResponse::FromMethodCall(&method_call, kDbusNoResponse, "");
  AddPendingMethodCallResult(nullptr, std::move(error_response));
  client_->ResetMemory(
      test_euicc_path, kTestResetOption,
      base::BindOnce(&hermes_test_utils::CopyHermesStatus, &status));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(status, HermesResponseStatus::kErrorNoResponse);
  EXPECT_EQ(1u, on_euicc_reset_calls.size());
}

}  // namespace ash