buffet: Added minimal role into command definition

Possible values "viewer", "user", "owner", "manager".
Default minimal role is set to "user".
Role check is not yet implemented.

BUG=brillo:808
TEST=`FEATURES=test emerge-gizmo buffet`

Change-Id: I74d16c24618b50de1e7a2ffa37d7aef36978a38a
Reviewed-on: https://chromium-review.googlesource.com/274117
Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
Commit-Queue: Vitaly Buka <vitalybuka@chromium.org>
Tested-by: Vitaly Buka <vitalybuka@chromium.org>
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp
index 8bcc7d8..b91bdef 100644
--- a/buffet/buffet.gyp
+++ b/buffet/buffet.gyp
@@ -35,6 +35,7 @@
         'commands/prop_values.cc',
         'commands/schema_constants.cc',
         'commands/schema_utils.cc',
+        'commands/user_role.cc',
         'device_registration_info.cc',
         'dbus_bindings/org.chromium.Buffet.Command.xml',
         'dbus_bindings/org.chromium.Buffet.Manager.xml',
diff --git a/buffet/commands/command_definition.h b/buffet/commands/command_definition.h
index a5e2916..ea8e5e6 100644
--- a/buffet/commands/command_definition.h
+++ b/buffet/commands/command_definition.h
@@ -11,6 +11,7 @@
 #include <base/macros.h>
 
 #include "buffet/commands/object_schema.h"
+#include "buffet/commands/user_role.h"
 
 namespace buffet {
 
@@ -61,6 +62,10 @@
   const Visibility& GetVisibility() const { return visibility_; }
   // Changes the command visibility.
   void SetVisibility(const Visibility& visibility);
+  // Returns the role required to execute command.
+  UserRole GetMinimalRole() const { return minimal_role_; }
+  // Changes the role required to execute command.
+  void SetMinimalRole(UserRole minimal_role) { minimal_role_ = minimal_role; }
 
  private:
   std::string category_;  // Cmd category. Could be "powerd" for "base.reboot".
@@ -68,6 +73,8 @@
   std::unique_ptr<const ObjectSchema> progress_;    // Command progress def.
   std::unique_ptr<const ObjectSchema> results_;  // Command results def.
   Visibility visibility_;  // Available to all by default.
+  // Minimal role required to execute command.
+  UserRole minimal_role_{UserRole::kUser};
 
   DISALLOW_COPY_AND_ASSIGN(CommandDefinition);
 };
diff --git a/buffet/commands/command_dictionary.cc b/buffet/commands/command_dictionary.cc
index 0fdae65..4f66c7d 100644
--- a/buffet/commands/command_dictionary.cc
+++ b/buffet/commands/command_dictionary.cc
@@ -72,6 +72,7 @@
       const ObjectSchema* base_results_def = nullptr;
       // By default make it available to all clients.
       auto visibility = CommandDefinition::Visibility::GetAll();
