buffet: Added base.updateDeviceInfo handler

base.updateDeviceInfo changes 'name', 'description' and 'location'
properties of the device resource.

BUG=brillo:697
TEST='FEATURES=test emerge-storm buffet'

Change-Id: Id1b349c97d2132117cdf803dda8e39d25c64621a
Reviewed-on: https://chromium-review.googlesource.com/270787
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/base_api_handler.cc b/buffet/base_api_handler.cc
new file mode 100644
index 0000000..ce08bd1
--- /dev/null
+++ b/buffet/base_api_handler.cc
@@ -0,0 +1,75 @@
+// 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.
+
+#include "buffet/base_api_handler.h"
+
+#include "buffet/commands/command_instance.h"
+#include "buffet/commands/command_manager.h"
+#include "buffet/device_registration_info.h"
+
+namespace buffet {
+
+namespace {
+
+// Helps to get parameters from native_types::Object representing
+// CommandInstance parameters.
+class ParametersReader {
+ public:
+  explicit ParametersReader(const native_types::Object* parameters)
+      : parameters_{parameters} {}
+
+  bool GetParameter(const std::string& name, std::string* value) const {
+    auto it = parameters_->find(name);
+    if (it == parameters_->end())
+      return false;
+    const StringValue* string_value = it->second->GetString();
+    if (!string_value)
+      return false;
+    *value = string_value->GetValue();
+    return true;
+  }
+
+ private:
+  const native_types::Object* parameters_;
+};
+
+}  // namespace
+
+BaseApiHandler::BaseApiHandler(
+    const base::WeakPtr<DeviceRegistrationInfo>& device_info,
+    const std::shared_ptr<CommandManager>& command_manager)
+    : device_info_{device_info} {
+  command_manager->AddOnCommandAddedCallback(base::Bind(
+      &BaseApiHandler::OnCommandAdded, weak_ptr_factory_.GetWeakPtr()));
+}
+
+void BaseApiHandler::OnCommandAdded(CommandInstance* command) {
+  if (command->GetStatus() != CommandInstance::kStatusQueued)
+    return;
+
+  if (command->GetName() == "base.updateDeviceInfo")
+    return UpdateDeviceInfo(command);
+}
+
+void BaseApiHandler::UpdateDeviceInfo(CommandInstance* command) {
+  command->SetProgress({});
+
+  const BuffetConfig& config{device_info_->GetConfig()};
+  std::string name{config.name()};
+  std::string description{config.description()};
+  std::string location{config.location()};
+
+  ParametersReader parameters(&command->GetParameters());
+  parameters.GetParameter("name", &name);
+  parameters.GetParameter("description", &description);
+  parameters.GetParameter("location", &location);
+
+  if (!device_info_->UpdateDeviceInfo(name, description, location, nullptr)) {
+    return command->Abort();
+  }
+
+  command->Done();
+}
+
+}  // namespace buffet
diff --git a/buffet/base_api_handler.h b/buffet/base_api_handler.h
new file mode 100644
index 0000000..8743bcf
--- /dev/null
+++ b/buffet/base_api_handler.h
@@ -0,0 +1,41 @@
+// 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_BASE_API_HANDLER_H_
+#define BUFFET_BASE_API_HANDLER_H_
+
+#include <memory>
+#include <string>
+
+#include <base/memory/weak_ptr.h>
+
+namespace buffet {
+
+class CommandInstance;
+class CommandManager;
+class DeviceRegistrationInfo;
+
+// Handles commands from 'base' package.
+// Objects of the class subscribe for notification from CommandManager and
+// execute incoming commands.
+// Handled commands:
+//  base.updateDeviceInfo
+class BaseApiHandler final {
+ public:
+  BaseApiHandler(const base::WeakPtr<DeviceRegistrationInfo>& device_info,
+                 const std::shared_ptr<CommandManager>& command_manager);
+
+ private:
+  void OnCommandAdded(CommandInstance* command);
+  void UpdateDeviceInfo(CommandInstance* command);
+
+  base::WeakPtr<DeviceRegistrationInfo> device_info_;
+
+  base::WeakPtrFactory<BaseApiHandler> weak_ptr_factory_{this};
+  DISALLOW_COPY_AND_ASSIGN(BaseApiHandler);
+};
+
+}  // namespace buffet
+
+#endif  // BUFFET_BASE_API_HANDLER_H_
diff --git a/buffet/base_api_handler_unittest.cc b/buffet/base_api_handler_unittest.cc
new file mode 100644
index 0000000..011f639
--- /dev/null
+++ b/buffet/base_api_handler_unittest.cc
@@ -0,0 +1,99 @@
+// 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.
+
+#include "buffet/base_api_handler.h"
+
+#include <base/strings/string_number_conversions.h>
+#include <base/values.h>
+#include <chromeos/http/http_transport_fake.h>
+#include <gtest/gtest.h>
+
+#include "buffet/buffet_config.h"
+#include "buffet/commands/command_manager.h"
+#include "buffet/commands/unittest_utils.h"
+#include "buffet/device_registration_info.h"
+#include "buffet/states/mock_state_change_queue_interface.h"
+#include "buffet/states/state_manager.h"
+#include "buffet/storage_impls.h"
+
+namespace buffet {
+
+class BaseApiHandlerTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    storage_ = std::make_shared<MemStorage>();
+    transport_ = std::make_shared<chromeos::http::fake::Transport>();
+    command_manager_ = std::make_shared<CommandManager>();
+    state_manager_ = std::make_shared<StateManager>(&mock_state_change_queue_);
+    state_manager_->Startup();
+    dev_reg_.reset(new DeviceRegistrationInfo(
+        command_manager_, state_manager_,
+        std::unique_ptr<BuffetConfig>{new BuffetConfig}, transport_, storage_,
+        true, nullptr));
+    handler_.reset(new BaseApiHandler{dev_reg_->AsWeakPtr(), command_manager_});
+  }
+
+  void LoadCommands(const std::string& command_definitions) {
+    auto json = unittests::CreateDictionaryValue(command_definitions.c_str());
+    EXPECT_TRUE(command_manager_->LoadBaseCommands(*json, nullptr));
+    EXPECT_TRUE(command_manager_->LoadCommands(*json, "", nullptr));
+  }
+
+  void AddCommand(const std::string& command) {
+    auto command_instance = buffet::CommandInstance::FromJson(
+        unittests::CreateDictionaryValue(command.c_str()).get(),
+        commands::attributes::kCommand_Visibility_Local,
+        command_manager_->GetCommandDictionary(), nullptr, nullptr);
+    EXPECT_TRUE(!!command_instance);
+
+    std::string id{base::IntToString(++command_id_)};
+    command_instance->SetID(id);
+    command_manager_->AddCommand(std::move(command_instance));
+    EXPECT_EQ(CommandInstance::kStatusDone,
+              command_manager_->FindCommand(id)->GetStatus());
+  }
+
+  std::shared_ptr<MemStorage> storage_;
+  std::shared_ptr<chromeos::http::fake::Transport> transport_;
+  std::unique_ptr<DeviceRegistrationInfo> dev_reg_;
+  std::shared_ptr<CommandManager> command_manager_;
+  testing::NiceMock<MockStateChangeQueueInterface> mock_state_change_queue_;
+  std::shared_ptr<StateManager> state_manager_;
+  std::unique_ptr<BaseApiHandler> handler_;
+  int command_id_{0};
+};
+
+TEST_F(BaseApiHandlerTest, UpdateDeviceInfo) {
+  LoadCommands(R"({
+    'base': {
+      'updateDeviceInfo': {
+        'parameters': {
+          'description': 'string',
+          'name': {
+            'type': 'string',
+            'minLength': 1
+          },
+          'location': 'string'
+        },
+        'results': {}
+      }
+    }
+  })");
+
+  AddCommand(R"({
+    'name' : 'base.updateDeviceInfo',
+    'parameters': {
+      'name': 'testName',
+      'description': 'testDescription',
+      'location': 'testLocation'
+    }
+  })");
+
+  const BuffetConfig& config{dev_reg_->GetConfig()};
+  EXPECT_EQ("testName", config.name());
+  EXPECT_EQ("testDescription", config.description());
+  EXPECT_EQ("testLocation", config.location());
+}
+
+}  // namespace buffet
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp
index e67b6ea..83cebba 100644
--- a/buffet/buffet.gyp
+++ b/buffet/buffet.gyp
@@ -18,6 +18,7 @@
         'dbus_service_config': 'dbus_bindings/dbus-service-config.json',
       },
       'sources': [
+        'base_api_handler.cc',
         'buffet_config.cc',
         'commands/command_definition.cc',
         'commands/command_dictionary.cc',
@@ -106,6 +107,7 @@
           },
           'includes': ['../common-mk/common_test.gypi'],
           'sources': [
+            'base_api_handler_unittest.cc',
             'buffet_testrunner.cc',
             'commands/command_definition_unittest.cc',
             'commands/command_dictionary_unittest.cc',
diff --git a/buffet/commands/command_manager.cc b/buffet/commands/command_manager.cc
index cbaaeca..d0743de 100644
--- a/buffet/commands/command_manager.cc
+++ b/buffet/commands/command_manager.cc
@@ -134,4 +134,14 @@
   return true;
 }
 
