chromium/chromeos/ash/components/dbus/cec_service/cec_service_client_unittest.cc

// Copyright 2018 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/cec_service/cec_service_client.h"

#include <memory>
#include <string>
#include <vector>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "dbus/bus.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::ContainerEq;
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;
}

// Functor that can be passed to GoogleMock to respond to a GetTvsPowerStatus
// D-Bus method call with the given power states.
class GetTvsPowerStatusHandler {
 public:
  explicit GetTvsPowerStatusHandler(std::vector<int32_t> power_states)
      : power_states_(power_states) {}

  void operator()(dbus::MethodCall* method_call,
                  int timeout_ms,
                  dbus::ObjectProxy::ResponseCallback* callback) {
    method_call->SetSerial(1);  // arbitrary but needed by FromMethodCall
    std::unique_ptr<dbus::Response> response =
        dbus::Response::FromMethodCall(method_call);

    dbus::MessageWriter writer(response.get());
    dbus::MessageWriter array_writer(nullptr);
    writer.OpenArray("i", &array_writer);
    for (int32_t power_state : power_states_)
      array_writer.AppendInt32(power_state);
    writer.CloseContainer(&array_writer);

    // Run the response callback asynchronously.
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(*callback), base::Owned(response.release())));
  }

 private:
  std::vector<int32_t> power_states_;
};

}  // namespace

class CecServiceClientTest : public testing::Test {
 public:
  CecServiceClientTest() {
    dbus::Bus::Options options;
    options.bus_type = dbus::Bus::SYSTEM;
    // Suppress warnings about uninteresting calls to the bus.
    mock_bus_ = base::MakeRefCounted<::testing::NiceMock<dbus::MockBus>>(
        dbus::Bus::Options());

    // Setup mock bus and proxy.
    mock_proxy_ = base::MakeRefCounted<dbus::MockObjectProxy>(
        mock_bus_.get(), cecservice::kCecServiceName,
        dbus::ObjectPath(cecservice::kCecServicePath));

    ON_CALL(*mock_bus_.get(),
            GetObjectProxy(cecservice::kCecServiceName,
                           dbus::ObjectPath(cecservice::kCecServicePath)))
        .WillByDefault(Return(mock_proxy_.get()));

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

    // Run the message loop to run the signal connection result callback.
    base::RunLoop().RunUntilIdle();
  }

  ~CecServiceClientTest() override { CecServiceClient::Shutdown(); }

 protected:
  base::test::SingleThreadTaskEnvironment task_environment_;
  raw_ptr<CecServiceClient, DanglingUntriaged> client_ = nullptr;
  scoped_refptr<dbus::MockBus> mock_bus_;
  scoped_refptr<dbus::MockObjectProxy> mock_proxy_;
};

TEST_F(CecServiceClientTest, SendStandByTriggersDBusMessage) {
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(
                  HasMember(cecservice::kSendStandByToAllDevicesMethod), _, _));

  client_->SendStandBy();
}

TEST_F(CecServiceClientTest, SendWakeUpTriggersDBusMessage) {
  EXPECT_CALL(
      *mock_proxy_.get(),
      DoCallMethod(HasMember(cecservice::kSendWakeUpToAllDevicesMethod), _, _));

  client_->SendWakeUp();
}

TEST_F(CecServiceClientTest, QueryPowerStatusSendDBusMessage) {
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(cecservice::kGetTvsPowerStatus), _, _));

  client_->QueryDisplayCecPowerState(base::DoNothing());

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

TEST_F(CecServiceClientTest, QueryPowerStatusNoCecDevicesGivesEmptyResponse) {
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(cecservice::kGetTvsPowerStatus), _, _))
      .WillOnce(Invoke(GetTvsPowerStatusHandler({})));

  base::MockCallback<CecServiceClient::PowerStateCallback> callback;
  EXPECT_CALL(callback,
              Run(ContainerEq(std::vector<CecServiceClient::PowerState>())));

  client_->QueryDisplayCecPowerState(callback.Get());
  base::RunLoop().RunUntilIdle();
}

TEST_F(CecServiceClientTest, QueryPowerStatusOneDeviceIsPropagated) {
  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(cecservice::kGetTvsPowerStatus), _, _))
      .WillOnce(
          Invoke(GetTvsPowerStatusHandler({cecservice::kTvPowerStatusOn})));

  base::MockCallback<CecServiceClient::PowerStateCallback> callback;
  EXPECT_CALL(callback,
              Run(ContainerEq(std::vector<CecServiceClient::PowerState>(
                  {CecServiceClient::PowerState::kOn}))));

  client_->QueryDisplayCecPowerState(callback.Get());
  base::RunLoop().RunUntilIdle();
}

TEST_F(CecServiceClientTest, QueryPowerStatusAllStatesCorrectlyHandled) {
  std::vector<int32_t> power_states{
      cecservice::kTvPowerStatusError,
      cecservice::kTvPowerStatusAdapterNotConfigured,
      cecservice::kTvPowerStatusNoTv,
      cecservice::kTvPowerStatusOn,
      cecservice::kTvPowerStatusStandBy,
      cecservice::kTvPowerStatusToOn,
      cecservice::kTvPowerStatusToStandBy,
      cecservice::kTvPowerStatusUnknown,
  };

  EXPECT_CALL(*mock_proxy_.get(),
              DoCallMethod(HasMember(cecservice::kGetTvsPowerStatus), _, _))
      .WillOnce(Invoke(GetTvsPowerStatusHandler(std::move(power_states))));

  base::MockCallback<CecServiceClient::PowerStateCallback> callback;
  EXPECT_CALL(callback,
              Run(ContainerEq(std::vector<CecServiceClient::PowerState>({
                  CecServiceClient::PowerState::kError,
                  CecServiceClient::PowerState::kAdapterNotConfigured,
                  CecServiceClient::PowerState::kNoDevice,
                  CecServiceClient::PowerState::kOn,
                  CecServiceClient::PowerState::kStandBy,
                  CecServiceClient::PowerState::kTransitioningToOn,
                  CecServiceClient::PowerState::kTransitioningToStandBy,
                  CecServiceClient::PowerState::kUnknown,
              }))));

  client_->QueryDisplayCecPowerState(callback.Get());
  base::RunLoop().RunUntilIdle();
}

}  // namespace ash