// 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/exported_object_manager.h>

#include <base/bind.h>
#include <brillo/dbus/dbus_object_test_helpers.h>
#include <brillo/dbus/utils.h>
#include <dbus/mock_bus.h>
#include <dbus/mock_exported_object.h>
#include <dbus/object_manager.h>
#include <dbus/object_path.h>
#include <gtest/gtest.h>

using ::testing::AnyNumber;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::_;

namespace brillo {

namespace dbus_utils {

namespace {

const dbus::ObjectPath kTestPath(std::string("/test/om_path"));
const dbus::ObjectPath kClaimedTestPath(std::string("/test/claimed_path"));
const std::string kClaimedInterface("claimed.interface");
const std::string kTestPropertyName("PropertyName");
const std::string kTestPropertyValue("PropertyValue");

void WriteTestPropertyDict(VariantDictionary* dict) {
  dict->insert(std::make_pair(kTestPropertyName, Any(kTestPropertyValue)));
}

void ReadTestPropertyDict(dbus::MessageReader* reader) {
  dbus::MessageReader all_properties(nullptr);
  dbus::MessageReader each_property(nullptr);
  ASSERT_TRUE(reader->PopArray(&all_properties));
  ASSERT_TRUE(all_properties.PopDictEntry(&each_property));
  std::string property_name;
  std::string property_value;
  ASSERT_TRUE(each_property.PopString(&property_name));
  ASSERT_TRUE(each_property.PopVariantOfString(&property_value));
  EXPECT_FALSE(each_property.HasMoreData());
  EXPECT_FALSE(all_properties.HasMoreData());
  EXPECT_EQ(property_name, kTestPropertyName);
  EXPECT_EQ(property_value, kTestPropertyValue);
}

void VerifyInterfaceClaimSignal(dbus::Signal* signal) {
  EXPECT_EQ(signal->GetInterface(), std::string(dbus::kObjectManagerInterface));
  EXPECT_EQ(signal->GetMember(),
            std::string(dbus::kObjectManagerInterfacesAdded));
  //   org.freedesktop.DBus.ObjectManager.InterfacesAdded (
  //       OBJPATH object_path,
  //       DICT<STRING,DICT<STRING,VARIANT>> interfaces_and_properties);
  dbus::MessageReader reader(signal);
  dbus::MessageReader all_interfaces(nullptr);
  dbus::MessageReader each_interface(nullptr);
  dbus::ObjectPath path;
  ASSERT_TRUE(reader.PopObjectPath(&path));
  ASSERT_TRUE(reader.PopArray(&all_interfaces));
  ASSERT_TRUE(all_interfaces.PopDictEntry(&each_interface));
  std::string interface_name;
  ASSERT_TRUE(each_interface.PopString(&interface_name));
  ReadTestPropertyDict(&each_interface);
  EXPECT_FALSE(each_interface.HasMoreData());
  EXPECT_FALSE(all_interfaces.HasMoreData());
  EXPECT_FALSE(reader.HasMoreData());
  EXPECT_EQ(interface_name, kClaimedInterface);
  EXPECT_EQ(path, kClaimedTestPath);
}

void VerifyInterfaceDropSignal(dbus::Signal* signal) {
  EXPECT_EQ(signal->GetInterface(), std::string(dbus::kObjectManagerInterface));
  EXPECT_EQ(signal->GetMember(),
            std::string(dbus::kObjectManagerInterfacesRemoved));
  //   org.freedesktop.DBus.ObjectManager.InterfacesRemoved (
  //       OBJPATH object_path, ARRAY<STRING> interfaces);
  dbus::MessageReader reader(signal);
  dbus::MessageReader each_interface(nullptr);
  dbus::ObjectPath path;
  ASSERT_TRUE(reader.PopObjectPath(&path));
  ASSERT_TRUE(reader.PopArray(&each_interface));
  std::string interface_name;
  ASSERT_TRUE(each_interface.PopString(&interface_name));
  EXPECT_FALSE(each_interface.HasMoreData());
  EXPECT_FALSE(reader.HasMoreData());
  EXPECT_EQ(interface_name, kClaimedInterface);
  EXPECT_EQ(path, kClaimedTestPath);
}

}  // namespace

class ExportedObjectManagerTest : 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 exported object.
    mock_exported_object_ = new dbus::MockExportedObject(bus_.get(), kTestPath);
    EXPECT_CALL(*bus_, GetExportedObject(kTestPath)).Times(1).WillOnce(
        Return(mock_exported_object_.get()));
    EXPECT_CALL(*mock_exported_object_, ExportMethod(_, _, _, _))
        .Times(AnyNumber());
    om_.reset(new ExportedObjectManager(bus_.get(), kTestPath));
    property_writer_ = base::Bind(&WriteTestPropertyDict);
    om_->RegisterAsync(AsyncEventSequencer::GetDefaultCompletionAction());
  }

