buffet: Add GCD device draft record to device registration

Use the command definitions provided by Command Manager in
device registration record sent to GCD server.

BUG=chromium:396716
TEST=USE=buffet P2_TEST_FILTER="buffet::*" FEATURES=test emerge-link platform2

Change-Id: If9109bfee8862d9252cdb03301a4362492064809
Reviewed-on: https://chromium-review.googlesource.com/210146
Reviewed-by: Christopher Wiley <wiley@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/commands/command_dictionary.cc b/buffet/commands/command_dictionary.cc
index dcba8c9..e011a23 100644
--- a/buffet/commands/command_dictionary.cc
+++ b/buffet/commands/command_dictionary.cc
@@ -145,6 +145,34 @@
   return true;
 }
 
+std::unique_ptr<base::DictionaryValue> CommandDictionary::GetCommandsAsJson(
+    bool full_schema, ErrorPtr* error) const {
+  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+  for (const auto& pair : definitions_) {
+    std::unique_ptr<base::DictionaryValue> definition =
+        pair.second->GetParameters()->ToJson(full_schema, error);
+    if (!definition) {
+      dict.reset();
+      return dict;
+    }
+    auto cmd_name_parts = string_utils::SplitAtFirst(pair.first, '.');
+    std::string package_name = cmd_name_parts.first;
+    std::string command_name = cmd_name_parts.second;
+    base::DictionaryValue* package = nullptr;
+    if (!dict->GetDictionaryWithoutPathExpansion(package_name, &package)) {
+      // If this is the first time we encounter this package, create a JSON
+      // object for it.
+      package = new base::DictionaryValue;
+      dict->SetWithoutPathExpansion(package_name, package);
+    }
+    base::DictionaryValue* command_def = new base::DictionaryValue;
+    command_def->SetWithoutPathExpansion(
+        commands::attributes::kCommand_Parameters, definition.release());
+    package->SetWithoutPathExpansion(command_name, command_def);
+  }
+  return dict;
+}
+
 const CommandDefinition* CommandDictionary::FindCommand(
     const std::string& command_name) const {
   auto pair = definitions_.find(command_name);
diff --git a/buffet/commands/command_dictionary.h b/buffet/commands/command_dictionary.h
index 78f2cee..b656684 100644
--- a/buffet/commands/command_dictionary.h
+++ b/buffet/commands/command_dictionary.h
@@ -54,6 +54,13 @@
   bool LoadCommands(const base::DictionaryValue& json,
                     const std::string& category,
                     const CommandDictionary* base_commands, ErrorPtr* error);
+  // Converts all the command definitions to a JSON object for CDD/Device
+  // draft. |full_schema| specifies whether full command definitions must
+  // be generated (true) for CDD or only overrides from the base schema (false).
+  // Returns empty unique_ptr in case of an error and fills in the additional
+  // error details in |error|.
+  std::unique_ptr<base::DictionaryValue> GetCommandsAsJson(
+      bool full_schema, ErrorPtr* error) const;
   // Returns the number of command definitions in the dictionary.
   size_t GetSize() const { return definitions_.size(); }
   // Checks if the dictionary has no command definitions.
diff --git a/buffet/commands/command_dictionary_unittest.cc b/buffet/commands/command_dictionary_unittest.cc
index 0aeab50..30d185f 100644
--- a/buffet/commands/command_dictionary_unittest.cc
+++ b/buffet/commands/command_dictionary_unittest.cc
@@ -165,3 +165,46 @@
             error->GetFirstError()->GetMessage());
   error.reset();
 }