+void CommandManager::AddOnCommandAddedCallback(
+    const CommandQueue::Callback& callback) {
+  command_queue_.AddOnCommandAddedCallback(callback);
+}
+
+void CommandManager::AddOnCommandRemovedCallback(
+    const CommandQueue::Callback& callback) {
+  command_queue_.AddOnCommandRemovedCallback(callback);
+}
+
 }  // namespace buffet
diff --git a/buffet/commands/command_manager.h b/buffet/commands/command_manager.h
index 88ebca7..9a891ba 100644
--- a/buffet/commands/command_manager.h
+++ b/buffet/commands/command_manager.h
@@ -98,6 +98,12 @@
                             CommandDefinition::Visibility visibility,
                             chromeos::ErrorPtr* error);
 
+  // Adds notifications callback for a new command being added to the queue.
+  void AddOnCommandAddedCallback(const CommandQueue::Callback& callback);
+
+  // Adds notifications callback for a command being removed from the queue.
+  void AddOnCommandRemovedCallback(const CommandQueue::Callback& callback);
+
  private:
   CommandDictionary base_dictionary_;  // Base/std command definitions/schemas.
   CommandDictionary dictionary_;  // Command definitions/schemas.
diff --git a/buffet/commands/command_queue.cc b/buffet/commands/command_queue.cc
index 614261e..f4cda60 100644
--- a/buffet/commands/command_queue.cc
+++ b/buffet/commands/command_queue.cc
@@ -15,6 +15,9 @@
 
 void CommandQueue::AddOnCommandAddedCallback(const Callback& callback) {
   on_command_added_.push_back(callback);
+  // Send all pre-existed commands.
+  for (const auto& command : map_)
+    callback.Run(command.second.get());
 }
 
 void CommandQueue::AddOnCommandRemovedCallback(const Callback& callback) {
diff --git a/buffet/device_registration_info.h b/buffet/device_registration_info.h
index a762758..40f4f94 100644
--- a/buffet/device_registration_info.h
+++ b/buffet/device_registration_info.h
@@ -146,6 +146,12 @@
                         const std::string& location,
                         chromeos::ErrorPtr* error);
 
