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

#include <brillo/dbus/dbus_signal_handler.h>

#include <string>

#include <brillo/bind_lambda.h>
#include <brillo/dbus/dbus_param_writer.h>
#include <dbus/mock_bus.h>
#include <dbus/mock_object_proxy.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>

using testing::AnyNumber;
using testing::Return;
using testing::SaveArg;
using testing::_;

namespace brillo {
namespace dbus_utils {

const char kTestPath[] = "/test/path";
const char kTestServiceName[] = "org.test.Object";
const char kInterface[] = "org.test.Object.TestInterface";
const char kSignal[] = "TestSignal";

class DBusSignalHandlerTest : public testing::Test {
 public:
  void SetUp() override {
    dbus::Bus::Options options;
    options.bus_type = dbus::Bus::SYSTEM;
    bus_ = new dbus::MockBus(options);
    // By default, don't worry about threading assertions.
    EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber());
    EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber());
    // Use a mock object proxy.
    mock_object_proxy_ = new dbus::MockObjectProxy(
        bus_.get(), kTestServiceName, dbus::ObjectPath(kTestPath));
    EXPECT_CALL(*bus_,
                GetObjectProxy(kTestServiceName, dbus::ObjectPath(kTestPath)))
        .WillRepeatedly(Return(mock_object_proxy_.get()));
  }

  void TearDown() override { bus_ = nullptr; }

 protected:
  template<typename SignalHandlerSink, typename... Args>
  void CallSignal(SignalHandlerSink* sink, Args... args) {
    dbus::ObjectProxy::SignalCallback signal_callback;
    EXPECT_CALL(*mock_object_proxy_, ConnectToSignal(kInterface, kSignal, _, _))
        .WillOnce(SaveArg<2>(&signal_callback));

    brillo::dbus_utils::ConnectToSignal(
        mock_object_proxy_.get(),
        kInterface,
        kSignal,
        base::Bind(&SignalHandlerSink::Handler, base::Unretained(sink)),
        {});

    dbus::Signal signal(kInterface, kSignal);
    dbus::MessageWriter writer(&signal);
    DBusParamWriter::Append(&writer, args...);
    signal_callback.Run(&signal);
  }

  scoped_refptr<dbus::MockBus> bus_;
  scoped_refptr<dbus::MockObjectProxy> mock_object_proxy_;
};

TEST_F(DBusSignalHandlerTest, ConnectToSignal) {
  EXPECT_CALL(*mock_object_proxy_, ConnectToSignal(kInterface, kSignal, _, _))
      .Times(1);

  brillo::dbus_utils::ConnectToSignal(
      mock_object_proxy_.get(), kInterface, kSignal, base::Closure{}, {});
}

TEST_F(DBusSignalHandlerTest, CallSignal_3Args) {
  class SignalHandlerSink {
   public:
    MOCK_METHOD3(Handler, void(int, int, double));
  } sink;

  EXPECT_CALL(sink, Handler(10, 20, 30.5)).Times(1);
  CallSignal(&sink, 10, 20, 30.5);
}

TEST_F(DBusSignalHandlerTest, CallSignal_2Args) {
  class SignalHandlerSink {
   public:
    // Take string both by reference and by value to make sure this works too.
    MOCK_METHOD2(Handler, void(const std::string&, std::string));
  } sink;

  EXPECT_CALL(sink, Handler(std::string{"foo"}, std::string{"bar"})).Times(1);
  CallSignal(&sink, std::string{"foo"}, std::string{"bar"});
}

TEST_F(DBusSignalHandlerTest, CallSignal_NoArgs) {
  class SignalHandlerSink {
   public:
    MOCK_METHOD0(Handler, void());
  } sink;

  EXPECT_CALL(sink, Handler()).Times(1);
  CallSignal(&sink);
}

TEST_F(DBusSignalHandlerTest, CallSignal_Error_TooManyArgs) {
  class SignalHandlerSink {
   public:
    MOCK_METHOD0(Handler, void());
  } sink;

  // Handler() expects no args, but we send an int.
  EXPECT_CALL(sink, Handler()).Times(0);
  CallSignal(&sink, 5);
}

TEST_F(DBusSignalHandlerTest, CallSignal_Error_TooFewArgs) {
  class SignalHandlerSink {
   public:
    MOCK_METHOD2(Handler, void(std::string, bool));
  } sink;

  // Handler() expects 2 args while we send it just one.
  EXPECT_CALL(sink, Handler(_, _)).Times(0);
  CallSignal(&sink, std::string{"bar"});
}

TEST_F(DBusSignalHandlerTest, CallSignal_Error_TypeMismatchArgs) {
  class SignalHandlerSink {
   public:
    MOCK_METHOD2(Handler, void(std::string, bool));
  } sink;

  // Handler() expects "sb" while we send it "ii".
  EXPECT_CALL(sink, Handler(_, _)).Times(0);
  CallSignal(&sink, 1, 2);
}

}  // namespace dbus_utils
}  // namespace brillo