chromium/chromeos/ash/components/dbus/chromebox_for_meetings/cfm_hotline_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/chromebox_for_meetings/cfm_hotline_client.h"

#include <algorithm>
#include <atomic>
#include <functional>
#include <memory>
#include <utility>
#include <vector>

#include "base/files/file.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.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/cfm/dbus-constants.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

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

namespace ash {

class FakeCfmObserver : public cfm::CfmObserver {
 public:
  FakeCfmObserver() { CfmHotlineClient::Get()->AddObserver(this); }
  ~FakeCfmObserver() override { CfmHotlineClient::Get()->RemoveObserver(this); }

  bool ServiceRequestReceived(const std::string& service_id) override {
    request_service_id_ = std::move(service_id);
    return true;
  }

  std::string request_service_id_;
};

class CfmHotlineClientTest : public testing::Test {
 public:
  CfmHotlineClientTest() = default;
  ~CfmHotlineClientTest() override = default;

  void SetUp() override {
    dbus::Bus::Options options;
    options.bus_type = dbus::Bus::SYSTEM;

    // Filter uninteresting calls to the bus
    mock_bus_ = base::MakeRefCounted<::testing::NiceMock<dbus::MockBus>>(
        dbus::Bus::Options());

    mock_proxy_ = base::MakeRefCounted<dbus::MockObjectProxy>(
        mock_bus_.get(), ::cfm::broker::kServiceName,
        dbus::ObjectPath(::cfm::broker::kServicePath));

    EXPECT_CALL(*mock_bus_.get(),
                GetObjectProxy(::cfm::broker::kServiceName,
                               dbus::ObjectPath(::cfm::broker::kServicePath)))
        .WillOnce(Return(mock_proxy_.get()));

    EXPECT_CALL(
        *mock_proxy_.get(),
        DoConnectToSignal(::cfm::broker::kServiceInterfaceName, _, _, _))
        .WillRepeatedly(Invoke(this, &CfmHotlineClientTest::ConnectToSignal));

    CfmHotlineClient::Initialize(mock_bus_.get());

    // The easiest source of fds is opening /dev/null.
    test_file_ = base::File(base::FilePath("/dev/null"),
                            base::File::FLAG_OPEN | base::File::FLAG_WRITE);
    ASSERT_TRUE(test_file_.IsValid());

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

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

  void CallMethod(dbus::MethodCall* method_call,
                  int timeout_ms,
                  dbus::ObjectProxy::ResponseCallback* callback) {
    dbus::Response* response = nullptr;

    if (!responses_.empty()) {
      used_responses_.push_back(std::move(responses_.front()));
      responses_.pop_front();
      response = used_responses_.back().get();
    }

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

  void EmitServiceRequestedSignal(const std::string& interface_name) {
    dbus::Signal signal(::cfm::broker::kServiceInterfaceName,
                        ::cfm::broker::kMojoServiceRequestedSignal);
    dbus::MessageWriter writer(&signal);
    writer.AppendString(interface_name);

    // simulate 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);
  }

 protected:
  // Handles calls to |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) {
    EXPECT_EQ(interface_name, ::cfm::broker::kServiceInterfaceName);
    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 */));
  }

  base::test::SingleThreadTaskEnvironment task_environment_;
  scoped_refptr<dbus::MockBus> mock_bus_;
  scoped_refptr<dbus::MockObjectProxy> mock_proxy_;
  std::deque<std::unique_ptr<dbus::Response>> responses_;
  base::File test_file_;

 private:
  std::deque<std::unique_ptr<dbus::Response>> used_responses_;
  // Maps from biod signal name to the corresponding callback provided by the
  // CfmHotlineClient.
  std::map<std::string, dbus::ObjectProxy::SignalCallback> signal_callbacks_;
};

TEST_F(CfmHotlineClientTest, BootstrapMojoSuccessTest) {
  responses_.push_back(dbus::Response::CreateEmpty());

  EXPECT_CALL(*mock_proxy_.get(), DoCallMethod(_, _, _))
      .WillOnce(Invoke(this, &CfmHotlineClientTest::CallMethod));

  base::MockCallback<CfmHotlineClient::BootstrapMojoConnectionCallback>
      callback;
  EXPECT_CALL(callback, Run(true)).Times(1);

  CfmHotlineClient::Get()->BootstrapMojoConnection(
      base::ScopedFD(test_file_.TakePlatformFile()), callback.Get());

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

TEST_F(CfmHotlineClientTest, BootstrapMojoFailureTest) {
  EXPECT_CALL(*mock_proxy_.get(), DoCallMethod(_, _, _))
      .WillOnce(Invoke(this, &CfmHotlineClientTest::CallMethod));

  base::MockCallback<CfmHotlineClient::BootstrapMojoConnectionCallback>
      callback;
  EXPECT_CALL(callback, Run(false)).Times(1);

  // Fail with no normal or error response
  CfmHotlineClient::Get()->BootstrapMojoConnection(
      base::ScopedFD(test_file_.TakePlatformFile()), callback.Get());

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

TEST_F(CfmHotlineClientTest, EmitMojoServiceRequestedSignal) {
  base::RunLoop run_loop;
  const std::string interface_name = "Foo";

  FakeCfmObserver observer;
  EmitServiceRequestedSignal(interface_name);

  run_loop.RunUntilIdle();
  EXPECT_EQ(observer.request_service_id_, interface_name);
}

}  // namespace ash