chromium/chromeos/ash/components/dbus/shill/modem_3gpp_client_unittest.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chromeos/ash/components/dbus/shill/modem_3gpp_client.h"

#include <memory>
#include <optional>
#include <utility>

#include "ash/constants/ash_features.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/values.h"
#include "dbus/message.h"
#include "dbus/mock_bus.h"
#include "dbus/mock_object_proxy.h"
#include "dbus/object_path.h"
#include "dbus/values_util.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 {

// D-Bus service name used by test.
const char kServiceName[] = "service.name";

// D-Bus object path used by test.
const char kObjectPath[] = "/object/path";

// Lock configuration used by test.
constexpr char kCarrierLockConfig[] = "carrier.lock.configuration";

}  // namespace

class Modem3gppClientTest : public testing::Test {
 public:
  Modem3gppClientTest() {}

  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 proxy.
    mock_proxy_ = new dbus::MockObjectProxy(mock_bus_.get(), kServiceName,
                                            dbus::ObjectPath(kObjectPath));

    // Set an expectation so mock_proxy's ConnectToSignal() will use
    // OnConnectToSignal() to run the callback.
    EXPECT_CALL(*mock_proxy_.get(),
                DoConnectToSignal(modemmanager::kModemManager13gppInterface,
                                  modemmanager::kModem3gppSetCarrierLock, _, _))
        .WillRepeatedly(Invoke(this, &Modem3gppClientTest::OnConnectToSignal));

    // Set an expectation so mock_bus's GetObjectProxy() for the given
    // service name and the object path will return mock_proxy_.
    EXPECT_CALL(*mock_bus_.get(),
                GetObjectProxy(kServiceName, dbus::ObjectPath(kObjectPath)))
        .WillOnce(Return(mock_proxy_.get()));

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

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

  void TearDown() override {
    client_ = nullptr;
    mock_bus_->ShutdownAndBlock();
    Modem3gppClient::Shutdown();
  }

  // Handles SetCarrierLock method call.
  void OnSetCarrierLock(dbus::MethodCall* method_call,
                        int timeout_ms,
                        dbus::ObjectProxy::ResponseOrErrorCallback* callback) {
    const uint8_t* configuration;
    size_t conf_len;

    EXPECT_EQ(modemmanager::kModemManager13gppInterface,
              method_call->GetInterface());
    EXPECT_EQ(modemmanager::kModem3gppSetCarrierLock, method_call->GetMember());

    dbus::MessageReader reader(method_call);
    EXPECT_TRUE(reader.PopArrayOfBytes(&configuration, &conf_len));
    EXPECT_EQ(conf_len, expected_configuration_.size());
    for (size_t i = 0; i < conf_len; i++) {
      EXPECT_EQ(expected_configuration_.c_str()[i], (char)configuration[i]);
    }
    EXPECT_FALSE(reader.HasMoreData());

    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*callback), response_.get(),
                                  error_response_.get()));
  }

 protected:
  raw_ptr<Modem3gppClient> client_ = nullptr;
  base::test::SingleThreadTaskEnvironment task_environment_;
  scoped_refptr<dbus::MockBus> mock_bus_;
  scoped_refptr<dbus::MockObjectProxy> mock_proxy_;

  // The 3gppReceived signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback received_callback_;

  // Expected argument for SetCarrierLock method.
  std::string expected_configuration_;

  // Response returned by mock methods.
  std::unique_ptr<dbus::Response> response_;
  std::unique_ptr<dbus::ErrorResponse> error_response_;

 private:
  // Used to implement the mock proxy.
  void OnConnectToSignal(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    received_callback_ = signal_callback;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(*on_connected_callback), interface_name,
                       signal_name, /*success=*/true));
  }
};

