buffet: Add parsing of command instances from JSON
CommandInstance class can be created from a JSON object
with proper command and parameter value validation against
command definition schema.
BUG=chromium:396713
TEST=USE=buffet P2_TEST_FILTER="buffet::*" FEATURES=test emerge-link platform2
Change-Id: Iba4c807225552f6a9d8b33a0aa1fc451e75753a4
Reviewed-on: https://chromium-review.googlesource.com/211338
Reviewed-by: Christopher Wiley <wiley@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/commands/command_instance_unittest.cc b/buffet/commands/command_instance_unittest.cc
index 0577004..cb639b5 100644
--- a/buffet/commands/command_instance_unittest.cc
+++ b/buffet/commands/command_instance_unittest.cc
@@ -4,8 +4,62 @@
#include <gtest/gtest.h>
+#include "buffet/commands/command_dictionary.h"
#include "buffet/commands/command_instance.h"
#include "buffet/commands/prop_types.h"
+#include "buffet/commands/unittest_utils.h"
+
+using buffet::unittests::CreateDictionaryValue;
+using buffet::unittests::CreateValue;
+
+namespace {
+
+class CommandInstanceTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() override {
+ auto json = CreateDictionaryValue(R"({
+ 'base': {
+ 'reboot': {
+ 'parameters': {}
+ }
+ },
+ 'robot': {
+ 'jump': {
+ 'parameters': {
+ 'height': {
+ 'type': 'integer',
+ 'minimum': 0,
+ 'maximum': 100
+ },
+ '_jumpType': {
+ 'type': 'string',
+ 'enum': ['_withAirFlip', '_withSpin', '_withKick']
+ }
+ }
+ },
+ 'speak': {
+ 'parameters': {
+ 'phrase': {
+ 'type': 'string',
+ 'enum': ['beamMeUpScotty', 'iDontDigOnSwine',
+ 'iPityDaFool', 'dangerWillRobinson']
+ },
+ 'volume': {
+ 'type': 'integer',
+ 'minimum': 0,
+ 'maximum': 10
+ }
+ }
+ }
+ }
+ })");
+ CHECK(dict_.LoadCommands(*json, "robotd", nullptr, nullptr))
+ << "Failed to parse test command dictionary";
+ }
+ buffet::CommandDictionary dict_;
+};
+
+} // anonymous namespace
TEST(CommandInstance, Test) {
buffet::native_types::Object params;
@@ -23,3 +77,92 @@
EXPECT_EQ(100, instance.FindParameter("volume")->GetInt()->GetValue());
EXPECT_EQ(nullptr, instance.FindParameter("blah").get());
}
+
+TEST_F(CommandInstanceTest, FromJson) {
+ auto json = CreateDictionaryValue(R"({
+ 'name': 'robot.jump',
+ 'parameters': {
+ 'height': 53,
+ '_jumpType': '_withKick'
+ }
+ })");
+ auto instance = buffet::CommandInstance::FromJson(json.get(), dict_, nullptr);
+ EXPECT_EQ("robot.jump", instance->GetName());
+ EXPECT_EQ("robotd", instance->GetCategory());
+ EXPECT_EQ(53, instance->FindParameter("height")->GetInt()->GetValue());
+ EXPECT_EQ("_withKick",
+ instance->FindParameter("_jumpType")->GetString()->GetValue());
+}
+
+TEST_F(CommandInstanceTest, FromJson_ParamsOmitted) {
+ auto json = CreateDictionaryValue("{'name': 'base.reboot'}");
+ auto instance = buffet::CommandInstance::FromJson(json.get(), dict_, nullptr);
+ EXPECT_EQ("base.reboot", instance->GetName());
+ EXPECT_EQ("robotd", instance->GetCategory());
+ EXPECT_TRUE(instance->GetParameters().empty());
+}
+
+TEST_F(CommandInstanceTest, FromJson_NotObject) {
+ auto json = CreateValue("'string'");
+ buffet::ErrorPtr error;
+ auto instance = buffet::CommandInstance::FromJson(json.get(), dict_, &error);
+ EXPECT_EQ(nullptr, instance.get());
+ EXPECT_EQ("json_object_expected", error->GetCode());
+ EXPECT_EQ("Command instance is not a JSON object", error->GetMessage());
+}
+
+TEST_F(CommandInstanceTest, FromJson_NameMissing) {
+ auto json = CreateDictionaryValue("{'param': 'value'}");
+ buffet::ErrorPtr error;
+ auto instance = buffet::CommandInstance::FromJson(json.get(), dict_, &error);
+ EXPECT_EQ(nullptr, instance.get());
+ EXPECT_EQ("parameter_missing", error->GetCode());
+ EXPECT_EQ("Command name is missing", error->GetMessage());
+}
+
+TEST_F(CommandInstanceTest, FromJson_UnknownCommand) {
+ auto json = CreateDictionaryValue("{'name': 'robot.scream'}");
+ buffet::ErrorPtr error;
+ auto instance = buffet::CommandInstance::FromJson(json.get(), dict_, &error);
+ EXPECT_EQ(nullptr, instance.get());
+ EXPECT_EQ("invalid_command_name", error->GetCode());
+ EXPECT_EQ("Unknown command received: robot.scream", error->GetMessage());
+}
+
+TEST_F(CommandInstanceTest, FromJson_ParamsNotObject) {
+ auto json = CreateDictionaryValue(R"({
+ 'name': 'robot.speak',
+ 'parameters': 'hello'
+ })");
+ buffet::ErrorPtr error;
+ auto instance = buffet::CommandInstance::FromJson(json.get(), dict_, &error);
+ EXPECT_EQ(nullptr, instance.get());
+ auto inner = error->GetInnerError();
+ EXPECT_EQ("json_object_expected", inner->GetCode());
+ EXPECT_EQ("Property 'parameters' must be a JSON object", inner->GetMessage());
+ EXPECT_EQ("command_failed", error->GetCode());
+ EXPECT_EQ("Failed to validate command 'robot.speak'", error->GetMessage());
+}
+
+TEST_F(CommandInstanceTest, FromJson_ParamError) {
+ auto json = CreateDictionaryValue(R"({
+ 'name': 'robot.speak',
+ 'parameters': {
+ 'phrase': 'iPityDaFool',
+ 'volume': 20
+ }
+ })");
+ buffet::ErrorPtr error;
+ auto instance = buffet::CommandInstance::FromJson(json.get(), dict_, &error);
+ EXPECT_EQ(nullptr, instance.get());
+ auto first = error->GetFirstError();
+ EXPECT_EQ("out_of_range", first->GetCode());
+ EXPECT_EQ("Value 20 is out of range. It must not be greater than 10",
+ first->GetMessage());
+ auto inner = error->GetInnerError();
+ EXPECT_EQ("invalid_parameter_value", inner->GetCode());
+ EXPECT_EQ("Invalid parameter value for property 'volume'",
+ inner->GetMessage());
+ EXPECT_EQ("command_failed", error->GetCode());
+ EXPECT_EQ("Failed to validate command 'robot.speak'", error->GetMessage());
+}