chromium/chromeos/ash/components/dbus/rmad/rmad_client_unittest.cc

// Copyright 2016 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/rmad/rmad_client.h"

#include <memory>
#include <optional>
#include <string>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "dbus/message.h"
#include "dbus/mock_bus.h"
#include "dbus/mock_object_proxy.h"
#include "dbus/object_path.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

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

namespace ash {

namespace {

// Matcher that verifies that a dbus::Message has member |name|.
MATCHER_P(HasMember, name, "") {
  if (arg->GetMember() != name) {
    *result_listener << "has member " << arg->GetMember();
    return false;
  }
  return true;
}

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

  void SetUp() override {
    // Create a mock bus.
    dbus::Bus::Options options;
    options.bus_type = dbus::Bus::SYSTEM;
    mock_bus_ = new dbus::MockBus(options);

    // Create a mock rmad daemon proxy.
    mock_proxy_ =
        new dbus::MockObjectProxy(mock_bus_.get(), rmad::kRmadInterfaceName,
                                  dbus::ObjectPath(rmad::kRmadServicePath));

    // |client_|'s Init() method should request a proxy for communicating with
    // the rmad daemon.
    EXPECT_CALL(*mock_bus_.get(),
                GetObjectProxy(rmad::kRmadInterfaceName,
                               dbus::ObjectPath(rmad::kRmadServicePath)))
        .WillOnce(Return(mock_proxy_.get()));

    // Save |client_|'s signal callbacks.
    EXPECT_CALL(*mock_proxy_,
                DoConnectToSignal(rmad::kRmadInterfaceName, _, _, _))
        .WillRepeatedly(Invoke(this, &RmadClientTest::ConnectToSignal));

    // ShutdownAndBlock() will be called in TearDown().
    EXPECT_CALL(*mock_bus_.get(), ShutdownAndBlock()).WillOnce(Return());

    // Create a client with the mock bus.
    RmadClient::Initialize(mock_bus_.get());
    client_ = RmadClient::Get();
  }

  void TearDown() override {
    mock_bus_->ShutdownAndBlock();
    RmadClient::Shutdown();
  }

