blob: 0519d88088a5a8a7db773e8f97f463315877e38e [file] [log] [blame]
Alex Vakulenko7c36b672014-07-16 14:50:58 -07001// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Alex Deymof6cbe322014-11-10 19:55:35 -08005#include "buffet/commands/command_dictionary.h"
6
Alex Vakulenko7c36b672014-07-16 14:50:58 -07007#include <gtest/gtest.h>
8
Alex Vakulenko7c36b672014-07-16 14:50:58 -07009#include "buffet/commands/unittest_utils.h"
10
11using buffet::unittests::CreateDictionaryValue;
12
13TEST(CommandDictionary, Empty) {
14 buffet::CommandDictionary dict;
15 EXPECT_TRUE(dict.IsEmpty());
16 EXPECT_EQ(nullptr, dict.FindCommand("robot.jump"));
17 EXPECT_TRUE(dict.GetCommandNamesByCategory("robotd").empty());
18}
19
20TEST(CommandDictionary, LoadCommands) {
21 auto json = CreateDictionaryValue(R"({
22 'robot': {
23 'jump': {
24 'parameters': {
25 'height': 'integer',
26 '_jumpType': ['_withAirFlip', '_withSpin', '_withKick']
Anton Muhin71fb9d52014-11-21 22:22:39 +040027 },
28 'results': {}
Alex Vakulenko7c36b672014-07-16 14:50:58 -070029 }
30 }
31 })");
32 buffet::CommandDictionary dict;
Alex Vakulenkofd448692014-07-22 07:46:53 -070033 EXPECT_TRUE(dict.LoadCommands(*json, "robotd", nullptr, nullptr));
Alex Vakulenko7c36b672014-07-16 14:50:58 -070034 EXPECT_EQ(1, dict.GetSize());
35 EXPECT_NE(nullptr, dict.FindCommand("robot.jump"));
36 json = CreateDictionaryValue(R"({
37 'base': {
38 'reboot': {
Anton Muhin71fb9d52014-11-21 22:22:39 +040039 'parameters': {'delay': 'integer'},
40 'results': {}
Alex Vakulenko7c36b672014-07-16 14:50:58 -070041 },
42 'shutdown': {
Anton Muhin71fb9d52014-11-21 22:22:39 +040043 'parameters': {},
44 'results': {}
Alex Vakulenko7c36b672014-07-16 14:50:58 -070045 }
46 }
47 })");
Alex Vakulenkofd448692014-07-22 07:46:53 -070048 EXPECT_TRUE(dict.LoadCommands(*json, "powerd", nullptr, nullptr));
Alex Vakulenko7c36b672014-07-16 14:50:58 -070049 EXPECT_EQ(3, dict.GetSize());
50 EXPECT_NE(nullptr, dict.FindCommand("robot.jump"));
51 EXPECT_NE(nullptr, dict.FindCommand("base.reboot"));
52 EXPECT_NE(nullptr, dict.FindCommand("base.shutdown"));
53 EXPECT_EQ(nullptr, dict.FindCommand("foo.bar"));
54 std::vector<std::string> expected_commands{"base.reboot", "base.shutdown"};
55 EXPECT_EQ(expected_commands, dict.GetCommandNamesByCategory("powerd"));
56}
57
58TEST(CommandDictionary, LoadCommands_Failures) {
59 buffet::CommandDictionary dict;
Alex Vakulenko5f472062014-08-14 17:54:04 -070060 chromeos::ErrorPtr error;
Alex Vakulenko7c36b672014-07-16 14:50:58 -070061
62 // Command definition missing 'parameters' property.
Anton Muhin71fb9d52014-11-21 22:22:39 +040063 auto json = CreateDictionaryValue("{'robot':{'jump':{'results':{}}}}");
Alex Vakulenkofd448692014-07-22 07:46:53 -070064 EXPECT_FALSE(dict.LoadCommands(*json, "robotd", nullptr, &error));
Alex Vakulenko7c36b672014-07-16 14:50:58 -070065 EXPECT_EQ("parameter_missing", error->GetCode());
66 EXPECT_EQ("Command definition 'robot.jump' is missing property 'parameters'",
67 error->GetMessage());
68 error.reset();
69
Anton Muhin71fb9d52014-11-21 22:22:39 +040070 // Command definition missing 'results' property.
71 json = CreateDictionaryValue("{'robot':{'jump':{'parameters':{}}}}");
72 EXPECT_FALSE(dict.LoadCommands(*json, "robotd", nullptr, &error));
73 EXPECT_EQ("parameter_missing", error->GetCode());
74 EXPECT_EQ("Command definition 'robot.jump' is missing property 'results'",
75 error->GetMessage());
76 error.reset();
77
Alex Vakulenko7c36b672014-07-16 14:50:58 -070078 // Command definition is not an object.
79 json = CreateDictionaryValue("{'robot':{'jump':0}}");
Alex Vakulenkofd448692014-07-22 07:46:53 -070080 EXPECT_FALSE(dict.LoadCommands(*json, "robotd", nullptr, &error));
Alex Vakulenko7c36b672014-07-16 14:50:58 -070081 EXPECT_EQ("type_mismatch", error->GetCode());
82 EXPECT_EQ("Expecting an object for command 'jump'", error->GetMessage());
83 error.reset();
84
85 // Package definition is not an object.
86 json = CreateDictionaryValue("{'robot':'blah'}");
Alex Vakulenkofd448692014-07-22 07:46:53 -070087 EXPECT_FALSE(dict.LoadCommands(*json, "robotd", nullptr, &error));
Alex Vakulenko7c36b672014-07-16 14:50:58 -070088 EXPECT_EQ("type_mismatch", error->GetCode());
89 EXPECT_EQ("Expecting an object for package 'robot'", error->GetMessage());
90 error.reset();
91
92 // Invalid command definition is not an object.
Anton Muhin71fb9d52014-11-21 22:22:39 +040093 json = CreateDictionaryValue(
94 "{'robot':{'jump':{'parameters':{'flip':0},'results':{}}}}");
Alex Vakulenkofd448692014-07-22 07:46:53 -070095 EXPECT_FALSE(dict.LoadCommands(*json, "robotd", nullptr, &error));
Alex Vakulenko7c36b672014-07-16 14:50:58 -070096 EXPECT_EQ("invalid_object_schema", error->GetCode());
97 EXPECT_EQ("Invalid definition for command 'robot.jump'", error->GetMessage());
98 EXPECT_NE(nullptr, error->GetInnerError()); // Must have additional info.
99 error.reset();
100
Alex Vakulenkofd448692014-07-22 07:46:53 -0700101 // Empty command name.
Anton Muhin71fb9d52014-11-21 22:22:39 +0400102 json = CreateDictionaryValue("{'robot':{'':{'parameters':{},'results':{}}}}");
Alex Vakulenkofd448692014-07-22 07:46:53 -0700103 EXPECT_FALSE(dict.LoadCommands(*json, "robotd", nullptr, &error));
104 EXPECT_EQ("invalid_command_name", error->GetCode());
105 EXPECT_EQ("Unnamed command encountered in package 'robot'",
106 error->GetMessage());
107 error.reset();
108}
109
Christopher Wiley13fca9d2015-01-14 09:56:34 -0800110TEST(CommandDictionaryDeathTest, LoadCommands_RedefineInDifferentCategory) {
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700111 // Redefine commands in different category.
Alex Vakulenkofd448692014-07-22 07:46:53 -0700112 buffet::CommandDictionary dict;
Alex Vakulenko5f472062014-08-14 17:54:04 -0700113 chromeos::ErrorPtr error;
Anton Muhin71fb9d52014-11-21 22:22:39 +0400114 auto json = CreateDictionaryValue(
115 "{'robot':{'jump':{'parameters':{},'results':{}}}}");
Alex Vakulenkofd448692014-07-22 07:46:53 -0700116 dict.LoadCommands(*json, "category1", nullptr, &error);
Christopher Wiley13fca9d2015-01-14 09:56:34 -0800117 ASSERT_DEATH(dict.LoadCommands(*json, "category2", nullptr, &error),
118 ".*Definition for command 'robot.jump' overrides an "
119 "earlier definition in category 'category1'");
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700120}
Alex Vakulenkofd448692014-07-22 07:46:53 -0700121
122TEST(CommandDictionary, LoadCommands_CustomCommandNaming) {
123 // Custom command must start with '_'.
124 buffet::CommandDictionary base_dict;
125 buffet::CommandDictionary dict;
Alex Vakulenko5f472062014-08-14 17:54:04 -0700126 chromeos::ErrorPtr error;
Alex Vakulenkofd448692014-07-22 07:46:53 -0700127 auto json = CreateDictionaryValue(R"({
128 'base': {
129 'reboot': {
Anton Muhin71fb9d52014-11-21 22:22:39 +0400130 'parameters': {'delay': 'integer'},
131 'results': {}
Alex Vakulenkofd448692014-07-22 07:46:53 -0700132 }
133 }
134 })");
135 base_dict.LoadCommands(*json, "", nullptr, &error);
136 EXPECT_TRUE(dict.LoadCommands(*json, "robotd", &base_dict, &error));
Anton Muhin71fb9d52014-11-21 22:22:39 +0400137 auto json2 = CreateDictionaryValue(
138 "{'base':{'jump':{'parameters':{},'results':{}}}}");
Alex Vakulenkofd448692014-07-22 07:46:53 -0700139 EXPECT_FALSE(dict.LoadCommands(*json2, "robotd", &base_dict, &error));
140 EXPECT_EQ("invalid_command_name", error->GetCode());
141 EXPECT_EQ("The name of custom command 'jump' in package 'base' must start "
142 "with '_'", error->GetMessage());
143 error.reset();
144
145 // If the command starts with "_", then it's Ok.
Anton Muhin71fb9d52014-11-21 22:22:39 +0400146 json2 = CreateDictionaryValue(
147 "{'base':{'_jump':{'parameters':{},'results':{}}}}");
Alex Vakulenkofd448692014-07-22 07:46:53 -0700148 EXPECT_TRUE(dict.LoadCommands(*json2, "robotd", &base_dict, nullptr));
149}
150
151TEST(CommandDictionary, LoadCommands_RedefineStdCommand) {
152 // Redefine commands parameter type.
153 buffet::CommandDictionary base_dict;
154 buffet::CommandDictionary dict;
Alex Vakulenko5f472062014-08-14 17:54:04 -0700155 chromeos::ErrorPtr error;
Alex Vakulenkofd448692014-07-22 07:46:53 -0700156 auto json = CreateDictionaryValue(R"({
157 'base': {
158 'reboot': {
Anton Muhin71fb9d52014-11-21 22:22:39 +0400159 'parameters': {'delay': 'integer'},
160 'results': {'version': 'integer'}
Alex Vakulenkofd448692014-07-22 07:46:53 -0700161 }
162 }
163 })");
164 base_dict.LoadCommands(*json, "", nullptr, &error);
Anton Muhin71fb9d52014-11-21 22:22:39 +0400165
Alex Vakulenkofd448692014-07-22 07:46:53 -0700166 auto json2 = CreateDictionaryValue(R"({
167 'base': {
168 'reboot': {
Anton Muhin71fb9d52014-11-21 22:22:39 +0400169 'parameters': {'delay': 'string'},
170 'results': {'version': 'integer'}
Alex Vakulenkofd448692014-07-22 07:46:53 -0700171 }
172 }
173 })");
174 EXPECT_FALSE(dict.LoadCommands(*json2, "robotd", &base_dict, &error));
175 EXPECT_EQ("invalid_object_schema", error->GetCode());
176 EXPECT_EQ("Invalid definition for command 'base.reboot'",
177 error->GetMessage());
178 EXPECT_EQ("invalid_parameter_definition", error->GetInnerError()->GetCode());
179 EXPECT_EQ("Error in definition of property 'delay'",
180 error->GetInnerError()->GetMessage());
181 EXPECT_EQ("param_type_changed", error->GetFirstError()->GetCode());
182 EXPECT_EQ("Redefining a property of type integer as string",
183 error->GetFirstError()->GetMessage());
184 error.reset();
Anton Muhin71fb9d52014-11-21 22:22:39 +0400185
186 auto json3 = CreateDictionaryValue(R"({
187 'base': {
188 'reboot': {
189 'parameters': {'delay': 'integer'},
190 'results': {'version': 'string'}
191 }
192 }
193 })");
194 EXPECT_FALSE(dict.LoadCommands(*json3, "robotd", &base_dict, &error));
195 EXPECT_EQ("invalid_object_schema", error->GetCode());
196 EXPECT_EQ("Invalid definition for command 'base.reboot'",
197 error->GetMessage());
198 // TODO(antonm): remove parameter from error below and use some generic.
199 EXPECT_EQ("invalid_parameter_definition", error->GetInnerError()->GetCode());
200 EXPECT_EQ("Error in definition of property 'version'",
201 error->GetInnerError()->GetMessage());
202 EXPECT_EQ("param_type_changed", error->GetFirstError()->GetCode());
203 EXPECT_EQ("Redefining a property of type integer as string",
204 error->GetFirstError()->GetMessage());
205 error.reset();
Alex Vakulenkofd448692014-07-22 07:46:53 -0700206}
Alex Vakulenko45109442014-07-29 11:07:10 -0700207
208TEST(CommandDictionary, GetCommandsAsJson) {
209 auto json_base = CreateDictionaryValue(R"({
210 'base': {
211 'reboot': {
Anton Muhin71fb9d52014-11-21 22:22:39 +0400212 'parameters': {'delay': {'maximum': 100}},
213 'results': {}
Alex Vakulenko45109442014-07-29 11:07:10 -0700214 },
215 'shutdown': {
Anton Muhin71fb9d52014-11-21 22:22:39 +0400216 'parameters': {},
217 'results': {}
Alex Vakulenko45109442014-07-29 11:07:10 -0700218 }
219 }
220 })");
221 buffet::CommandDictionary base_dict;
222 base_dict.LoadCommands(*json_base, "base", nullptr, nullptr);
223
224 auto json = buffet::unittests::CreateDictionaryValue(R"({
225 'base': {
226 'reboot': {
Anton Muhin71fb9d52014-11-21 22:22:39 +0400227 'parameters': {'delay': {'minimum': 10}},
228 'results': {}
Alex Vakulenko45109442014-07-29 11:07:10 -0700229 }
230 },
231 'robot': {
232 '_jump': {
Anton Muhin71fb9d52014-11-21 22:22:39 +0400233 'parameters': {'_height': 'integer'},
234 'results': {}
Alex Vakulenko45109442014-07-29 11:07:10 -0700235 }
236 }
237 })");
238 buffet::CommandDictionary dict;
239 dict.LoadCommands(*json, "device", &base_dict, nullptr);
240
241 json = dict.GetCommandsAsJson(false, nullptr);
242 EXPECT_NE(nullptr, json.get());
243 EXPECT_EQ("{'base':{'reboot':{'parameters':{'delay':{'minimum':10}}}},"
244 "'robot':{'_jump':{'parameters':{'_height':'integer'}}}}",
245 buffet::unittests::ValueToString(json.get()));
246
247 json = dict.GetCommandsAsJson(true, nullptr);
248 EXPECT_NE(nullptr, json.get());
249 EXPECT_EQ("{'base':{'reboot':{'parameters':{'delay':{"
250 "'maximum':100,'minimum':10,'type':'integer'}}}},"
251 "'robot':{'_jump':{'parameters':{'_height':{'type':'integer'}}}}}",
252 buffet::unittests::ValueToString(json.get()));
253}