TEST_F(Modem3gppClientTest, SetCarrierLockSuccess) {
  // Set expectations.
  expected_configuration_ = kCarrierLockConfig;
  EXPECT_CALL(*mock_proxy_.get(), DoCallMethodWithErrorResponse(_, _, _))
      .WillOnce(Invoke(this, &Modem3gppClientTest::OnSetCarrierLock));

  // Create response.
  response_ = dbus::Response::CreateEmpty();

  // Call SetCarrierLock.
  base::test::TestFuture<CarrierLockResult> set_carrier_lock_future;
  client_->SetCarrierLock(kServiceName, dbus::ObjectPath(kObjectPath),
                          expected_configuration_,
                          set_carrier_lock_future.GetCallback());

  EXPECT_EQ(CarrierLockResult::kSuccess, set_carrier_lock_future.Get());
}

TEST_F(Modem3gppClientTest, SetCarrierLockFailure) {
  const std::vector<std::pair<CarrierLockResult, std::string>> all_errors = {
      {CarrierLockResult::kUnknownError, "org.error"},
      {CarrierLockResult::kInvalidSignature, "org.InvalidSignature"},
      {CarrierLockResult::kInvalidImei, "org.InvalidImei"},
      {CarrierLockResult::kInvalidTimeStamp, "org.InvalidTimestamp"},
      {CarrierLockResult::kNetworkListTooLarge, "org.NetworkListTooLarge"},
      {CarrierLockResult::kAlgorithmNotSupported, "org.AlgorithmNotSupported"},
      {CarrierLockResult::kFeatureNotSupported, "org.FeatureNotSupported"},
      {CarrierLockResult::kDecodeOrParsingError, "org.DecodeOrParsingError"},
      {CarrierLockResult::kOperationNotSupported, "org.Unsupported"},
  };

  // Set expectations.
  expected_configuration_ = kCarrierLockConfig;
  EXPECT_CALL(*mock_proxy_.get(), DoCallMethodWithErrorResponse(_, _, _))
      .WillRepeatedly(Invoke(this, &Modem3gppClientTest::OnSetCarrierLock));

  // Create response.
  error_response_ = dbus::ErrorResponse::FromRawMessage(
      dbus_message_new(DBUS_MESSAGE_TYPE_ERROR));

  for (std::pair<CarrierLockResult, std::string> error : all_errors) {
    error_response_->SetErrorName(error.second);

    // Call SetCarrierLock.
    base::test::TestFuture<CarrierLockResult> set_carrier_lock_future;
    client_->SetCarrierLock(kServiceName, dbus::ObjectPath(kObjectPath),
                            expected_configuration_,
                            set_carrier_lock_future.GetCallback());

    EXPECT_EQ(error.first, set_carrier_lock_future.Get());
  }
}

TEST_F(Modem3gppClientTest, CallSetCarrierLockTwice) {
  // Set expectations.
  expected_configuration_ = kCarrierLockConfig;
  EXPECT_CALL(*mock_proxy_.get(), DoCallMethodWithErrorResponse(_, _, _))
      .WillOnce(Invoke(this, &Modem3gppClientTest::OnSetCarrierLock))
      .WillOnce(Invoke(this, &Modem3gppClientTest::OnSetCarrierLock));

  // Create response.
  response_ = dbus::Response::CreateEmpty();

  // Call SetCarrierLock.
  base::test::TestFuture<CarrierLockResult> set_carrier_lock_future1;
  client_->SetCarrierLock(kServiceName, dbus::ObjectPath(kObjectPath),
                          expected_configuration_,
                          set_carrier_lock_future1.GetCallback());

  // Call SetCarrierLock again.
  base::test::TestFuture<CarrierLockResult> set_carrier_lock_future2;
  client_->SetCarrierLock(kServiceName, dbus::ObjectPath(kObjectPath),
                          expected_configuration_,
                          set_carrier_lock_future2.GetCallback());

  EXPECT_EQ(CarrierLockResult::kSuccess, set_carrier_lock_future1.Get());
  EXPECT_EQ(CarrierLockResult::kSuccess, set_carrier_lock_future2.Get());
}

}  // namespace ash