  // Responsible for responding to a rmad API method call.
  void OnCallDbusMethod(dbus::MethodCall* method_call,
                        int timeout_ms,
                        dbus::ObjectProxy::ResponseCallback* callback) {
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*callback), response_));
  }

  // Synchronously passes |signal| to |client_|'s handler, simulating the signal
  // being emitted by rmad.
  void EmitSignal(dbus::Signal* signal) {
    const std::string signal_name = signal->GetMember();
    const auto it = signal_callbacks_.find(signal_name);
    ASSERT_TRUE(it != signal_callbacks_.end())
        << "Client didn't register for signal " << signal_name;
    it->second.Run(signal);
  }

  // Used to trigger errors on signals with parameters.
  void EmitEmptySignal(const std::string& signal_name) {
    dbus::Signal signal(rmad::kRmadInterfaceName, signal_name);
    EmitSignal(&signal);
  }

  // Passes an error signal to |client_|.
  void EmitErrorSignal(rmad::RmadErrorCode error) {
    dbus::Signal signal(rmad::kRmadInterfaceName, rmad::kErrorSignal);
    dbus::MessageWriter(&signal).AppendInt32(static_cast<int32_t>(error));
    EmitSignal(&signal);
  }

  // Passes a calibration progress signal to |client_|.
  void EmitCalibrationProgressSignal(
      rmad::RmadComponent component,
      rmad::CalibrationComponentStatus::CalibrationStatus status,
      double progress) {
    dbus::Signal signal(rmad::kRmadInterfaceName,
                        rmad::kCalibrationProgressSignal);
    dbus::MessageWriter writer(&signal);
    dbus::MessageWriter struct_writer(nullptr);
    writer.OpenStruct(&struct_writer);
    struct_writer.AppendInt32(static_cast<int32_t>(component));
    struct_writer.AppendInt32(static_cast<int32_t>(status));
    struct_writer.AppendDouble(progress);
    writer.CloseContainer(&struct_writer);
    EmitSignal(&signal);
  }

  // Passes a calibration overall progress signal to |client_|.
  void EmitCalibrationOverallProgressSignal(
      rmad::CalibrationOverallStatus status) {
    dbus::Signal signal(rmad::kRmadInterfaceName,
                        rmad::kCalibrationOverallSignal);
    dbus::MessageWriter(&signal).AppendInt32(static_cast<int32_t>(status));
    EmitSignal(&signal);
  }

  // Passes a provisioning progress signal to |client_|.
  void EmitHardwareWriteProtectionStateSignal(bool enabled) {
    dbus::Signal signal(rmad::kRmadInterfaceName,
                        rmad::kHardwareWriteProtectionStateSignal);
    dbus::MessageWriter(&signal).AppendBool(enabled);
    EmitSignal(&signal);
  }

  // Passes a power cable state signal to |client_|.
  void EmitPowerCableStateSignal(bool plugged_in) {
    dbus::Signal signal(rmad::kRmadInterfaceName, rmad::kPowerCableStateSignal);
    dbus::MessageWriter(&signal).AppendBool(plugged_in);
    EmitSignal(&signal);
  }

  // Passes an external disk signal to |client_|.
  void EmitExternalDiskStateSignal(bool detected) {
    dbus::Signal signal(rmad::kRmadInterfaceName,
                        rmad::kExternalDiskDetectedSignal);
    dbus::MessageWriter(&signal).AppendBool(detected);
    EmitSignal(&signal);
  }

  // Passes a provisioning progress signal to |client_|.
  void EmitProvisioningProgressSignal(rmad::ProvisionStatus::Status status,
                                      double progress,
                                      rmad::ProvisionStatus::Error error) {
    dbus::Signal signal(rmad::kRmadInterfaceName,
                        rmad::kProvisioningProgressSignal);
    dbus::MessageWriter writer(&signal);
    dbus::MessageWriter struct_writer(nullptr);
    writer.OpenStruct(&struct_writer);
    struct_writer.AppendInt32(static_cast<int32_t>(status));
    struct_writer.AppendDouble(progress);
    struct_writer.AppendInt32(static_cast<int32_t>(error));
    writer.CloseContainer(&struct_writer);
    EmitSignal(&signal);
  }

  // Passes a hardware verification status signal to |client_|.
  void EmitHardwareVerificationResultSignal(bool is_compliant,
                                            std::string error_message) {
    dbus::Signal signal(rmad::kRmadInterfaceName,
                        rmad::kHardwareVerificationResultSignal);
    dbus::MessageWriter writer(&signal);
    dbus::MessageWriter struct_writer(nullptr);
    writer.OpenStruct(&struct_writer);
    struct_writer.AppendBool(is_compliant);
    struct_writer.AppendString(error_message);
    writer.CloseContainer(&struct_writer);
    EmitSignal(&signal);
  }

  // Passes a finalization status signal to |client_|.
  void EmitFinalizationProgressSignal(rmad::FinalizeStatus::Status status,
                                      double progress,
                                      rmad::FinalizeStatus::Error error) {
    dbus::Signal signal(rmad::kRmadInterfaceName,
                        rmad::kFinalizeProgressSignal);
    dbus::MessageWriter writer(&signal);
    dbus::MessageWriter struct_writer(nullptr);
    writer.OpenStruct(&struct_writer);
    struct_writer.AppendInt32(static_cast<int32_t>(status));
    struct_writer.AppendDouble(progress);
    struct_writer.AppendInt32(static_cast<int32_t>(error));
    writer.CloseContainer(&struct_writer);
    EmitSignal(&signal);
  }

  // Passes a ro firmware update status signal to |client_|.
  void EmitRoFirmwareUpdateProgressSignal(rmad::UpdateRoFirmwareStatus status) {
    dbus::Signal signal(rmad::kRmadInterfaceName,
                        rmad::kUpdateRoFirmwareStatusSignal);
    dbus::MessageWriter(&signal).AppendInt32(static_cast<int32_t>(status));
    EmitSignal(&signal);
  }

 protected:
  // Maps from rmad signal name to the corresponding callback provided by
  // |client_|.
  std::map<std::string, dbus::ObjectProxy::SignalCallback> signal_callbacks_;

  raw_ptr<RmadClient, DanglingUntriaged> client_ =
      nullptr;  // Unowned convenience pointer.
  // A message loop to emulate asynchronous behavior.
  base::test::SingleThreadTaskEnvironment task_environment_;
  raw_ptr<dbus::Response, DanglingUntriaged> response_ = nullptr;
  // Mock D-Bus objects for |client_| to interact with.
  scoped_refptr<dbus::MockBus> mock_bus_;
  scoped_refptr<dbus::MockObjectProxy> mock_proxy_;

 private:
  // Handles calls to |mock_proxy_|'s ConnectToSignal() method.
  void ConnectToSignal(
      const std::string& interface_name,
      const std::string& signal_name,
      dbus::ObjectProxy::SignalCallback signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    CHECK_EQ(interface_name, rmad::kRmadInterfaceName);
    signal_callbacks_[signal_name] = signal_callback;

    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(*on_connected_callback), interface_name,
                       signal_name, true /* success */));
  }
};

// Interface for observing changes from rmad.
class TestObserver : public RmadClient::Observer {
 public:
  explicit TestObserver(RmadClient* client) : client_(client) {
    client_->AddObserver(this);
  }
  ~TestObserver() override { client_->RemoveObserver(this); }

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