+      UserRole minimal_role{UserRole::kUser};
       if (base_commands) {
         auto cmd = base_commands->FindCommand(full_command_name);
         if (cmd) {
@@ -79,6 +80,7 @@
           base_progress_def = cmd->GetProgress();
           base_results_def = cmd->GetResults();
           visibility = cmd->GetVisibility();
+          minimal_role = cmd->GetMinimalRole();
         }
 
         // If the base command dictionary was provided but the command was not
@@ -123,8 +125,8 @@
         return false;
 
       std::string value;
-      using commands::attributes::kCommand_Visibility;
-      if (command_def_json->GetString(kCommand_Visibility, &value)) {
+      if (command_def_json->GetString(commands::attributes::kCommand_Visibility,
+                                      &value)) {
         if (!visibility.FromString(value, error)) {
           chromeos::Error::AddToPrintf(
               error, FROM_HERE, errors::commands::kDomain,
@@ -134,12 +136,24 @@
         }
       }
 
+      if (command_def_json->GetString(commands::attributes::kCommand_Role,
+                                      &value)) {
+        if (!FromString(value, &minimal_role, error)) {
+          chromeos::Error::AddToPrintf(
+              error, FROM_HERE, errors::commands::kDomain,
+              errors::commands::kInvalidMinimalRole,
+              "Error parsing command '%s'", full_command_name.c_str());
+          return false;
+        }
+      }
+
       std::unique_ptr<CommandDefinition> command_def{
           new CommandDefinition{category,
                                 std::move(parameters_schema),
                                 std::move(progress_schema),
                                 std::move(results_schema)}};
       command_def->SetVisibility(visibility);
+      command_def->SetMinimalRole(minimal_role);
       new_defs.emplace(full_command_name, std::move(command_def));
 
       command_iter.Advance();
@@ -227,6 +241,8 @@
     base::DictionaryValue* command_def = new base::DictionaryValue;
     command_def->Set(commands::attributes::kCommand_Parameters,
                      parameters.release());
+    command_def->SetString(commands::attributes::kCommand_Role,
+                           ToString(pair.second->GetMinimalRole()));
     package->SetWithoutPathExpansion(command_name, command_def);
   }
   return dict;
diff --git a/buffet/commands/command_dictionary_unittest.cc b/buffet/commands/command_dictionary_unittest.cc
index e31f8cc..aeae236 100644
--- a/buffet/commands/command_dictionary_unittest.cc
+++ b/buffet/commands/command_dictionary_unittest.cc
@@ -230,12 +230,14 @@
   auto expected = R"({
     'base': {
       'reboot': {
-        'parameters': {'delay': {'minimum': 10}}
+        'parameters': {'delay': {'minimum': 10}},
+        'minimalRole': 'user'
       }
     },
     'robot': {
       '_jump': {
-        'parameters': {'_height': 'integer'}
+        'parameters': {'_height': 'integer'},
+        'minimalRole': 'user'
       }
     }
   })";
@@ -253,7 +255,8 @@
             'minimum': 10,
             'type': 'integer'
           }
-        }
+        },
+        'minimalRole': 'user'
       }
     },
     'robot': {
@@ -262,7 +265,8 @@
           '_height': {
            'type': 'integer'
           }
-        }
+        },
+        'minimalRole': 'user'
       }
     }
   })";
@@ -322,14 +326,14 @@
   ASSERT_NE(nullptr, json.get());
   auto expected = R"({
     'test': {
-      'command1': {'parameters': {}},
-      'command2': {'parameters': {}},
-      'command3': {'parameters': {}},
-      'command4': {'parameters': {}},
-      'command5': {'parameters': {}},
-      'command6': {'parameters': {}},
-      'command7': {'parameters': {}},
-      'command8': {'parameters': {}}
+      '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);
@@ -340,10 +344,10 @@
   ASSERT_NE(nullptr, json.get());
   expected = R"({
     'test': {
-      'command2': {'parameters': {}},
-      'command4': {'parameters': {}},
-      'command6': {'parameters': {}},
-      'command8': {'parameters': {}}
+      'command2': {'parameters': {}, 'minimalRole': 'user'},
+      'command4': {'parameters': {}, 'minimalRole': 'user'},
+      'command6': {'parameters': {}, 'minimalRole': 'user'},
+      'command8': {'parameters': {}, 'minimalRole': 'user'}
     }
   })";
   EXPECT_JSON_EQ(expected, *json);
@@ -354,10 +358,10 @@
   ASSERT_NE(nullptr, json.get());
   expected = R"({
     'test': {
-      'command3': {'parameters': {}},
-      'command4': {'parameters': {}},
-      'command7': {'parameters': {}},
-      'command8': {'parameters': {}}
+      'command3': {'parameters': {}, 'minimalRole': 'user'},
+      'command4': {'parameters': {}, 'minimalRole': 'user'},
+      'command7': {'parameters': {}, 'minimalRole': 'user'},
+      'command8': {'parameters': {}, 'minimalRole': 'user'}
     }
   })";
   EXPECT_JSON_EQ(expected, *json);
@@ -369,67 +373,14 @@
   ASSERT_NE(nullptr, json.get());
   expected = R"({
     'test': {
-      'command4': {'parameters': {}},
-      'command8': {'parameters': {}}
+      'command4': {'parameters': {}, 'minimalRole': 'user'},
+      'command8': {'parameters': {}, 'minimalRole': 'user'}
     }
   })";
   EXPECT_JSON_EQ(expected, *json);
 }
 
