buffet: Add DBus proxy class for command instance object

Added DBusCommandProxy class that implements
org.chromium.Buffet.Command DBus interface, including command
methods and properties.

BUG=chromium:374864
TEST=USE=buffet P2_TEST_FILTER="buffet::*" FEATURES=test emerge-link platform2

Change-Id: Iaf17f2b7c276edc1e9f3ca09a759a4a7d4dc3b10
Reviewed-on: https://chromium-review.googlesource.com/213267
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Christopher Wiley <wiley@chromium.org>
diff --git a/buffet/commands/dbus_command_proxy_unittest.cc b/buffet/commands/dbus_command_proxy_unittest.cc
new file mode 100644
index 0000000..8dd11a6
--- /dev/null
+++ b/buffet/commands/dbus_command_proxy_unittest.cc
@@ -0,0 +1,230 @@
+// 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 <functional>
+#include <memory>
+
+#include <dbus/mock_bus.h>
+#include <dbus/mock_exported_object.h>
+#include <dbus/property.h>
+#include <chromeos/dbus/dbus_object.h>
+#include <gtest/gtest.h>
+
+#include "buffet/commands/command_dictionary.h"
+#include "buffet/commands/command_instance.h"
+#include "buffet/commands/dbus_command_proxy.h"
+#include "buffet/commands/unittest_utils.h"
+#include "buffet/dbus_constants.h"
+
+using ::testing::AnyNumber;
+using ::testing::Return;
+using ::testing::Invoke;
+using ::testing::_;
+
+using chromeos::dbus_utils::ExportedObjectManager;
+using buffet::unittests::CreateDictionaryValue;
+
+namespace buffet {
+
+namespace {
+
+const char kTestCommandCategoty[] = "test_command_category";
+const char kTestCommandId[] = "cmd_1";
+
+void NoAction(bool all_succeeded) {}
+
+}  // namespace
+
+class DBusCommandProxyTest : public ::testing::Test {
+ public:
+  void SetUp() override {
+    // Set up a mock DBus bus object.
+    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());
+
+    // Command instance.
+    auto json = CreateDictionaryValue(R"({
+      'robot': {
+        'jump': {
+          'parameters': {
+            'height': {
+              'type': 'integer',
+              'minimum': 0,
+              'maximum': 100
+            },
+            '_jumpType': {
+              'type': 'string',
+              'enum': ['_withAirFlip', '_withSpin', '_withKick']
+            }
+          }
+        }
+      }
+    })");
+    CHECK(dict_.LoadCommands(*json, kTestCommandCategoty, nullptr, nullptr))
+        << "Failed to parse test command dictionary";
+
+    json = CreateDictionaryValue(R"({
+      'name': 'robot.jump',
+      'parameters': {
+        'height': 53,
+        '_jumpType': '_withKick'
+      }
+    })");
+    command_instance_ = CommandInstance::FromJson(json.get(), dict_, nullptr);
+    command_instance_->SetID(kTestCommandId);
+
+    // Set up a mock ExportedObject to be used with the DBus command proxy.
+    std::string cmd_path = dbus_constants::kCommandServicePathPrefix;
+    cmd_path += kTestCommandId;
+    const dbus::ObjectPath kCmdObjPath(cmd_path);
+    // Use a mock exported object for the exported object manager.
+    mock_exported_object_command_ =
+        new dbus::MockExportedObject(bus_.get(), kCmdObjPath);
+    EXPECT_CALL(*bus_, GetExportedObject(kCmdObjPath)).Times(AnyNumber())
+        .WillRepeatedly(Return(mock_exported_object_command_.get()));
+    EXPECT_CALL(*mock_exported_object_command_,
+                ExportMethod(_, _, _, _)).Times(AnyNumber());
+
+    command_proxy_.reset(new DBusCommandProxy(nullptr, bus_,
+                                              command_instance_.get()));
+    command_proxy_->RegisterAsync(base::Bind(NoAction));
+  }
+
+  void TearDown() override {
+    EXPECT_CALL(*mock_exported_object_command_, Unregister()).Times(1);
+    command_proxy_.reset();
+    command_instance_.reset();
+    dict_.Clear();
+    bus_ = nullptr;
+  }
+
+  chromeos::dbus_utils::DBusObject* GetProxyDBusObject() {
+    return &command_proxy_->dbus_object_;
+  }
+
+  std::string GetStatus() const {
+    return command_proxy_->status_.value();
+  }
+
+  int32_t GetProgress() const {
+    return command_proxy_->progress_.value();
+  }
+
+  std::unique_ptr<dbus::Response> CallMethod(
+      const std::string& method_name,
+      const std::function<void(dbus::MessageWriter*)>& param_callback) {
+    dbus::MethodCall method_call(dbus_constants::kCommandInterface,
+                                 method_name);
+    method_call.SetSerial(1234);
+    dbus::MessageWriter writer(&method_call);
+    if (param_callback)
+      param_callback(&writer);
+    return chromeos::dbus_utils::CallMethod(*GetProxyDBusObject(),
+                                            &method_call);
+  }
+
+  static bool IsResponseError(const std::unique_ptr<dbus::Response>& response) {
+    return (response->GetMessageType() == dbus::Message::MESSAGE_ERROR);
+  }
+
+  static void VerifyResponse(
+      const std::unique_ptr<dbus::Response>& response,
+      const std::function<void(dbus::MessageReader*)>& result_callback) {
+    EXPECT_FALSE(IsResponseError(response));
+    dbus::MessageReader reader(response.get());
+    if (result_callback)
+      result_callback(&reader);
+    EXPECT_FALSE(reader.HasMoreData());
+  }
+
+  template<typename T>
+  T GetPropertyValue(const std::string& property_name) {
+    dbus::MethodCall method_call(dbus::kPropertiesInterface,
+                                 dbus::kPropertiesGet);
+    method_call.SetSerial(1234);
+    dbus::MessageWriter writer(&method_call);
+    writer.AppendString(dbus_constants::kCommandInterface);
+    writer.AppendString(property_name);
+    auto response = chromeos::dbus_utils::CallMethod(*GetProxyDBusObject(),
+                                                     &method_call);
+    T value{};
+    VerifyResponse(response, [&value](dbus::MessageReader* reader) {
+      EXPECT_TRUE(chromeos::dbus_utils::PopValueFromReader(reader, &value));
+    });
+    return value;
+  }
+
+  std::unique_ptr<DBusCommandProxy> command_proxy_;
+  std::unique_ptr<CommandInstance> command_instance_;
+  CommandDictionary dict_;
+
+  scoped_refptr<dbus::MockExportedObject> mock_exported_object_command_;
+  scoped_refptr<dbus::MockBus> bus_;
+};
+
+TEST_F(DBusCommandProxyTest, Init) {
+  EXPECT_EQ(dbus_constants::kCommandStatusQueued, GetStatus());
+  EXPECT_EQ(0, GetProgress());
+  EXPECT_EQ("robot.jump",
+            GetPropertyValue<std::string>(dbus_constants::kCommandName));
+  EXPECT_EQ(kTestCommandCategoty,
+            GetPropertyValue<std::string>(dbus_constants::kCommandCategory));
+  EXPECT_EQ(kTestCommandId,
+            GetPropertyValue<std::string>(dbus_constants::kCommandId));
+  EXPECT_EQ(dbus_constants::kCommandStatusQueued,
+            GetPropertyValue<std::string>(dbus_constants::kCommandStatus));
+  EXPECT_EQ(0, GetPropertyValue<int32_t>(dbus_constants::kCommandProgress));
+}
+
+TEST_F(DBusCommandProxyTest, SetProgress) {
+  EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(2);
+  auto response = CallMethod(dbus_constants::kCommandSetProgress,
+                             [](dbus::MessageWriter* writer) {
+    writer->AppendInt32(10);
+  });
+  VerifyResponse(response, {});
+  EXPECT_EQ(dbus_constants::kCommandStatusInProgress, GetStatus());
+  EXPECT_EQ(10, GetProgress());
+  EXPECT_EQ(dbus_constants::kCommandStatusInProgress,
+            GetPropertyValue<std::string>(dbus_constants::kCommandStatus));
+  EXPECT_EQ(10, GetPropertyValue<int32_t>(dbus_constants::kCommandProgress));
+}
+
+TEST_F(DBusCommandProxyTest, SetProgress_OutOfRange) {
+  auto response = CallMethod(dbus_constants::kCommandSetProgress,
+                             [](dbus::MessageWriter* writer) {
+    writer->AppendInt32(110);
+  });
+  EXPECT_TRUE(IsResponseError(response));
+  EXPECT_EQ(dbus_constants::kCommandStatusQueued, GetStatus());
+  EXPECT_EQ(0, GetProgress());
+}
+
+TEST_F(DBusCommandProxyTest, Abort) {
+  EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(1);
+  auto response = CallMethod(dbus_constants::kCommandAbort, {});
+  VerifyResponse(response, {});
+  EXPECT_EQ(dbus_constants::kCommandStatusAborted, GetStatus());
+}
+
+TEST_F(DBusCommandProxyTest, Cancel) {
+  EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(1);
+  auto response = CallMethod(dbus_constants::kCommandCancel, {});
+  VerifyResponse(response, {});
+  EXPECT_EQ(dbus_constants::kCommandStatusCanceled, GetStatus());
+}
+
+TEST_F(DBusCommandProxyTest, Done) {
+  EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(2);
+  auto response = CallMethod(dbus_constants::kCommandDone, {});
+  VerifyResponse(response, {});
+  EXPECT_EQ(dbus_constants::kCommandStatusDone, GetStatus());
+  EXPECT_EQ(100, GetProgress());
+}
+
+}  // namespace buffet