  int num_error() const { return num_error_; }
  rmad::RmadErrorCode last_error() const { return last_error_; }
  int num_calibration_progress() const { return num_calibration_progress_; }
  const rmad::CalibrationComponentStatus& last_calibration_component_status()
      const {
    return last_calibration_component_status_;
  }
  int num_calibration_overall_progress() const {
    return num_calibration_overall_progress_;
  }
  rmad::CalibrationOverallStatus last_calibration_overall_status() const {
    return last_calibration_overall_status_;
  }
  int num_provisioning_progress() const { return num_provisioning_progress_; }
  rmad::ProvisionStatus last_provisioning_status() const {
    return last_provisioning_status_;
  }
  int num_hardware_write_protection_state() const {
    return num_hardware_write_protection_state_;
  }
  bool last_hardware_write_protection_state() const {
    return last_hardware_write_protection_state_;
  }
  int num_power_cable_state() const { return num_power_cable_state_; }
  bool last_power_cable_state() const { return last_power_cable_state_; }
  int num_external_disk_state() const { return num_external_disk_state_; }
  bool last_external_disk_state() const { return last_external_disk_state_; }
  int num_hardware_verification_result() const {
    return num_hardware_verification_result_;
  }
  const rmad::HardwareVerificationResult& last_hardware_verification_result()
      const {
    return last_hardware_verification_result_;
  }
  int num_finalization_progress() const { return num_finalization_progress_; }
  const rmad::FinalizeStatus& last_finalization_progress() const {
    return last_finalization_progress_;
  }

  int num_ro_firmware_update_progress() const {
    return num_ro_firmware_update_progress_;
  }
  rmad::UpdateRoFirmwareStatus last_ro_firmware_update_status() const {
    return last_ro_firmware_update_status_;
  }

  // Called when an error occurs outside of state transitions.
  // e.g. while calibrating devices.
  void Error(rmad::RmadErrorCode error) override {
    num_error_++;
    last_error_ = error;
  }

  // Called when calibration progress is updated.
  void CalibrationProgress(
      const rmad::CalibrationComponentStatus& componentStatus) override {
    num_calibration_progress_++;
    last_calibration_component_status_ = componentStatus;
  }

  void CalibrationOverallProgress(
      const rmad::CalibrationOverallStatus status) override {
    num_calibration_overall_progress_++;
    last_calibration_overall_status_ = status;
  }

  // Called when provisioning progress is updated.
  void ProvisioningProgress(const rmad::ProvisionStatus& status) override {
    num_provisioning_progress_++;
    last_provisioning_status_ = status;
  }

  // Called when hardware write protection state changes.
  void HardwareWriteProtectionState(bool enabled) override {
    num_hardware_write_protection_state_++;
    last_hardware_write_protection_state_ = enabled;
  }

  // Called when power cable is plugged in or removed.
  void PowerCableState(bool plugged_in) override {
    num_power_cable_state_++;
    last_power_cable_state_ = plugged_in;
  }

  // Called when power cable is plugged in or removed.
  void ExternalDiskState(bool detected) override {
    num_external_disk_state_++;
    last_external_disk_state_ = detected;
  }

  // Called when hardware verification completes.
  void HardwareVerificationResult(
      const rmad::HardwareVerificationResult& result) override {
    num_hardware_verification_result_++;
    last_hardware_verification_result_ = result;
  }

  // Called when hardware verification completes.
  void FinalizationProgress(const rmad::FinalizeStatus& status) override {
    num_finalization_progress_++;
    last_finalization_progress_ = status;
  }

  // Called when overall calibration progress is updated.
  void RoFirmwareUpdateProgress(rmad::UpdateRoFirmwareStatus status) override {
    num_ro_firmware_update_progress_++;
    last_ro_firmware_update_status_ = status;
  }

 private:
  raw_ptr<RmadClient> client_;  // Not owned.
  int num_error_ = 0;
  rmad::RmadErrorCode last_error_ = rmad::RmadErrorCode::RMAD_ERROR_NOT_SET;
  int num_calibration_progress_ = 0;
  rmad::CalibrationComponentStatus last_calibration_component_status_;
  int num_calibration_overall_progress_ = 0;
  rmad::CalibrationOverallStatus last_calibration_overall_status_ =
      rmad::CalibrationOverallStatus::RMAD_CALIBRATION_OVERALL_UNKNOWN;
  int num_provisioning_progress_ = 0;
  rmad::ProvisionStatus last_provisioning_status_;
  int num_hardware_write_protection_state_ = 0;
  bool last_hardware_write_protection_state_ = true;
  int num_power_cable_state_ = 0;
  bool last_power_cable_state_ = true;
  int num_external_disk_state_ = 0;
  bool last_external_disk_state_ = true;
  int num_hardware_verification_result_ = 0;
  rmad::HardwareVerificationResult last_hardware_verification_result_;
  int num_finalization_progress_ = 0;
  rmad::FinalizeStatus last_finalization_progress_;
  int num_ro_firmware_update_progress_ = 0;
  rmad::UpdateRoFirmwareStatus last_ro_firmware_update_status_ =
      rmad::UpdateRoFirmwareStatus::RMAD_UPDATE_RO_FIRMWARE_UNKNOWN;
};

