// Copyright (c) 2012 The Chromium 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 "chromeos/dbus/modem_messaging_client.h"

#include "base/bind.h"
#include "base/message_loop/message_loop.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 chromeos {

namespace {

// A mock SmsReceivedHandler.
class MockSmsReceivedHandler {
 public:
  MOCK_METHOD2(Run, void(const dbus::ObjectPath &sms, bool complete));
};

// A mock DeleteCallback.
class MockDeleteCallback {
 public:
  MOCK_METHOD0(Run, void());
};

// A mock ListCallback.
class MockListCallback {
 public:
  MOCK_METHOD1(Run, void(const std::vector<dbus::ObjectPath>& result));
};

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

}  // namespace

class ModemMessagingClientTest : public testing::Test {
 public:
  ModemMessagingClientTest() : response_(NULL),
                               expected_result_(NULL) {}

  virtual 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(),
                ConnectToSignal(modemmanager::kModemManager1MessagingInterface,
                                modemmanager::kSMSAddedSignal,
                                _,
                                _))
        .WillRepeatedly(
             Invoke(this, &ModemMessagingClientTest::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.
    client_.reset(ModemMessagingClient::Create());
    client_->Init(mock_bus_.get());
  }

  virtual void TearDown() OVERRIDE {
    mock_bus_->ShutdownAndBlock();
  }

  // Handles Delete method call.
  void OnDelete(dbus::MethodCall* method_call,
                int timeout_ms,
                const dbus::ObjectProxy::ResponseCallback& callback) {
    EXPECT_EQ(modemmanager::kModemManager1MessagingInterface,
              method_call->GetInterface());
    EXPECT_EQ(modemmanager::kSMSDeleteFunction, method_call->GetMember());
    dbus::ObjectPath sms_path;
    dbus::MessageReader reader(method_call);
    EXPECT_TRUE(reader.PopObjectPath(&sms_path));
    EXPECT_EQ(expected_sms_path_, sms_path);
    EXPECT_FALSE(reader.HasMoreData());

    message_loop_.PostTask(FROM_HERE, base::Bind(callback, response_));
  }

  // Handles List method call.
  void OnList(dbus::MethodCall* method_call,
              int timeout_ms,
              const dbus::ObjectProxy::ResponseCallback& callback) {
    EXPECT_EQ(modemmanager::kModemManager1MessagingInterface,
              method_call->GetInterface());
    EXPECT_EQ(modemmanager::kSMSListFunction, method_call->GetMember());
    dbus::MessageReader reader(method_call);
    EXPECT_FALSE(reader.HasMoreData());

    message_loop_.PostTask(FROM_HERE, base::Bind(callback, response_));
  }

  // Checks the results of List.
  void CheckResult(const std::vector<dbus::ObjectPath>& result) {
    EXPECT_EQ(result, *expected_result_);
  }

 protected:
  // The client to be tested.
  scoped_ptr<ModemMessagingClient> client_;
  // A message loop to emulate asynchronous behavior.
  base::MessageLoop message_loop_;
  // The mock bus.
  scoped_refptr<dbus::MockBus> mock_bus_;
  // The mock object proxy.
  scoped_refptr<dbus::MockObjectProxy> mock_proxy_;
  // The SmsReceived signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback sms_received_callback_;
  // Expected argument for Delete method.
  dbus::ObjectPath expected_sms_path_;
  // Response returned by mock methods.
  dbus::Response* response_;
  // Expected result of List method.
  std::vector<dbus::ObjectPath>* expected_result_;

 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,
      const dbus::ObjectProxy::OnConnectedCallback& on_connected_callback) {
    sms_received_callback_ = signal_callback;
    const bool success = true;
    message_loop_.PostTask(FROM_HERE, base::Bind(on_connected_callback,
                                                 interface_name,
                                                 signal_name,
                                                 success));
  }
};

TEST_F(ModemMessagingClientTest, SmsReceived) {
  // Set expectations.
  const dbus::ObjectPath kSmsPath("/SMS/0");
  const bool kComplete = true;
  MockSmsReceivedHandler handler;
  EXPECT_CALL(handler, Run(kSmsPath, kComplete)).Times(1);
  // Set handler.
  client_->SetSmsReceivedHandler(kServiceName, dbus::ObjectPath(kObjectPath),
                                 base::Bind(&MockSmsReceivedHandler::Run,
                                            base::Unretained(&handler)));

  // Run the message loop to run the signal connection result callback.
  message_loop_.RunUntilIdle();

  // Send signal.
  dbus::Signal signal(modemmanager::kModemManager1MessagingInterface,
                      modemmanager::kSMSAddedSignal);
  dbus::MessageWriter writer(&signal);
  writer.AppendObjectPath(kSmsPath);
  writer.AppendBool(kComplete);
  ASSERT_FALSE(sms_received_callback_.is_null());
  sms_received_callback_.Run(&signal);
  // Reset handler.
  client_->ResetSmsReceivedHandler(kServiceName, dbus::ObjectPath(kObjectPath));
  // Send signal again.
  sms_received_callback_.Run(&signal);
}

TEST_F(ModemMessagingClientTest, Delete) {
  // Set expectations.
  const dbus::ObjectPath kSmsPath("/SMS/0");
  expected_sms_path_ = kSmsPath;
  EXPECT_CALL(*mock_proxy_.get(), CallMethod(_, _, _))
      .WillOnce(Invoke(this, &ModemMessagingClientTest::OnDelete));
  MockDeleteCallback callback;
  EXPECT_CALL(callback, Run()).Times(1);
  // Create response.
  scoped_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
  response_ = response.get();
  // Call Delete.
  client_->Delete(kServiceName, dbus::ObjectPath(kObjectPath), kSmsPath,
                  base::Bind(&MockDeleteCallback::Run,
                             base::Unretained(&callback)));

  // Run the message loop.
  message_loop_.RunUntilIdle();
}

TEST_F(ModemMessagingClientTest, List) {
  // Set expectations.
  EXPECT_CALL(*mock_proxy_.get(), CallMethod(_, _, _))
      .WillOnce(Invoke(this, &ModemMessagingClientTest::OnList));
  MockListCallback callback;
  EXPECT_CALL(callback, Run(_))
      .WillOnce(Invoke(this, &ModemMessagingClientTest::CheckResult));
  // Create response.
  scoped_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
  dbus::ObjectPath path1("/SMS/1");
  dbus::ObjectPath path2("/SMS/2");
  std::vector<dbus::ObjectPath> expected_result;
  expected_result.push_back(path1);
  expected_result.push_back(path2);

  dbus::MessageWriter writer(response.get());
  writer.AppendArrayOfObjectPaths(expected_result);
  response_ = response.get();

  // Save expected result.
  expected_result_ = &expected_result;
  // Call List.
  client_->List(kServiceName, dbus::ObjectPath(kObjectPath),
                base::Bind(&MockListCallback::Run,
                           base::Unretained(&callback)));

  // Run the message loop.
  message_loop_.RunUntilIdle();
}

}  // namespace chromeos