-TEST(CommandDictionary, LoadCommandsWithVisibility) {
-  CommandDictionary dict;
-  auto json = CreateDictionaryValue(R"({
-    'base': {
-      'command1': {
-        'parameters': {},
-        'results': {},
-        'visibility':'none'
-      },
-      'command2': {
-        'parameters': {},
-        'results': {},
-        'visibility':'local'
-      },
-      'command3': {
-        'parameters': {},
-        'results': {},
-        'visibility':'cloud'
-      },
-      'command4': {
-        'parameters': {},
-        'results': {},
-        'visibility':'all'
-      },
-      'command5': {
-        'parameters': {},
-        'results': {},
-        'visibility':'cloud,local'
-      }
-    }
-  })");
-  EXPECT_TRUE(dict.LoadCommands(*json, "testd", nullptr, nullptr));
-  auto cmd = dict.FindCommand("base.command1");
-  ASSERT_NE(nullptr, cmd);
-  EXPECT_EQ("none", cmd->GetVisibility().ToString());
-
-  cmd = dict.FindCommand("base.command2");
-  ASSERT_NE(nullptr, cmd);
-  EXPECT_EQ("local", cmd->GetVisibility().ToString());
-
-  cmd = dict.FindCommand("base.command3");
-  ASSERT_NE(nullptr, cmd);
-  EXPECT_EQ("cloud", cmd->GetVisibility().ToString());
-
-  cmd = dict.FindCommand("base.command4");
-  ASSERT_NE(nullptr, cmd);
-  EXPECT_EQ("all", cmd->GetVisibility().ToString());
-
-  cmd = dict.FindCommand("base.command5");
-  ASSERT_NE(nullptr, cmd);
-  EXPECT_EQ("all", cmd->GetVisibility().ToString());
-}
-
-TEST(CommandDictionary, LoadCommandsWithVisibility_Inheritance) {
+TEST(CommandDictionary, LoadWithPermissions) {
   CommandDictionary base_dict;
   auto json = CreateDictionaryValue(R"({
     'base': {
@@ -439,21 +390,25 @@
         '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'
@@ -462,6 +417,31 @@
   })");
   EXPECT_TRUE(base_dict.LoadCommands(*json, "testd", 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': {
@@ -493,32 +473,38 @@
   })");
   EXPECT_TRUE(dict.LoadCommands(*json, "testd", &base_dict, nullptr));
 
-  auto cmd = dict.FindCommand("base.command1");
+  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, LoadCommandsWithVisibility_Failures) {
+TEST(CommandDictionary, LoadWithPermissions_InvalidVisibility) {
   CommandDictionary dict;
   chromeos::ErrorPtr error;
 
@@ -533,10 +519,27 @@
   })");
   EXPECT_FALSE(dict.LoadCommands(*json, "testd", nullptr, &error));
   EXPECT_EQ("invalid_command_visibility", error->GetCode());
-  EXPECT_EQ("Error parsing command 'base.jump'", error->GetMessage());
   EXPECT_EQ("invalid_parameter_value", error->GetInnerError()->GetCode());
-  EXPECT_EQ("Invalid command visibility value 'foo'",
-            error->GetInnerError()->GetMessage());
+  error.reset();
+}
+
+TEST(CommandDictionary, LoadWithPermissions_InvalidRole) {
+  CommandDictionary dict;
+  chromeos::ErrorPtr error;
+
+  auto json = CreateDictionaryValue(R"({
+    'base': {
+      'jump': {
+        'parameters': {},
+        'results': {},
+        'visibility':'local,cloud',
+        'minimalRole':'foo'
+      }
+    }
+  })");
+  EXPECT_FALSE(dict.LoadCommands(*json, "testd", nullptr, &error));
+  EXPECT_EQ("invalid_minimal_role", error->GetCode());
+  EXPECT_EQ("invalid_parameter_value", error->GetInnerError()->GetCode());
   error.reset();
 }
 
