| // Copyright 2015 The Weave 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 "src/commands/command_dictionary.h" |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/commands/unittest_utils.h" |
| |
| namespace weave { |
| |
| using test::CreateDictionaryValue; |
| using test::IsEqualValue; |
| |
| TEST(CommandDictionary, Empty) { |
| CommandDictionary dict; |
| EXPECT_TRUE(dict.IsEmpty()); |
| EXPECT_EQ(nullptr, dict.FindCommand("robot.jump")); |
| } |
| |
| TEST(CommandDictionary, LoadCommands) { |
| auto json = CreateDictionaryValue(R"({ |
| 'robot': { |
| 'jump': { |
| 'parameters': { |
| 'height': 'integer', |
| '_jumpType': ['_withAirFlip', '_withSpin', '_withKick'] |
| }, |
| 'progress': { |
| 'progress': 'integer' |
| }, |
| 'results': {} |
| } |
| } |
| })"); |
| CommandDictionary dict; |
| EXPECT_TRUE(dict.LoadCommands(*json, nullptr, nullptr)); |
| EXPECT_EQ(1, dict.GetSize()); |
| EXPECT_NE(nullptr, dict.FindCommand("robot.jump")); |
| json = CreateDictionaryValue(R"({ |
| 'base': { |
| 'reboot': { |
| 'parameters': {'delay': 'integer'} |
| }, |
| 'shutdown': { |
| } |
| } |
| })"); |
| EXPECT_TRUE(dict.LoadCommands(*json, nullptr, nullptr)); |
| EXPECT_EQ(3, dict.GetSize()); |
| EXPECT_NE(nullptr, dict.FindCommand("robot.jump")); |
| EXPECT_NE(nullptr, dict.FindCommand("base.reboot")); |
| EXPECT_NE(nullptr, dict.FindCommand("base.shutdown")); |
| EXPECT_EQ(nullptr, dict.FindCommand("foo.bar")); |
| } |
| |
| TEST(CommandDictionary, LoadWithInheritance) { |
| auto json = CreateDictionaryValue(R"({ |
| 'robot': { |
| 'jump': { |
| 'minimalRole': 'viewer', |
| 'visibility':'local', |
| 'parameters': { |
| 'height': 'integer' |
| }, |
| 'progress': { |
| 'progress': 'integer' |
| }, |
| 'results': { |
| 'success': 'boolean' |
| } |
| } |
| } |
| })"); |
| CommandDictionary base_dict; |
| EXPECT_TRUE(base_dict.LoadCommands(*json, nullptr, nullptr)); |
| EXPECT_EQ(1, base_dict.GetSize()); |
| json = CreateDictionaryValue(R"({'robot': {'jump': {}}})"); |
| |
| CommandDictionary dict; |
| EXPECT_TRUE(dict.LoadCommands(*json, &base_dict, nullptr)); |
| EXPECT_EQ(1, dict.GetSize()); |
| |
| auto cmd = dict.FindCommand("robot.jump"); |
| EXPECT_NE(nullptr, cmd); |
| |
| EXPECT_EQ("local", cmd->GetVisibility().ToString()); |
| EXPECT_EQ(UserRole::kViewer, cmd->GetMinimalRole()); |
| |
| EXPECT_JSON_EQ("{'height': {'type': 'integer'}}", |
| *cmd->GetParameters()->ToJson(true, true)); |
| EXPECT_JSON_EQ("{'progress': {'type': 'integer'}}", |
| *cmd->GetProgress()->ToJson(true, false)); |
| EXPECT_JSON_EQ("{'success': {'type': 'boolean'}}", |
| *cmd->GetResults()->ToJson(true, false)); |
| } |
| |
| TEST(CommandDictionary, LoadCommands_Failures) { |
| CommandDictionary dict; |
| ErrorPtr error; |
| |
| // Command definition is not an object. |
| auto json = CreateDictionaryValue("{'robot':{'jump':0}}"); |
| EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error)); |
| EXPECT_EQ("type_mismatch", error->GetCode()); |
| error.reset(); |
| |
| // Package definition is not an object. |
| json = CreateDictionaryValue("{'robot':'blah'}"); |
| EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error)); |
| EXPECT_EQ("type_mismatch", error->GetCode()); |
| error.reset(); |
| |
| // Invalid command definition is not an object. |
| json = CreateDictionaryValue( |
| "{'robot':{'jump':{'parameters':{'flip':0},'results':{}}}}"); |
| EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error)); |
| EXPECT_EQ("invalid_object_schema", error->GetCode()); |
| EXPECT_NE(nullptr, error->GetInnerError()); // Must have additional info. |
| error.reset(); |
| |
| // Empty command name. |
| json = CreateDictionaryValue("{'robot':{'':{'parameters':{},'results':{}}}}"); |
| EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error)); |
| EXPECT_EQ("invalid_command_name", error->GetCode()); |
| error.reset(); |
| } |
| |
| TEST(CommandDictionaryDeathTest, LoadCommands_RedefineInDifferentCategory) { |
| // Redefine commands in different category. |
| CommandDictionary dict; |
| ErrorPtr error; |
| auto json = CreateDictionaryValue("{'robot':{'jump':{}}}"); |
| dict.LoadCommands(*json, nullptr, &error); |
| ASSERT_DEATH(dict.LoadCommands(*json, nullptr, &error), |
| ".*Definition for command 'robot.jump' overrides an " |
| "earlier definition"); |
| } |
| |
| TEST(CommandDictionary, LoadCommands_CustomCommandNaming) { |
| // Custom command must start with '_'. |
| CommandDictionary base_dict; |
| CommandDictionary dict; |
| ErrorPtr error; |
| auto json = CreateDictionaryValue(R"({ |
| 'base': { |
| 'reboot': { |
| 'parameters': {'delay': 'integer'}, |
| 'results': {} |
| } |
| } |
| })"); |
| base_dict.LoadCommands(*json, nullptr, &error); |
| EXPECT_TRUE(dict.LoadCommands(*json, &base_dict, &error)); |
| auto json2 = |
| CreateDictionaryValue("{'base':{'jump':{'parameters':{},'results':{}}}}"); |
| EXPECT_FALSE(dict.LoadCommands(*json2, &base_dict, &error)); |
| EXPECT_EQ("invalid_command_name", error->GetCode()); |
| error.reset(); |
| |
| // If the command starts with "_", then it's Ok. |
| json2 = CreateDictionaryValue( |
| "{'base':{'_jump':{'parameters':{},'results':{}}}}"); |
| EXPECT_TRUE(dict.LoadCommands(*json2, &base_dict, nullptr)); |
| } |
| |
| TEST(CommandDictionary, LoadCommands_RedefineStdCommand) { |
| // Redefine commands parameter type. |
| CommandDictionary base_dict; |
| CommandDictionary dict; |
| ErrorPtr error; |
| auto json = CreateDictionaryValue(R"({ |
| 'base': { |
| 'reboot': { |
| 'parameters': {'delay': 'integer'}, |
| 'results': {'version': 'integer'} |
| } |
| } |
| })"); |
| base_dict.LoadCommands(*json, nullptr, &error); |
| |
| auto json2 = CreateDictionaryValue(R"({ |
| 'base': { |
| 'reboot': { |
| 'parameters': {'delay': 'string'}, |
| 'results': {'version': 'integer'} |
| } |
| } |
| })"); |
| EXPECT_FALSE(dict.LoadCommands(*json2, &base_dict, &error)); |
| EXPECT_EQ("invalid_object_schema", error->GetCode()); |
| EXPECT_EQ("invalid_parameter_definition", error->GetInnerError()->GetCode()); |
| EXPECT_EQ("param_type_changed", error->GetFirstError()->GetCode()); |
| error.reset(); |
| |
| auto json3 = CreateDictionaryValue(R"({ |
| 'base': { |
| 'reboot': { |
| 'parameters': {'delay': 'integer'}, |
| 'results': {'version': 'string'} |
| } |
| } |
| })"); |
| EXPECT_FALSE(dict.LoadCommands(*json3, &base_dict, &error)); |
| EXPECT_EQ("invalid_object_schema", error->GetCode()); |
| // TODO(antonm): remove parameter from error below and use some generic. |
| EXPECT_EQ("invalid_parameter_definition", error->GetInnerError()->GetCode()); |
| EXPECT_EQ("param_type_changed", error->GetFirstError()->GetCode()); |
| error.reset(); |
| } |
| |
| TEST(CommandDictionary, GetCommandsAsJson) { |
| auto json_base = CreateDictionaryValue(R"({ |
| 'base': { |
| 'reboot': { |
| 'parameters': {'delay': {'maximum': 100}}, |
| 'results': {} |
| }, |
| 'shutdown': { |
| 'parameters': {}, |
| 'results': {} |
| } |
| } |
| })"); |
| CommandDictionary base_dict; |
| base_dict.LoadCommands(*json_base, nullptr, nullptr); |
| |
| auto json = CreateDictionaryValue(R"({ |
| 'base': { |
| 'reboot': { |
| 'parameters': {'delay': {'minimum': 10}}, |
| 'results': {} |
| } |
| }, |
| 'robot': { |
| '_jump': { |
| 'parameters': {'_height': 'integer'}, |
| 'results': {} |
| } |
| } |
| })"); |
| CommandDictionary dict; |
| dict.LoadCommands(*json, &base_dict, nullptr); |
| |
| json = dict.GetCommandsAsJson( |
| [](const CommandDefinition* def) { return true; }, false, nullptr); |
| ASSERT_NE(nullptr, json.get()); |
| auto expected = R"({ |
| 'base': { |
| 'reboot': { |
| 'parameters': {'delay': {'minimum': 10}}, |
| 'minimalRole': 'user' |
| } |
| }, |
| 'robot': { |
| '_jump': { |
| 'parameters': {'_height': 'integer'}, |
| 'minimalRole': 'user' |
| } |
| } |
| })"; |
| EXPECT_JSON_EQ(expected, *json); |
| |
| json = dict.GetCommandsAsJson( |
| [](const CommandDefinition* def) { return true; }, true, nullptr); |
| ASSERT_NE(nullptr, json.get()); |
| expected = R"({ |
| 'base': { |
| 'reboot': { |
| 'parameters': { |
| 'delay': { |
| 'maximum': 100, |
| 'minimum': 10, |
| 'type': 'integer' |
| } |
| }, |
| 'minimalRole': 'user' |
| } |
| }, |
| 'robot': { |
| '_jump': { |
| 'parameters': { |
| '_height': { |
| 'type': 'integer' |
| } |
| }, |
| 'minimalRole': 'user' |
| } |
| } |
| })"; |
| EXPECT_JSON_EQ(expected, *json); |
| } |
| |
| TEST(CommandDictionary, GetCommandsAsJsonWithVisibility) { |
| auto json = CreateDictionaryValue(R"({ |
| 'test': { |
| 'command1': { |
| 'parameters': {}, |
| 'results': {}, |
| 'visibility': 'none' |
| }, |
| 'command2': { |
| 'parameters': {}, |
| 'results': {}, |
| 'visibility': 'local' |
| }, |
| 'command3': { |
| 'parameters': {}, |
| 'results': {}, |
| 'visibility': 'cloud' |
| }, |
| 'command4': { |
| 'parameters': {}, |
| 'results': {}, |
| 'visibility': 'all' |
| }, |
| 'command5': { |
| 'parameters': {}, |
| 'results': {}, |
| 'visibility': 'none' |
| }, |
| 'command6': { |
| 'parameters': {}, |
| 'results': {}, |
| 'visibility': 'local' |
| }, |
| 'command7': { |
| 'parameters': {}, |
| 'results': {}, |
| 'visibility': 'cloud' |
| }, |
| 'command8': { |
| 'parameters': {}, |
| 'results': {}, |
| 'visibility': 'all' |
| } |
| } |
| })"); |
| CommandDictionary dict; |
| ASSERT_TRUE(dict.LoadCommands(*json, nullptr, nullptr)); |
| |
| json = dict.GetCommandsAsJson( |
| [](const CommandDefinition* def) { return true; }, false, nullptr); |
| ASSERT_NE(nullptr, json.get()); |
| auto expected = R"({ |
| 'test': { |
| 'command1': {'parameters': {}, 'minimalRole': 'user'}, |
| 'command2': {'parameters': {}, 'minimalRole': 'user'}, |
| 'command3': {'parameters': {}, 'minimalRole': 'user'}, |
| 'command4': {'parameters': {}, 'minimalRole': 'user'}, |
| 'command5': {'parameters': {}, 'minimalRole': 'user'}, |
| 'command6': {'parameters': {}, 'minimalRole': 'user'}, |
| 'command7': {'parameters': {}, 'minimalRole': 'user'}, |
| 'command8': {'parameters': {}, 'minimalRole': 'user'} |
| } |
| })"; |
| EXPECT_JSON_EQ(expected, *json); |
| |
| json = dict.GetCommandsAsJson( |
| [](const CommandDefinition* def) { return def->GetVisibility().local; }, |
| false, nullptr); |
| ASSERT_NE(nullptr, json.get()); |
| expected = R"({ |
| 'test': { |
| 'command2': {'parameters': {}, 'minimalRole': 'user'}, |
| 'command4': {'parameters': {}, 'minimalRole': 'user'}, |
| 'command6': {'parameters': {}, 'minimalRole': 'user'}, |
| 'command8': {'parameters': {}, 'minimalRole': 'user'} |
| } |
| })"; |
| EXPECT_JSON_EQ(expected, *json); |
| |
| json = dict.GetCommandsAsJson( |
| [](const CommandDefinition* def) { return def->GetVisibility().cloud; }, |
| false, nullptr); |
| ASSERT_NE(nullptr, json.get()); |
| expected = R"({ |
| 'test': { |
| 'command3': {'parameters': {}, 'minimalRole': 'user'}, |
| 'command4': {'parameters': {}, 'minimalRole': 'user'}, |
| 'command7': {'parameters': {}, 'minimalRole': 'user'}, |
| 'command8': {'parameters': {}, 'minimalRole': 'user'} |
| } |
| })"; |
| EXPECT_JSON_EQ(expected, *json); |
| |
| json = dict.GetCommandsAsJson( |
| [](const CommandDefinition* def) { |
| return def->GetVisibility().local && def->GetVisibility().cloud; |
| }, |
| false, nullptr); |
| ASSERT_NE(nullptr, json.get()); |
| expected = R"({ |
| 'test': { |
| 'command4': {'parameters': {}, 'minimalRole': 'user'}, |
| 'command8': {'parameters': {}, 'minimalRole': 'user'} |
| } |
| })"; |
| EXPECT_JSON_EQ(expected, *json); |
| } |
| |
| TEST(CommandDictionary, LoadWithPermissions) { |
| CommandDictionary base_dict; |
| auto json = CreateDictionaryValue(R"({ |
| 'base': { |
| 'command1': { |
| 'parameters': {}, |
| 'results': {}, |
| 'visibility':'none' |
| }, |
| 'command2': { |
| 'minimalRole': 'viewer', |
| 'parameters': {}, |
| 'results': {}, |
| 'visibility':'local' |
| }, |
| 'command3': { |
| 'minimalRole': 'user', |
| 'parameters': {}, |
| 'results': {}, |
| 'visibility':'cloud' |
| }, |
| 'command4': { |
| 'minimalRole': 'manager', |
| 'parameters': {}, |
| 'results': {}, |
| 'visibility':'all' |
| }, |
| 'command5': { |
| 'minimalRole': 'owner', |
| 'parameters': {}, |
| 'results': {}, |
| 'visibility':'local,cloud' |
| } |
| } |
| })"); |
| EXPECT_TRUE(base_dict.LoadCommands(*json, nullptr, nullptr)); |
| |
| auto cmd = base_dict.FindCommand("base.command1"); |
| ASSERT_NE(nullptr, cmd); |
| EXPECT_EQ("none", cmd->GetVisibility().ToString()); |
| EXPECT_EQ(UserRole::kUser, cmd->GetMinimalRole()); |
| |
| cmd = base_dict.FindCommand("base.command2"); |
| ASSERT_NE(nullptr, cmd); |
| EXPECT_EQ("local", cmd->GetVisibility().ToString()); |
| EXPECT_EQ(UserRole::kViewer, cmd->GetMinimalRole()); |
| |
| cmd = base_dict.FindCommand("base.command3"); |
| ASSERT_NE(nullptr, cmd); |
| EXPECT_EQ("cloud", cmd->GetVisibility().ToString()); |
| EXPECT_EQ(UserRole::kUser, cmd->GetMinimalRole()); |
| |
| cmd = base_dict.FindCommand("base.command4"); |
| ASSERT_NE(nullptr, cmd); |
| EXPECT_EQ("all", cmd->GetVisibility().ToString()); |
| EXPECT_EQ(UserRole::kManager, cmd->GetMinimalRole()); |
| |
| cmd = base_dict.FindCommand("base.command5"); |
| ASSERT_NE(nullptr, cmd); |
| EXPECT_EQ("all", cmd->GetVisibility().ToString()); |
| EXPECT_EQ(UserRole::kOwner, cmd->GetMinimalRole()); |
| |
| CommandDictionary dict; |
| json = CreateDictionaryValue(R"({ |
| 'base': { |
| 'command1': { |
| 'parameters': {}, |
| 'results': {} |
| }, |
| 'command2': { |
| 'parameters': {}, |
| 'results': {} |
| }, |
| 'command3': { |
| 'parameters': {}, |
| 'results': {} |
| }, |
| 'command4': { |
| 'parameters': {}, |
| 'results': {} |
| }, |
| 'command5': { |
| 'parameters': {}, |
| 'results': {} |
| }, |
| '_command6': { |
| 'parameters': {}, |
| 'results': {} |
| } |
| } |
| })"); |
| EXPECT_TRUE(dict.LoadCommands(*json, &base_dict, nullptr)); |
| |
| cmd = dict.FindCommand("base.command1"); |
| ASSERT_NE(nullptr, cmd); |
| EXPECT_EQ("none", cmd->GetVisibility().ToString()); |
| EXPECT_EQ(UserRole::kUser, cmd->GetMinimalRole()); |
| |
| cmd = dict.FindCommand("base.command2"); |
| ASSERT_NE(nullptr, cmd); |
| EXPECT_EQ("local", cmd->GetVisibility().ToString()); |
| EXPECT_EQ(UserRole::kViewer, cmd->GetMinimalRole()); |
| |
| cmd = dict.FindCommand("base.command3"); |
| ASSERT_NE(nullptr, cmd); |
| EXPECT_EQ("cloud", cmd->GetVisibility().ToString()); |
| EXPECT_EQ(UserRole::kUser, cmd->GetMinimalRole()); |
| |
| cmd = dict.FindCommand("base.command4"); |
| ASSERT_NE(nullptr, cmd); |
| EXPECT_EQ("all", cmd->GetVisibility().ToString()); |
| EXPECT_EQ(UserRole::kManager, cmd->GetMinimalRole()); |
| |
| cmd = dict.FindCommand("base.command5"); |
| ASSERT_NE(nullptr, cmd); |
| EXPECT_EQ("all", cmd->GetVisibility().ToString()); |
| EXPECT_EQ(UserRole::kOwner, cmd->GetMinimalRole()); |
| |
| cmd = dict.FindCommand("base._command6"); |
| ASSERT_NE(nullptr, cmd); |
| EXPECT_EQ("all", cmd->GetVisibility().ToString()); |
| EXPECT_EQ(UserRole::kUser, cmd->GetMinimalRole()); |
| } |
| |
| TEST(CommandDictionary, LoadWithPermissions_InvalidVisibility) { |
| CommandDictionary dict; |
| ErrorPtr error; |
| |
| auto json = CreateDictionaryValue(R"({ |
| 'base': { |
| 'jump': { |
| 'parameters': {}, |
| 'results': {}, |
| 'visibility':'foo' |
| } |
| } |
| })"); |
| EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error)); |
| EXPECT_EQ("invalid_command_visibility", error->GetCode()); |
| EXPECT_EQ("invalid_parameter_value", error->GetInnerError()->GetCode()); |
| error.reset(); |
| } |
| |
| TEST(CommandDictionary, LoadWithPermissions_InvalidRole) { |
| CommandDictionary dict; |
| ErrorPtr error; |
| |
| auto json = CreateDictionaryValue(R"({ |
| 'base': { |
| 'jump': { |
| 'parameters': {}, |
| 'results': {}, |
| 'visibility':'local,cloud', |
| 'minimalRole':'foo' |
| } |
| } |
| })"); |
| EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error)); |
| EXPECT_EQ("invalid_minimal_role", error->GetCode()); |
| EXPECT_EQ("invalid_parameter_value", error->GetInnerError()->GetCode()); |
| error.reset(); |
| } |
| |
| } // namespace weave |