TEST_F(RmadClientTest, GetCurrentState) {
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
  rmad::GetStateReply expected_proto;
  expected_proto.set_error(rmad::RMAD_ERROR_RMA_NOT_REQUIRED);
  ASSERT_TRUE(dbus::MessageWriter(response.get())
                  .AppendProtoAsArrayOfBytes(expected_proto));

  response_ = response.get();
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kGetCurrentStateMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->GetCurrentState(base::BindLambdaForTesting(
      [&](std::optional<rmad::GetStateReply> response) {
        EXPECT_TRUE(response.has_value());
        EXPECT_EQ(response->error(), rmad::RMAD_ERROR_RMA_NOT_REQUIRED);
        EXPECT_FALSE(response->has_state());
        run_loop.Quit();
      }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, GetCurrentState_NullResponse) {
  response_ = nullptr;
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kGetCurrentStateMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->GetCurrentState(base::BindLambdaForTesting(
      [&](std::optional<rmad::GetStateReply> response) {
        EXPECT_FALSE(response.has_value());
        run_loop.Quit();
      }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, GetCurrentState_EmptyResponse) {
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();

  response_ = response.get();
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kGetCurrentStateMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->GetCurrentState(base::BindLambdaForTesting(
      [&](std::optional<rmad::GetStateReply> response) {
        EXPECT_FALSE(response.has_value());
        run_loop.Quit();
      }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, TransitionNextState) {
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
  rmad::GetStateReply expected_proto;
  rmad::RmadState* expected_state = new rmad::RmadState();
  expected_state->set_allocated_device_destination(
      new rmad::DeviceDestinationState());
  expected_proto.set_allocated_state(expected_state);
  expected_proto.set_error(rmad::RMAD_ERROR_OK);
  ASSERT_TRUE(dbus::MessageWriter(response.get())
                  .AppendProtoAsArrayOfBytes(expected_proto));

  response_ = response.get();
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kTransitionNextStateMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  rmad::RmadState request;
  request.set_allocated_welcome(new rmad::WelcomeState());

  base::RunLoop run_loop;
  client_->TransitionNextState(
      request, base::BindLambdaForTesting(
                   [&](std::optional<rmad::GetStateReply> response) {
                     EXPECT_TRUE(response.has_value());
                     EXPECT_EQ(response->error(), rmad::RMAD_ERROR_OK);
                     EXPECT_TRUE(response->has_state());
                     EXPECT_TRUE(response->state().has_device_destination());
                     run_loop.Quit();
                   }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, TransitionNextState_NullResponse) {
  response_ = nullptr;
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kTransitionNextStateMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  rmad::RmadState request;
  request.set_allocated_welcome(new rmad::WelcomeState());
  base::RunLoop run_loop;
  client_->TransitionNextState(
      request, base::BindLambdaForTesting(
                   [&](std::optional<rmad::GetStateReply> response) {
                     EXPECT_FALSE(response.has_value());
                     run_loop.Quit();
                   }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, TransitionNextState_EmptyResponse) {
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();

  response_ = response.get();
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kTransitionNextStateMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  rmad::RmadState request;
  request.set_allocated_welcome(new rmad::WelcomeState());
  base::RunLoop run_loop;
  client_->TransitionNextState(
      request, base::BindLambdaForTesting(
                   [&](std::optional<rmad::GetStateReply> response) {
                     EXPECT_FALSE(response.has_value());
                     run_loop.Quit();
                   }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, TransitionPreviousState) {
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
  rmad::GetStateReply expected_proto;
  rmad::RmadState* expected_state = new rmad::RmadState();
  expected_state->set_allocated_welcome(new rmad::WelcomeState());
  expected_proto.set_allocated_state(expected_state);
  expected_proto.set_error(rmad::RMAD_ERROR_TRANSITION_FAILED);
  ASSERT_TRUE(dbus::MessageWriter(response.get())
                  .AppendProtoAsArrayOfBytes(expected_proto));

  response_ = response.get();
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kTransitionPreviousStateMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->TransitionPreviousState(base::BindLambdaForTesting(
      [&](std::optional<rmad::GetStateReply> response) {
        EXPECT_TRUE(response.has_value());
        EXPECT_EQ(response->error(), rmad::RMAD_ERROR_TRANSITION_FAILED);
        EXPECT_TRUE(response->has_state());
        EXPECT_TRUE(response->state().has_welcome());
        run_loop.Quit();
      }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, TransitionPreviousState_NullResponse) {
  response_ = nullptr;
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kTransitionPreviousStateMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->TransitionPreviousState(base::BindLambdaForTesting(
      [&](std::optional<rmad::GetStateReply> response) {
        EXPECT_FALSE(response.has_value());
        run_loop.Quit();
      }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, TransitionPreviousState_EmptyResponse) {
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();

  response_ = response.get();
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kTransitionPreviousStateMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->TransitionPreviousState(base::BindLambdaForTesting(
      [&](std::optional<rmad::GetStateReply> response) {
        EXPECT_FALSE(response.has_value());
        run_loop.Quit();
      }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, AbortRma) {
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
  rmad::AbortRmaReply expected_proto;
  expected_proto.set_error(rmad::RMAD_ERROR_OK);
  ASSERT_TRUE(dbus::MessageWriter(response.get())
                  .AppendProtoAsArrayOfBytes(expected_proto));

  response_ = response.get();
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kAbortRmaMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->AbortRma(base::BindLambdaForTesting(
      [&](std::optional<rmad::AbortRmaReply> response) {
        EXPECT_TRUE(response.has_value());
        EXPECT_EQ(response->error(), rmad::RMAD_ERROR_OK);
        run_loop.Quit();
      }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, AbortRma_NullResponse) {
  response_ = nullptr;
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kAbortRmaMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->AbortRma(base::BindLambdaForTesting(
      [&](std::optional<rmad::AbortRmaReply> response) {
        EXPECT_FALSE(response.has_value());
        run_loop.Quit();
      }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, AbortRma_EmptyResponse) {
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();

  response_ = response.get();
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kAbortRmaMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->AbortRma(base::BindLambdaForTesting(
      [&](std::optional<rmad::AbortRmaReply> response) {
        EXPECT_FALSE(response.has_value());
        run_loop.Quit();
      }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, GetLog) {
  const std::string expected_log = "test rma log";
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
  rmad::GetLogReply expected_proto;
  expected_proto.set_log(expected_log);
  expected_proto.set_error(rmad::RMAD_ERROR_OK);
  ASSERT_TRUE(dbus::MessageWriter(response.get())
                  .AppendProtoAsArrayOfBytes(expected_proto));

  response_ = response.get();
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kGetLogMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->GetLog(base::BindLambdaForTesting(
      [&](std::optional<rmad::GetLogReply> response) {
        EXPECT_TRUE(response.has_value());
        EXPECT_EQ(response->log(), expected_log);
        EXPECT_EQ(response->error(), rmad::RMAD_ERROR_OK);
        run_loop.Quit();
      }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, GetLog_NullResponse) {
  response_ = nullptr;
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kGetLogMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->GetLog(base::BindLambdaForTesting(
      [&](std::optional<rmad::GetLogReply> response) {
        EXPECT_FALSE(response.has_value());
        run_loop.Quit();
      }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, GetLog_EmptyResponse) {
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();

  response_ = response.get();
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kGetLogMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->GetLog(base::BindLambdaForTesting(
      [&](std::optional<rmad::GetLogReply> response) {
        EXPECT_FALSE(response.has_value());
        run_loop.Quit();
      }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, SaveLog) {
  const std::string expected_save_path = "test save path";
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
  rmad::SaveLogReply expected_proto;
  expected_proto.set_save_path(expected_save_path);
  expected_proto.set_error(rmad::RMAD_ERROR_OK);
  ASSERT_TRUE(dbus::MessageWriter(response.get())
                  .AppendProtoAsArrayOfBytes(expected_proto));

  response_ = response.get();
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kSaveLogMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->SaveLog("Diagnostics log text",
                   base::BindLambdaForTesting(
                       [&](std::optional<rmad::SaveLogReply> response) {
                         EXPECT_TRUE(response.has_value());
                         EXPECT_EQ(response->save_path(), expected_save_path);
                         EXPECT_EQ(response->error(), rmad::RMAD_ERROR_OK);
                         run_loop.Quit();
                       }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, SaveLog_NullResponse) {
  response_ = nullptr;
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kSaveLogMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->SaveLog("Diagnostics log text",
                   base::BindLambdaForTesting(
                       [&](std::optional<rmad::SaveLogReply> response) {
                         EXPECT_FALSE(response.has_value());
                         run_loop.Quit();
                       }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, SaveLog_EmptyResponse) {
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();

  response_ = response.get();
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kSaveLogMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->SaveLog("Diagnostics log text",
                   base::BindLambdaForTesting(
                       [&](std::optional<rmad::SaveLogReply> response) {
                         EXPECT_FALSE(response.has_value());
                         run_loop.Quit();
                       }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, RecordBrowserActionMetric) {
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();

  rmad::RecordBrowserActionMetricRequest request;
  request.set_diagnostics(true);
  request.set_os_update(false);
  rmad::RecordBrowserActionMetricReply expected_reply;
  expected_reply.set_error(rmad::RMAD_ERROR_OK);

  ASSERT_TRUE(dbus::MessageWriter(response.get())
                  .AppendProtoAsArrayOfBytes(expected_reply));

  response_ = response.get();
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kRecordBrowserActionMetricMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->RecordBrowserActionMetric(
      request,
      base::BindLambdaForTesting(
          [&](std::optional<rmad::RecordBrowserActionMetricReply> response) {
            EXPECT_TRUE(response.has_value());
            EXPECT_EQ(response->error(), rmad::RMAD_ERROR_OK);
            run_loop.Quit();
          }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, RecordBrowserActionMetric_NullResponse) {
  response_ = nullptr;

  rmad::RecordBrowserActionMetricRequest request;
  request.set_diagnostics(true);
  request.set_os_update(false);

  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kRecordBrowserActionMetricMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->RecordBrowserActionMetric(
      request,
      base::BindLambdaForTesting(
          [&](std::optional<rmad::RecordBrowserActionMetricReply> response) {
            EXPECT_FALSE(response.has_value());
            run_loop.Quit();
          }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, RecordBrowserActionMetric_EmptyResponse) {
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();

  rmad::RecordBrowserActionMetricRequest request;
  request.set_diagnostics(true);
  request.set_os_update(false);

  response_ = response.get();
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kRecordBrowserActionMetricMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::RunLoop run_loop;
  client_->RecordBrowserActionMetric(
      request,
      base::BindLambdaForTesting(
          [&](std::optional<rmad::RecordBrowserActionMetricReply> response) {
            EXPECT_FALSE(response.has_value());
            run_loop.Quit();
          }));
  run_loop.RunUntilIdle();
}

TEST_F(RmadClientTest, ExtractExternalDiagnosticsApp) {
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
  rmad::ExtractExternalDiagnosticsAppReply expected_proto;
  expected_proto.set_error(rmad::RMAD_ERROR_OK);
  expected_proto.set_diagnostics_app_swbn_path("swbn_path");
  expected_proto.set_diagnostics_app_crx_path("crx_path");
  ASSERT_TRUE(dbus::MessageWriter(response.get())
                  .AppendProtoAsArrayOfBytes(expected_proto));

  response_ = response.get();
  EXPECT_CALL(
      *mock_proxy_.get(),
      DoCallMethod(HasMember(rmad::kExtractExternalDiagnosticsAppMethod),
                   dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::test::TestFuture<
      std::optional<rmad::ExtractExternalDiagnosticsAppReply>>
      future;
  client_->ExtractExternalDiagnosticsApp(future.GetCallback());
  EXPECT_TRUE(future.Get().has_value());
  EXPECT_EQ(future.Get()->error(), expected_proto.error());
  EXPECT_EQ(future.Get()->diagnostics_app_swbn_path(),
            expected_proto.diagnostics_app_swbn_path());
  EXPECT_EQ(future.Get()->diagnostics_app_crx_path(),
            expected_proto.diagnostics_app_crx_path());
}

TEST_F(RmadClientTest, InstallExtractedDiagnosticsApp) {
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
  rmad::InstallExtractedDiagnosticsAppReply expected_proto;
  expected_proto.set_error(rmad::RMAD_ERROR_OK);
  ASSERT_TRUE(dbus::MessageWriter(response.get())
                  .AppendProtoAsArrayOfBytes(expected_proto));

  response_ = response.get();
  EXPECT_CALL(
      *mock_proxy_.get(),
      DoCallMethod(HasMember(rmad::kInstallExtractedDiagnosticsAppMethod),
                   dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::test::TestFuture<
      std::optional<rmad::InstallExtractedDiagnosticsAppReply>>
      future;
  client_->InstallExtractedDiagnosticsApp(future.GetCallback());
  EXPECT_TRUE(future.Get().has_value());
  EXPECT_EQ(future.Get()->error(), expected_proto.error());
}

TEST_F(RmadClientTest, GetInstalledDiagnosticsApp) {
  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
  rmad::GetInstalledDiagnosticsAppReply expected_proto;
  expected_proto.set_error(rmad::RMAD_ERROR_OK);
  expected_proto.set_diagnostics_app_swbn_path("swbn_path");
  expected_proto.set_diagnostics_app_crx_path("crx_path");
  ASSERT_TRUE(dbus::MessageWriter(response.get())
                  .AppendProtoAsArrayOfBytes(expected_proto));

  response_ = response.get();
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(rmad::kGetInstalledDiagnosticsAppMethod),
                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));

  base::test::TestFuture<std::optional<rmad::GetInstalledDiagnosticsAppReply>>
      future;
  client_->GetInstalledDiagnosticsApp(future.GetCallback());
  EXPECT_TRUE(future.Get().has_value());
  EXPECT_EQ(future.Get()->error(), expected_proto.error());
  EXPECT_EQ(future.Get()->diagnostics_app_swbn_path(),
            expected_proto.diagnostics_app_swbn_path());
  EXPECT_EQ(future.Get()->diagnostics_app_crx_path(),
            expected_proto.diagnostics_app_crx_path());
}

// Tests that synchronous observers are notified about errors that occur outside
// of state transitions.
TEST_F(RmadClientTest, Error) {
  TestObserver observer_1(client_);

  EmitErrorSignal(rmad::RmadErrorCode::RMAD_ERROR_REIMAGING_UNKNOWN_FAILURE);
  EXPECT_EQ(observer_1.num_error(), 1);
  EXPECT_EQ(observer_1.last_error(),
            rmad::RmadErrorCode::RMAD_ERROR_REIMAGING_UNKNOWN_FAILURE);
}

// Tests that synchronous observers are notified about component calibration
// progress.
TEST_F(RmadClientTest, CalibrationProgress) {
  TestObserver observer_1(client_);

  EmitCalibrationProgressSignal(
      rmad::RmadComponent::RMAD_COMPONENT_LID_ACCELEROMETER,
      rmad::CalibrationComponentStatus::RMAD_CALIBRATION_IN_PROGRESS, 0.5);
  EXPECT_EQ(observer_1.num_calibration_progress(), 1);
  EXPECT_EQ(observer_1.last_calibration_component_status().component(),
            rmad::RmadComponent::RMAD_COMPONENT_LID_ACCELEROMETER);
  EXPECT_EQ(observer_1.last_calibration_component_status().status(),
            rmad::CalibrationComponentStatus::RMAD_CALIBRATION_IN_PROGRESS);
  EXPECT_EQ(observer_1.last_calibration_component_status().progress(), 0.5);
}

TEST_F(RmadClientTest, CalibrationOverallProgress) {
  TestObserver observer_1(client_);

  EmitCalibrationOverallProgressSignal(
      rmad::CalibrationOverallStatus::
          RMAD_CALIBRATION_OVERALL_CURRENT_ROUND_COMPLETE);
  EXPECT_EQ(observer_1.num_calibration_overall_progress(), 1);
  EXPECT_EQ(observer_1.last_calibration_overall_status(),
            rmad::CalibrationOverallStatus::
                RMAD_CALIBRATION_OVERALL_CURRENT_ROUND_COMPLETE);
}

TEST_F(RmadClientTest, CalibrationOverallProgressBadParameterFails) {
  TestObserver observer_1(client_);

  EmitEmptySignal(rmad::kCalibrationOverallSignal);
  EXPECT_EQ(observer_1.num_calibration_overall_progress(), 0);
  EXPECT_EQ(observer_1.last_calibration_overall_status(),
            rmad::CalibrationOverallStatus::RMAD_CALIBRATION_OVERALL_UNKNOWN);
}

// Tests that synchronous observers are notified about provisioning progress.
TEST_F(RmadClientTest, ProvisioningProgress) {
  TestObserver observer_1(client_);

  EmitProvisioningProgressSignal(
      rmad::ProvisionStatus::RMAD_PROVISION_STATUS_IN_PROGRESS, 0.25,
      rmad::ProvisionStatus::RMAD_PROVISION_ERROR_UNKNOWN);
  EXPECT_EQ(observer_1.num_provisioning_progress(), 1);
  EXPECT_EQ(observer_1.last_provisioning_status().status(),
            rmad::ProvisionStatus::RMAD_PROVISION_STATUS_IN_PROGRESS);
  EXPECT_EQ(observer_1.last_provisioning_status().progress(), 0.25);
  EXPECT_EQ(observer_1.last_provisioning_status().error(),
            rmad::ProvisionStatus::RMAD_PROVISION_ERROR_UNKNOWN);
}

// Tests that synchronous observers are notified about provisioning errors.
TEST_F(RmadClientTest, ProvisioningErrors) {
  TestObserver observer_1(client_);

  EmitProvisioningProgressSignal(
      rmad::ProvisionStatus::RMAD_PROVISION_STATUS_FAILED_BLOCKING, 0.25,
      rmad::ProvisionStatus::RMAD_PROVISION_ERROR_CR50);
  EXPECT_EQ(observer_1.num_provisioning_progress(), 1);
  EXPECT_EQ(observer_1.last_provisioning_status().status(),
            rmad::ProvisionStatus::RMAD_PROVISION_STATUS_FAILED_BLOCKING);
  EXPECT_EQ(observer_1.last_provisioning_status().progress(), 0.25);
  EXPECT_EQ(observer_1.last_provisioning_status().error(),
            rmad::ProvisionStatus::RMAD_PROVISION_ERROR_CR50);
}

// Tests that synchronous observers are notified about provisioning progress.
TEST_F(RmadClientTest, HardwareWriteProtectionState) {
  TestObserver observer_1(client_);

  EmitHardwareWriteProtectionStateSignal(false);
  EXPECT_EQ(observer_1.num_hardware_write_protection_state(), 1);
  EXPECT_FALSE(observer_1.last_hardware_write_protection_state());

  EmitHardwareWriteProtectionStateSignal(true);
  EXPECT_EQ(observer_1.num_hardware_write_protection_state(), 2);
  EXPECT_TRUE(observer_1.last_hardware_write_protection_state());
}

// Tests that synchronous observers are notified about power cable state.
TEST_F(RmadClientTest, PowerCableState) {
  TestObserver observer_1(client_);

  EmitPowerCableStateSignal(false);
  EXPECT_EQ(observer_1.num_power_cable_state(), 1);
  EXPECT_FALSE(observer_1.last_power_cable_state());

  EmitPowerCableStateSignal(true);
  EXPECT_EQ(observer_1.num_power_cable_state(), 2);
  EXPECT_TRUE(observer_1.last_power_cable_state());
}

// Tests that synchronous observers are notified about external disk state.
TEST_F(RmadClientTest, ExternalDiskState) {
  TestObserver observer_1(client_);

  EmitExternalDiskStateSignal(false);
  EXPECT_EQ(observer_1.num_external_disk_state(), 1);
  EXPECT_FALSE(observer_1.last_external_disk_state());

  EmitExternalDiskStateSignal(true);
  EXPECT_EQ(observer_1.num_external_disk_state(), 2);
  EXPECT_TRUE(observer_1.last_external_disk_state());
}

// Tests that synchronous observers are notified about hardware verification
// status.
TEST_F(RmadClientTest, HardwareVerificationResult) {
  TestObserver observer_1(client_);

  EmitHardwareVerificationResultSignal(false, "fatal error");
  EXPECT_EQ(observer_1.num_hardware_verification_result(), 1);
  EXPECT_FALSE(observer_1.last_hardware_verification_result().is_compliant());
  EXPECT_EQ(observer_1.last_hardware_verification_result().error_str(),
            "fatal error");

  EmitHardwareVerificationResultSignal(true, "ok");
  EXPECT_EQ(observer_1.num_hardware_verification_result(), 2);
  EXPECT_TRUE(observer_1.last_hardware_verification_result().is_compliant());
  EXPECT_EQ(observer_1.last_hardware_verification_result().error_str(), "ok");
}

// Tests that synchronous observers are notified about finalization status.
TEST_F(RmadClientTest, FinalizationProgress) {
  TestObserver observer_1(client_);

  EmitFinalizationProgressSignal(
      rmad::FinalizeStatus::RMAD_FINALIZE_STATUS_IN_PROGRESS, 0.5,
      rmad::FinalizeStatus::RMAD_FINALIZE_ERROR_UNKNOWN);
  EXPECT_EQ(observer_1.num_finalization_progress(), 1);
  EXPECT_EQ(observer_1.last_finalization_progress().status(),
            rmad::FinalizeStatus::RMAD_FINALIZE_STATUS_IN_PROGRESS);
  EXPECT_EQ(observer_1.last_finalization_progress().progress(), 0.5);

  EmitFinalizationProgressSignal(
      rmad::FinalizeStatus::RMAD_FINALIZE_STATUS_COMPLETE, 1.0,
      rmad::FinalizeStatus::RMAD_FINALIZE_ERROR_UNKNOWN);
  EXPECT_EQ(observer_1.num_finalization_progress(), 2);
  EXPECT_EQ(observer_1.last_finalization_progress().status(),
            rmad::FinalizeStatus::RMAD_FINALIZE_STATUS_COMPLETE);
  EXPECT_EQ(observer_1.last_finalization_progress().progress(), 1.0);
}

// Tests that synchronous observers are notified about finalization errors.
TEST_F(RmadClientTest, FinalizationErrors) {
  TestObserver observer_1(client_);

  EmitFinalizationProgressSignal(
      rmad::FinalizeStatus::RMAD_FINALIZE_STATUS_FAILED_BLOCKING, 0.5,
      rmad::FinalizeStatus::RMAD_FINALIZE_ERROR_CR50);
  EXPECT_EQ(observer_1.num_finalization_progress(), 1);
  EXPECT_EQ(observer_1.last_finalization_progress().status(),
            rmad::FinalizeStatus::RMAD_FINALIZE_STATUS_FAILED_BLOCKING);
  EXPECT_EQ(observer_1.last_finalization_progress().progress(), 0.5);
  EXPECT_EQ(observer_1.last_finalization_progress().error(),
            rmad::FinalizeStatus::RMAD_FINALIZE_ERROR_CR50);
}

TEST_F(RmadClientTest, RoFirmwareUpdateProgress) {
  TestObserver observer_1(client_);

  EmitRoFirmwareUpdateProgressSignal(
      rmad::UpdateRoFirmwareStatus::RMAD_UPDATE_RO_FIRMWARE_DOWNLOADING);
  EXPECT_EQ(observer_1.num_ro_firmware_update_progress(), 1);
  EXPECT_EQ(observer_1.last_ro_firmware_update_status(),
            rmad::UpdateRoFirmwareStatus::RMAD_UPDATE_RO_FIRMWARE_DOWNLOADING);
}

TEST_F(RmadClientTest, RoFirmwareUpdateProgressBadParameterFails) {
  TestObserver observer_1(client_);

  EmitEmptySignal(rmad::kUpdateRoFirmwareStatusSignal);
  EXPECT_EQ(observer_1.num_ro_firmware_update_progress(), 0);
  EXPECT_EQ(observer_1.last_ro_firmware_update_status(),
            rmad::UpdateRoFirmwareStatus::RMAD_UPDATE_RO_FIRMWARE_UNKNOWN);
}

}  // namespace

}  // namespace ash