diff --git a/buffet/commands/enum_to_string.h b/buffet/commands/enum_to_string.h
new file mode 100644
index 0000000..031c164
--- /dev/null
+++ b/buffet/commands/enum_to_string.h
@@ -0,0 +1,69 @@
+// Copyright 2015 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.
+
+#ifndef BUFFET_COMMANDS_ENUM_TO_STRING_H_
+#define BUFFET_COMMANDS_ENUM_TO_STRING_H_
+
+#include <string>
+
+namespace buffet {
+
+// Helps to map enumeration to stings and back.
+//
+// Usage example:
+// .h file:
+// enum class MyEnum { kV1, kV2 };
+// std::string ToString(MyEnum id);
+// bool FromString(const std::string& str, MyEnum* id);
+//
+// .cc file:
+// template <>
+// const EnumToString<MyEnum>::Map EnumToString<MyEnum>::kMap[] = {
+//     {MyEnum::kV1, "v1"},
+//     {MyEnum::kV2, "v2"},
+// };
+//
+// std::string ToString(MyEnum id) {
+//   return EnumToString<MyEnum>::FindNameById(id);
+// }
+//
+// bool FromString(const std::string& str, MyEnum* id) {
+//   return EnumToString<MyEnum>::FindIdByName(str, id));
+// }
+template <typename T>
+class EnumToString final {
+ public:
+  static std::string FindNameById(T id) {
+    for (const Map& m : kMap) {
+      if (m.id == id) {
+        CHECK(m.name);
+        return m.name;
+      }
+    }
+    NOTREACHED();
+    return std::string();
+  }
+
+  static bool FindIdByName(const std::string& name, T* id) {
+    for (const Map& m : kMap) {
+      if (m.name && m.name == name) {
+        *id = m.id;
+        return true;
+      }
+    }
+    return false;
+  }
+
+ private:
+  struct Map {
+    const T id;
+    const char* const name;
+  };
+
+  static const Map kMap[];
+};
+
+}  // namespace buffet
+
+#endif  // BUFFET_COMMANDS_ENUM_TO_STRING_H_
diff --git a/buffet/commands/schema_constants.cc b/buffet/commands/schema_constants.cc
index eec6177..7af3a89 100644
--- a/buffet/commands/schema_constants.cc
+++ b/buffet/commands/schema_constants.cc
@@ -24,11 +24,13 @@
 const char kInvalidCommandName[] = "invalid_command_name";
 const char kCommandFailed[] = "command_failed";
 const char kInvalidCommandVisibility[] = "invalid_command_visibility";
+const char kInvalidMinimalRole[] = "invalid_minimal_role";
 }  // namespace commands
 }  // namespace errors
 
 namespace commands {
 namespace attributes {
+
 const char kType[] = "type";
 const char kDisplayName[] = "displayName";
 const char kDefault[] = "default";
@@ -52,6 +54,13 @@
 const char kCommand_Progress[] = "progress";
 const char kCommand_Results[] = "results";
 const char kCommand_State[] = "state";
+
+const char kCommand_Role[] = "minimalRole";
+const char kCommand_Role_Manager[] = "manager";
+const char kCommand_Role_Owner[] = "owner";
+const char kCommand_Role_User[] = "user";
+const char kCommand_Role_Viewer[] = "viewer";
+
 const char kCommand_ErrorCode[] = "error.code";
 const char kCommand_ErrorMessage[] = "error.message";
 
@@ -60,6 +69,7 @@
 const char kCommand_Visibility_Local[] = "local";
 const char kCommand_Visibility_Cloud[] = "cloud";
 const char kCommand_Visibility_All[] = "all";
+
 }  // namespace attributes
 }  // namespace commands
 
diff --git a/buffet/commands/schema_constants.h b/buffet/commands/schema_constants.h
index 2bc371d..f9690fc 100644
--- a/buffet/commands/schema_constants.h
+++ b/buffet/commands/schema_constants.h
@@ -27,6 +27,7 @@
 extern const char kInvalidCommandName[];
 extern const char kCommandFailed[];
 extern const char kInvalidCommandVisibility[];
+extern const char kInvalidMinimalRole[];
 }  // namespace commands
 }  // namespace errors
 
@@ -56,6 +57,13 @@
 extern const char kCommand_Progress[];
 extern const char kCommand_Results[];
 extern const char kCommand_State[];
+
+extern const char kCommand_Role[];
+extern const char kCommand_Role_Manager[];
+extern const char kCommand_Role_Owner[];
+extern const char kCommand_Role_User[];
+extern const char kCommand_Role_Viewer[];
+
 extern const char kCommand_ErrorCode[];
 extern const char kCommand_ErrorMessage[];
 
@@ -64,6 +72,7 @@
 extern const char kCommand_Visibility_Local[];
 extern const char kCommand_Visibility_Cloud[];
 extern const char kCommand_Visibility_All[];