+
+TEST(CommandDictionary, GetCommandsAsJson) {
+  auto json_base = CreateDictionaryValue(R"({
+    'base': {
+      'reboot': {
+        'parameters': {'delay': {'maximum': 100}}
+      },
+      'shutdown': {
+        'parameters': {}
+      }
+    }
+  })");
+  buffet::CommandDictionary base_dict;
+  base_dict.LoadCommands(*json_base, "base", nullptr, nullptr);
+
+  auto json = buffet::unittests::CreateDictionaryValue(R"({
+    'base': {
+      'reboot': {
+        'parameters': {'delay': {'minimum': 10}}
+      }
+    },
+    'robot': {
+      '_jump': {
+        'parameters': {'_height': 'integer'}
+      }
+    }
+  })");
+  buffet::CommandDictionary dict;
+  dict.LoadCommands(*json, "device", &base_dict, nullptr);
+
+  json = dict.GetCommandsAsJson(false, nullptr);
+  EXPECT_NE(nullptr, json.get());
+  EXPECT_EQ("{'base':{'reboot':{'parameters':{'delay':{'minimum':10}}}},"
+            "'robot':{'_jump':{'parameters':{'_height':'integer'}}}}",
+            buffet::unittests::ValueToString(json.get()));
+
+  json = dict.GetCommandsAsJson(true, nullptr);
+  EXPECT_NE(nullptr, json.get());
+  EXPECT_EQ("{'base':{'reboot':{'parameters':{'delay':{"
+            "'maximum':100,'minimum':10,'type':'integer'}}}},"
+            "'robot':{'_jump':{'parameters':{'_height':{'type':'integer'}}}}}",
+            buffet::unittests::ValueToString(json.get()));
+}
diff --git a/buffet/device_registration_info.cc b/buffet/device_registration_info.cc
index 0ec3de7..6c21429 100644
--- a/buffet/device_registration_info.cc
+++ b/buffet/device_registration_info.cc
@@ -11,6 +11,8 @@
 #include <base/json/json_writer.h>
 #include <base/values.h>
 
+#include "buffet/commands/command_definition.h"
+#include "buffet/commands/command_manager.h"
 #include "buffet/data_encoding.h"
 #include "buffet/device_registration_storage_keys.h"
 #include "buffet/http_transport_curl.h"
@@ -356,36 +358,18 @@
   if (!CheckParam(storage_keys::kServiceURL, service_url_, error))
     return std::string();
 
-  std::vector<std::pair<std::string, std::vector<std::string>>> commands = {
-    {"SetDeviceConfiguration", {"data"}}
-  };
+  std::unique_ptr<base::DictionaryValue> commands =
+      command_manager_->GetCommandDictionary().GetCommandsAsJson(true, error);
+  if (!commands)
+    return std::string();
 
   base::DictionaryValue req_json;
-  base::ListValue* set_device_configuration_params = new base::ListValue;
-  base::DictionaryValue* param1 = new base::DictionaryValue;
-  param1->SetString("name", "data");
-  set_device_configuration_params->Append(param1);
-
-  base::ListValue* vendor_commands = new base::ListValue;
-  for (const auto& pair : commands) {
-    base::ListValue* params = new base::ListValue;
-    for (const auto& param_name : pair.second) {
-      base::DictionaryValue* param = new base::DictionaryValue;
-      param->SetString("name", param_name);
-      params->Append(param);
-    }
-    base::DictionaryValue* command = new base::DictionaryValue;
-    command->SetString("name", pair.first);
-    command->Set("parameter", params);
-    vendor_commands->Append(command);
-  }
-
   req_json.SetString("oauthClientId", client_id_);
   req_json.SetString("deviceDraft.deviceKind", device_kind_);
   req_json.SetString("deviceDraft.systemName", system_name_);
   req_json.SetString("deviceDraft.displayName", display_name_);
   req_json.SetString("deviceDraft.channel.supportedType", "xmpp");