  void TearDown() override {
    EXPECT_CALL(*mock_exported_object_, Unregister()).Times(1);
    om_.reset();
    bus_ = nullptr;
  }

  std::unique_ptr<dbus::Response> CallHandleGetManagedObjects() {
    dbus::MethodCall method_call(dbus::kObjectManagerInterface,
                                 dbus::kObjectManagerGetManagedObjects);
    method_call.SetSerial(1234);
    return brillo::dbus_utils::testing::CallMethod(om_->dbus_object_,
                                                   &method_call);
  }

  scoped_refptr<dbus::MockBus> bus_;
  scoped_refptr<dbus::MockExportedObject> mock_exported_object_;
  std::unique_ptr<ExportedObjectManager> om_;
  ExportedPropertySet::PropertyWriter property_writer_;
};

TEST_F(ExportedObjectManagerTest, ClaimInterfaceSendsSignals) {
  EXPECT_CALL(*mock_exported_object_, SendSignal(_))
      .Times(1).WillOnce(Invoke(&VerifyInterfaceClaimSignal));
  om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_);
}

TEST_F(ExportedObjectManagerTest, ReleaseInterfaceSendsSignals) {
  InSequence dummy;
  EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1);
  EXPECT_CALL(*mock_exported_object_, SendSignal(_))
      .Times(1).WillOnce(Invoke(&VerifyInterfaceDropSignal));
  om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_);
  om_->ReleaseInterface(kClaimedTestPath, kClaimedInterface);
}

TEST_F(ExportedObjectManagerTest, GetManagedObjectsResponseEmptyCorrectness) {
  auto response = CallHandleGetManagedObjects();
  dbus::MessageReader reader(response.get());
  dbus::MessageReader all_paths(nullptr);
  ASSERT_TRUE(reader.PopArray(&all_paths));
  EXPECT_FALSE(reader.HasMoreData());
}

TEST_F(ExportedObjectManagerTest, GetManagedObjectsResponseCorrectness) {
  // org.freedesktop.DBus.ObjectManager.GetManagedObjects (
  //     out DICT<OBJPATH,
  //              DICT<STRING,
  //                   DICT<STRING,VARIANT>>> )
  EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1);
  om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_);
  auto response = CallHandleGetManagedObjects();
  dbus::MessageReader reader(response.get());
  dbus::MessageReader all_paths(nullptr);
  dbus::MessageReader each_path(nullptr);
  dbus::MessageReader all_interfaces(nullptr);
  dbus::MessageReader each_interface(nullptr);
  ASSERT_TRUE(reader.PopArray(&all_paths));
  ASSERT_TRUE(all_paths.PopDictEntry(&each_path));
  dbus::ObjectPath path;
  ASSERT_TRUE(each_path.PopObjectPath(&path));
  ASSERT_TRUE(each_path.PopArray(&all_interfaces));
  ASSERT_TRUE(all_interfaces.PopDictEntry(&each_interface));
  std::string interface_name;
  ASSERT_TRUE(each_interface.PopString(&interface_name));
  ReadTestPropertyDict(&each_interface);
  EXPECT_FALSE(each_interface.HasMoreData());
  EXPECT_FALSE(all_interfaces.HasMoreData());
  EXPECT_FALSE(each_path.HasMoreData());
  EXPECT_FALSE(all_paths.HasMoreData());
  EXPECT_FALSE(reader.HasMoreData());
  EXPECT_EQ(path, kClaimedTestPath);
  EXPECT_EQ(interface_name, kClaimedInterface);
}

}  // namespace dbus_utils

}  // namespace brillo