+
 }  // namespace attributes
 }  // namespace commands
 
diff --git a/buffet/commands/user_role.cc b/buffet/commands/user_role.cc
new file mode 100644
index 0000000..a9237ee
--- /dev/null
+++ b/buffet/commands/user_role.cc
@@ -0,0 +1,37 @@
+// 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 "buffet/commands/user_role.h"
+
+#include <chromeos/errors/error.h>
+
+#include "buffet/commands/enum_to_string.h"
+#include "buffet/commands/schema_constants.h"
+
+namespace buffet {
+
+template <>
+const EnumToString<UserRole>::Map EnumToString<UserRole>::kMap[] = {
+    {UserRole::kViewer, commands::attributes::kCommand_Role_Viewer},
+    {UserRole::kUser, commands::attributes::kCommand_Role_User},
+    {UserRole::kOwner, commands::attributes::kCommand_Role_Owner},
+    {UserRole::kManager, commands::attributes::kCommand_Role_Manager},
+};
+
+std::string ToString(UserRole role) {
+  return EnumToString<UserRole>::FindNameById(role);
+}
+
+bool FromString(const std::string& str,
+                UserRole* role,
+                chromeos::ErrorPtr* error) {
+  if (EnumToString<UserRole>::FindIdByName(str, role))
+    return true;
+  chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                               errors::commands::kInvalidPropValue,
+                               "Invalid role: '%s'", str.c_str());
+  return false;
+}
+
+}  // namespace buffet
diff --git a/buffet/commands/user_role.h b/buffet/commands/user_role.h
new file mode 100644
index 0000000..711abdf
--- /dev/null
+++ b/buffet/commands/user_role.h
@@ -0,0 +1,28 @@
+// Copyright 2015 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.
+
+#ifndef BUFFET_COMMANDS_USER_ROLE_H_
+#define BUFFET_COMMANDS_USER_ROLE_H_
+
+#include <string>
+#include <chromeos/errors/error.h>
+
+namespace buffet {
+
+enum class UserRole {
+  kViewer,
+  kUser,
+  kManager,
+  kOwner,
+};
+
+std::string ToString(UserRole role);
+
+bool FromString(const std::string& str,
+                UserRole* role,
+                chromeos::ErrorPtr* error);
+
+}  // namespace buffet
+
+#endif  // BUFFET_COMMANDS_USER_ROLE_H_
diff --git a/buffet/device_registration_info_unittest.cc b/buffet/device_registration_info_unittest.cc
index c3088c4..c791355 100644
--- a/buffet/device_registration_info_unittest.cc
+++ b/buffet/device_registration_info_unittest.cc
@@ -373,7 +373,8 @@
               'minimum': 10,
               'type': 'integer'
             }
-          }
+          },
+          'minimalRole': 'user'
         }
       },
       'robot': {
@@ -382,7 +383,8 @@
             '_height': {
               'type': 'integer'
             }
-          }
+          },
+          'minimalRole': 'user'
         }
       }
     })";
@@ -406,10 +408,12 @@
     'base': {
       'reboot': {
         'parameters': {'delay': 'integer'},
+        'minimalRole': 'user',
         'results': {}
       },
       'shutdown': {
         'parameters': {},
+        'minimalRole': 'user',
         'results': {}
       }
     }
@@ -419,12 +423,14 @@
     'base': {
       'reboot': {
         'parameters': {'delay': {'minimum': 10}},
+        'minimalRole': 'user',
         'results': {}
       }
     },
     'robot': {
       '_jump': {
         'parameters': {'_height': 'integer'},
+        'minimalRole': 'user',
         'results': {}
       }
     }
@@ -481,7 +487,8 @@
     'robot': {
       '_jump': {
         'parameters': {'_height': 'integer'},
-        'results': {'status': 'string'}
+        'results': {'status': 'string'},
+        'minimalRole': 'user'
       }
     }
   })");
@@ -492,7 +499,8 @@
   auto commands_json = unittests::CreateValue(R"([{
     'name':'robot._jump',
     'id':'1234',
-    'parameters': {'_height': 100}
+    'parameters': {'_height': 100},
+    'minimalRole': 'user'
   }])");
   ASSERT_NE(nullptr, commands_json.get());
   const base::ListValue* command_list = nullptr;