+  const BuffetConfig& GetConfig() const { return *config_; }
+
+  base::WeakPtr<DeviceRegistrationInfo> AsWeakPtr() {
+    return weak_factory_.GetWeakPtr();
+  }
+
  private:
   // Cause DeviceRegistrationInfo to attempt to StartDevice on its own later.
   void ScheduleStartDevice(const base::TimeDelta& later);
diff --git a/buffet/manager.cc b/buffet/manager.cc
index a5fff7d..90562aa 100644
--- a/buffet/manager.cc
+++ b/buffet/manager.cc
@@ -21,6 +21,7 @@
 #include <dbus/object_path.h>
 #include <dbus/values_util.h>
 
+#include "buffet/base_api_handler.h"
 #include "buffet/commands/command_instance.h"
 #include "buffet/commands/schema_constants.h"
 #include "buffet/states/state_change_queue.h"
@@ -57,8 +58,7 @@
       &Manager::OnCommandDefsChanged, weak_ptr_factory_.GetWeakPtr()));
   command_manager_->Startup(base::FilePath{"/etc/buffet"},
                             test_definitions_path);
-  state_change_queue_ = std::unique_ptr<StateChangeQueue>(
-      new StateChangeQueue(kMaxStateChangeQueueSize));
+  state_change_queue_.reset(new StateChangeQueue(kMaxStateChangeQueueSize));
   state_manager_ = std::make_shared<StateManager>(state_change_queue_.get());
   state_manager_->Startup();
   std::unique_ptr<BuffetConfig> config{new BuffetConfig};
@@ -66,15 +66,14 @@
   std::unique_ptr<FileStorage> state_store{new FileStorage{state_path}};
   // TODO(avakulenko): Figure out security implications of storing
   // device info state data unencrypted.
-  device_info_ = std::unique_ptr<DeviceRegistrationInfo>(
-      new DeviceRegistrationInfo(
-          command_manager_,
-          state_manager_,
-          std::move(config),
-          chromeos::http::Transport::CreateDefault(),
-          std::move(state_store),
-          xmpp_enabled,
-          &dbus_adaptor_));
+  device_info_.reset(new DeviceRegistrationInfo(
+      command_manager_, state_manager_, std::move(config),
+      chromeos::http::Transport::CreateDefault(), std::move(state_store),
+      xmpp_enabled, &dbus_adaptor_));
+
+  base_api_handler_.reset(
+      new BaseApiHandler{device_info_->AsWeakPtr(), command_manager_});
+
   device_info_->Load();
   dbus_adaptor_.RegisterWithDBusObject(&dbus_object_);
   dbus_object_.RegisterAsync(cb);
diff --git a/buffet/manager.h b/buffet/manager.h
index a685a05..44790d3 100644
--- a/buffet/manager.h
+++ b/buffet/manager.h
@@ -31,9 +31,10 @@
 
 namespace buffet {
 
+class BaseApiHandler;
+class BuffetConfig;
 class StateChangeQueue;
 class StateManager;
-class BuffetConfig;
 
 template<typename... Types>
 using DBusMethodResponse =
@@ -88,9 +89,9 @@
   std::unique_ptr<StateChangeQueue> state_change_queue_;
   std::shared_ptr<StateManager> state_manager_;
   std::unique_ptr<DeviceRegistrationInfo> device_info_;
+  std::unique_ptr<BaseApiHandler> base_api_handler_;
 
   base::WeakPtrFactory<Manager> weak_ptr_factory_{this};
-
   DISALLOW_COPY_AND_ASSIGN(Manager);
 };