-  req_json.Set("deviceDraft.commands.base.vendorCommands", vendor_commands);
+  req_json.Set("deviceDraft.commandDefs", commands.release());
 
   std::string url = GetServiceURL("registrationTickets", {{"key", api_key_}});
   auto resp_json = http::ParseJsonResponse(
diff --git a/buffet/device_registration_info_unittest.cc b/buffet/device_registration_info_unittest.cc
index b8059cd..5f5d8ca 100644
--- a/buffet/device_registration_info_unittest.cc
+++ b/buffet/device_registration_info_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "buffet/bind_lambda.h"
 #include "buffet/commands/command_manager.h"
+#include "buffet/commands/unittest_utils.h"
 #include "buffet/device_registration_info.h"
 #include "buffet/device_registration_storage_keys.h"
 #include "buffet/http_request.h"
@@ -157,50 +158,51 @@
 class DeviceRegistrationInfoTest : public ::testing::Test {
  protected:
   virtual void SetUp() override {
-    InitDefaultStorage(&data);
-    storage = std::make_shared<MemStorage>();
-    storage->Save(&data);
-    transport = std::make_shared<fake::Transport>();
-    dev_reg = std::unique_ptr<DeviceRegistrationInfo>(
-        new DeviceRegistrationInfo(std::make_shared<CommandManager>(),
-                                   transport, storage));
+    InitDefaultStorage(&data_);
+    storage_ = std::make_shared<MemStorage>();
+    storage_->Save(&data_);
+    transport_ = std::make_shared<fake::Transport>();
+    command_manager_ = std::make_shared<CommandManager>();
+    dev_reg_ = std::unique_ptr<DeviceRegistrationInfo>(
+        new DeviceRegistrationInfo(command_manager_, transport_, storage_));
   }
 
-  base::DictionaryValue data;
-  std::shared_ptr<MemStorage> storage;
-  std::shared_ptr<fake::Transport> transport;
-  std::unique_ptr<DeviceRegistrationInfo> dev_reg;
+  base::DictionaryValue data_;
+  std::shared_ptr<MemStorage> storage_;
+  std::shared_ptr<fake::Transport> transport_;
+  std::unique_ptr<DeviceRegistrationInfo> dev_reg_;
+  std::shared_ptr<CommandManager> command_manager_;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 TEST_F(DeviceRegistrationInfoTest, GetServiceURL) {
-  EXPECT_TRUE(dev_reg->Load());
-  EXPECT_EQ(test_data::kServiceURL, dev_reg->GetServiceURL());
+  EXPECT_TRUE(dev_reg_->Load());
+  EXPECT_EQ(test_data::kServiceURL, dev_reg_->GetServiceURL());
   std::string url = test_data::kServiceURL;
   url += "registrationTickets";
-  EXPECT_EQ(url, dev_reg->GetServiceURL("registrationTickets"));
+  EXPECT_EQ(url, dev_reg_->GetServiceURL("registrationTickets"));
   url += "?key=";
   url += test_data::kApiKey;
-  EXPECT_EQ(url, dev_reg->GetServiceURL("registrationTickets", {
+  EXPECT_EQ(url, dev_reg_->GetServiceURL("registrationTickets", {
     {"key", test_data::kApiKey}
   }));
   url += "&restart=true";
-  EXPECT_EQ(url, dev_reg->GetServiceURL("registrationTickets", {
+  EXPECT_EQ(url, dev_reg_->GetServiceURL("registrationTickets", {
     {"key", test_data::kApiKey},
     {"restart", "true"},
   }));
 }
 
 TEST_F(DeviceRegistrationInfoTest, GetOAuthURL) {
-  EXPECT_TRUE(dev_reg->Load());
-  EXPECT_EQ(test_data::kOAuthURL, dev_reg->GetOAuthURL());
+  EXPECT_TRUE(dev_reg_->Load());
+  EXPECT_EQ(test_data::kOAuthURL, dev_reg_->GetOAuthURL());
   std::string url = test_data::kOAuthURL;
   url += "auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fclouddevices&";
   url += "redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&";
   url += "response_type=code&";
   url += "client_id=";
   url += test_data::kClientId;
-  EXPECT_EQ(url, dev_reg->GetOAuthURL("auth", {
+  EXPECT_EQ(url, dev_reg_->GetOAuthURL("auth", {
     {"scope", "https://www.googleapis.com/auth/clouddevices"},
     {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
     {"response_type", "code"},
@@ -209,33 +211,33 @@
 }
 
 TEST_F(DeviceRegistrationInfoTest, CheckRegistration) {
-  EXPECT_TRUE(dev_reg->Load());
-  EXPECT_FALSE(dev_reg->CheckRegistration(nullptr));
-  EXPECT_EQ(0, transport->GetRequestCount());
+  EXPECT_TRUE(dev_reg_->Load());
+  EXPECT_FALSE(dev_reg_->CheckRegistration(nullptr));
+  EXPECT_EQ(0, transport_->GetRequestCount());
 
-  SetDefaultDeviceRegistration(&data);
-  storage->Save(&data);
-  EXPECT_TRUE(dev_reg->Load());
+  SetDefaultDeviceRegistration(&data_);
+  storage_->Save(&data_);
+  EXPECT_TRUE(dev_reg_->Load());
 
-  transport->AddHandler(dev_reg->GetOAuthURL("token"), request_type::kPost,
-                        base::Bind(OAuth2Handler));
-  transport->ResetRequestCount();
-  EXPECT_TRUE(dev_reg->CheckRegistration(nullptr));
-  EXPECT_EQ(1, transport->GetRequestCount());
+  transport_->AddHandler(dev_reg_->GetOAuthURL("token"), request_type::kPost,
+                         base::Bind(OAuth2Handler));
+  transport_->ResetRequestCount();
+  EXPECT_TRUE(dev_reg_->CheckRegistration(nullptr));
+  EXPECT_EQ(1, transport_->GetRequestCount());
 }
 
 TEST_F(DeviceRegistrationInfoTest, GetDeviceInfo) {
-  SetDefaultDeviceRegistration(&data);
-  storage->Save(&data);
-  EXPECT_TRUE(dev_reg->Load());
+  SetDefaultDeviceRegistration(&data_);
+  storage_->Save(&data_);
+  EXPECT_TRUE(dev_reg_->Load());
 
-  transport->AddHandler(dev_reg->GetOAuthURL("token"), request_type::kPost,
-                        base::Bind(OAuth2Handler));
-  transport->AddHandler(dev_reg->GetDeviceURL(), request_type::kGet,
-                        base::Bind(DeviceInfoHandler));
-  transport->ResetRequestCount();
-  auto device_info = dev_reg->GetDeviceInfo(nullptr);
-  EXPECT_EQ(2, transport->GetRequestCount());
+  transport_->AddHandler(dev_reg_->GetOAuthURL("token"), request_type::kPost,
+                         base::Bind(OAuth2Handler));
+  transport_->AddHandler(dev_reg_->GetDeviceURL(), request_type::kGet,
+                         base::Bind(DeviceInfoHandler));
+  transport_->ResetRequestCount();
+  auto device_info = dev_reg_->GetDeviceInfo(nullptr);
+  EXPECT_EQ(2, transport_->GetRequestCount());
   EXPECT_NE(nullptr, device_info.get());
   base::DictionaryValue* dict = nullptr;
   EXPECT_TRUE(device_info->GetAsDictionary(&dict));
@@ -245,20 +247,20 @@
 }
 
 TEST_F(DeviceRegistrationInfoTest, GetDeviceId) {
-  SetDefaultDeviceRegistration(&data);
-  storage->Save(&data);
-  EXPECT_TRUE(dev_reg->Load());
+  SetDefaultDeviceRegistration(&data_);
+  storage_->Save(&data_);
+  EXPECT_TRUE(dev_reg_->Load());
 
-  transport->AddHandler(dev_reg->GetOAuthURL("token"), request_type::kPost,
-                        base::Bind(OAuth2Handler));
-  transport->AddHandler(dev_reg->GetDeviceURL(), request_type::kGet,
-                        base::Bind(DeviceInfoHandler));
-  std::string id = dev_reg->GetDeviceId(nullptr);
+  transport_->AddHandler(dev_reg_->GetOAuthURL("token"), request_type::kPost,
+                         base::Bind(OAuth2Handler));
+  transport_->AddHandler(dev_reg_->GetDeviceURL(), request_type::kGet,
+                         base::Bind(DeviceInfoHandler));
+  std::string id = dev_reg_->GetDeviceId(nullptr);
   EXPECT_EQ(test_data::kDeviceId, id);
 }
 
 TEST_F(DeviceRegistrationInfoTest, StartRegistration) {
-  EXPECT_TRUE(dev_reg->Load());
+  EXPECT_TRUE(dev_reg_->Load());
 
   auto create_ticket = [](const fake::ServerRequest& request,
                           fake::ServerResponse* response) {
@@ -272,6 +274,14 @@
     EXPECT_EQ(test_data::kClientId, value);
     EXPECT_TRUE(json->GetString("deviceDraft.deviceKind", &value));
     EXPECT_EQ("vendor", value);
+    base::DictionaryValue* commandDefs = nullptr;
+    EXPECT_TRUE(json->GetDictionary("deviceDraft.commandDefs", &commandDefs));
+    EXPECT_FALSE(commandDefs->empty());
+    EXPECT_EQ("{'base':{'reboot':{'parameters':{"
+              "'delay':{'minimum':10,'type':'integer'}}}},"
+              "'robot':{'_jump':{'parameters':{"
+              "'_height':{'type':'integer'}}}}}",
+              buffet::unittests::ValueToString(commandDefs));
 
     base::DictionaryValue json_resp;
     json_resp.SetString("id", test_data::kClaimTicketId);
@@ -287,11 +297,36 @@
     response->ReplyJson(status_code::Ok, &json_resp);
   };
 
-  transport->AddHandler(dev_reg->GetServiceURL("registrationTickets"),
-                        request_type::kPost,
-                        base::Bind(create_ticket));
+  auto json_base = buffet::unittests::CreateDictionaryValue(R"({
+    'base': {
+      'reboot': {
+        'parameters': {'delay': 'integer'}
+      },
+      'shutdown': {
+        'parameters': {}
+      }
+    }
+  })");
+  EXPECT_TRUE(command_manager_->LoadBaseCommands(*json_base, nullptr));
+  auto json_cmds = buffet::unittests::CreateDictionaryValue(R"({
+    'base': {
+      'reboot': {
+        'parameters': {'delay': {'minimum': 10}}
+      }
+    },
+    'robot': {
+      '_jump': {
+        'parameters': {'_height': 'integer'}
+      }
+    }
+  })");
+  EXPECT_TRUE(command_manager_->LoadCommands(*json_cmds, "", nullptr));
+
+  transport_->AddHandler(dev_reg_->GetServiceURL("registrationTickets"),
+                         request_type::kPost,
+                         base::Bind(create_ticket));
   std::map<std::string, std::shared_ptr<base::Value>> params;
-  std::string json_resp = dev_reg->StartRegistration(params, nullptr);
+  std::string json_resp = dev_reg_->StartRegistration(params, nullptr);
   auto json = std::unique_ptr<base::Value>(base::JSONReader::Read(json_resp));
   EXPECT_NE(nullptr, json.get());
   base::DictionaryValue* dict = nullptr;
@@ -304,26 +339,27 @@
 TEST_F(DeviceRegistrationInfoTest, FinishRegistration_NoAuth) {
   // Test finalizing ticket with no user authorization token.
   // This assumes that a client would patch in their email separately.
-  EXPECT_TRUE(dev_reg->Load());
+  EXPECT_TRUE(dev_reg_->Load());
 
   // General ticket finalization handler.
   std::string ticket_url =
-      dev_reg->GetServiceURL("registrationTickets/" +
+      dev_reg_->GetServiceURL("registrationTickets/" +
                              std::string(test_data::kClaimTicketId));
-  transport->AddHandler(ticket_url + "/finalize", request_type::kPost,
-                        base::Bind(FinalizeTicketHandler));
+  transport_->AddHandler(ticket_url + "/finalize", request_type::kPost,
+                         base::Bind(FinalizeTicketHandler));
 
-  transport->AddHandler(dev_reg->GetOAuthURL("token"), request_type::kPost,
-                        base::Bind(OAuth2Handler));
+  transport_->AddHandler(dev_reg_->GetOAuthURL("token"), request_type::kPost,
+                         base::Bind(OAuth2Handler));
 
-  storage->reset_save_count();
-  DeviceRegistrationInfo::TestHelper::SetTestTicketId(dev_reg.get());
-  EXPECT_TRUE(dev_reg->FinishRegistration("", nullptr));
-  EXPECT_EQ(1, storage->save_count());  // The device info must have been saved.
-  EXPECT_EQ(2, transport->GetRequestCount());
+  storage_->reset_save_count();
+  DeviceRegistrationInfo::TestHelper::SetTestTicketId(dev_reg_.get());
+  EXPECT_TRUE(dev_reg_->FinishRegistration("", nullptr));
+  EXPECT_EQ(1,
+            storage_->save_count());  // The device info must have been saved.
+  EXPECT_EQ(2, transport_->GetRequestCount());
 
   // Validate the device info saved to storage...
-  auto storage_data = storage->Load();
+  auto storage_data = storage_->Load();
   base::DictionaryValue* dict = nullptr;
   EXPECT_TRUE(storage_data->GetAsDictionary(&dict));
   std::string value;
@@ -347,17 +383,17 @@
 
 TEST_F(DeviceRegistrationInfoTest, FinishRegistration_Auth) {
   // Test finalizing ticket with user authorization token.
-  EXPECT_TRUE(dev_reg->Load());
+  EXPECT_TRUE(dev_reg_->Load());
 
   // General ticket finalization handler.
   std::string ticket_url =
-      dev_reg->GetServiceURL("registrationTickets/" +
+      dev_reg_->GetServiceURL("registrationTickets/" +
                              std::string(test_data::kClaimTicketId));
-  transport->AddHandler(ticket_url + "/finalize", request_type::kPost,
-                        base::Bind(FinalizeTicketHandler));
+  transport_->AddHandler(ticket_url + "/finalize", request_type::kPost,
+                         base::Bind(FinalizeTicketHandler));
 
-  transport->AddHandler(dev_reg->GetOAuthURL("token"), request_type::kPost,
-                        base::Bind(OAuth2Handler));
+  transport_->AddHandler(dev_reg_->GetOAuthURL("token"), request_type::kPost,
+                         base::Bind(OAuth2Handler));
 
   // Handle patching in the user email onto the device record.
   auto email_patch_handler = [](const fake::ServerRequest& request,
@@ -382,13 +418,14 @@
       {"deviceDraft.channel.supportedType", "xmpp"},
     });
   };
-  transport->AddHandler(ticket_url, request_type::kPatch,
-                        base::Bind(email_patch_handler));
+  transport_->AddHandler(ticket_url, request_type::kPatch,
+                         base::Bind(email_patch_handler));
 
-  storage->reset_save_count();
-  DeviceRegistrationInfo::TestHelper::SetTestTicketId(dev_reg.get());
-  EXPECT_TRUE(dev_reg->FinishRegistration(test_data::kUserAccountAuthCode,
+  storage_->reset_save_count();
+  DeviceRegistrationInfo::TestHelper::SetTestTicketId(dev_reg_.get());
+  EXPECT_TRUE(dev_reg_->FinishRegistration(test_data::kUserAccountAuthCode,
                                           nullptr));
-  EXPECT_EQ(1, storage->save_count());  // The device info must have been saved.
-  EXPECT_EQ(4, transport->GetRequestCount());
+  EXPECT_EQ(1,
+            storage_->save_count());  // The device info must have been saved.
+  EXPECT_EQ(4, transport_->GetRequestCount());
 }