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()); +}