Add support for state updates on ComponentManager

Implemented state support functionality from StateManager. Now the
ComponentManager object can replace StateManager, StatePackage,
CommandDictionary and CommandManager classes.

Most implementation was borrowed/adapted from existing StateManager
and StatePackage classes. And the interfaces were made compatible
with those two classes so the new ComponentManager can be used as
a drop-in replacement of the old classes.

BUG: 25841719
Change-Id: Ib981de1cf44c47a95594c56d78ac211812b3fa44
Reviewed-on: https://weave-review.googlesource.com/1780
Reviewed-by: Vitaly Buka <vitalybuka@google.com>
diff --git a/src/component_manager.cc b/src/component_manager.cc
index 67a0524..bf92a78 100644
--- a/src/component_manager.cc
+++ b/src/component_manager.cc
@@ -9,12 +9,19 @@
 #include <base/strings/string_util.h>
 
 #include "src/commands/schema_constants.h"
+#include "src/json_error_codes.h"
 #include "src/string_utils.h"
 #include "src/utils.h"
 
 namespace weave {
 
+namespace {
+// Max of 100 state update events should be enough in the queue.
+const size_t kMaxStateChangeQueueSize = 100;
+}  // namespace
+
 ComponentManager::ComponentManager() {}
+ComponentManager::ComponentManager(base::Clock* clock) : clock_{clock} {}
 ComponentManager::~ComponentManager() {}
 
 bool ComponentManager::AddComponent(const std::string& path,
@@ -49,6 +56,8 @@
   traits_list->AppendStrings(traits);
   dict->Set("traits", traits_list.release());
   root->SetWithoutPathExpansion(name, dict.release());
+  for (const auto& cb : on_componet_tree_changed_)
+    cb.Run();
   return true;
 }
 
@@ -73,9 +82,17 @@
   traits_list->AppendStrings(traits);
   dict->Set("traits", traits_list.release());
   array_value->Append(dict.release());
+  for (const auto& cb : on_componet_tree_changed_)
+    cb.Run();
   return true;
 }
 
+void ComponentManager::AddComponentTreeChangedCallback(
+    const base::Closure& callback) {
+  on_componet_tree_changed_.push_back(callback);
+  callback.Run();
+}
+
 bool ComponentManager::LoadTraits(const base::DictionaryValue& dict,
                                   ErrorPtr* error) {
   bool modified = false;
@@ -100,9 +117,10 @@
         result = false;
         break;
       }
+    } else {
+      traits_.Set(it.key(), it.value().DeepCopy());
+      modified = true;
     }
-    traits_.Set(it.key(), it.value().DeepCopy());
-    modified = true;
   }
 
   if (modified) {
@@ -119,7 +137,8 @@
   return LoadTraits(*dict, error);
 }
 
-void ComponentManager::AddTraitDefChanged(const base::Closure& callback) {
+void ComponentManager::AddTraitDefChangedCallback(
+    const base::Closure& callback) {
   on_trait_changed_.push_back(callback);
   callback.Run();
 }
@@ -163,7 +182,7 @@
     command_instance->SetComponent(component_path);
   }
 
-  const auto* component = FindComponent(component_path, error);
+  const base::DictionaryValue* component = FindComponent(component_path, error);
   if (!component)
     return false;
 
@@ -266,11 +285,139 @@
   return true;
 }
 
+void ComponentManager::AddStateChangedCallback(const base::Closure& callback) {
+  on_state_changed_.push_back(callback);
+  callback.Run();  // Force to read current state.
+}
+
+bool ComponentManager::SetStateProperties(const std::string& component_path,
+                                          const base::DictionaryValue& dict,
+                                          ErrorPtr* error) {
+  base::DictionaryValue* component =
+      FindMutableComponent(component_path, error);
+  if (!component)
+    return false;
+
+  base::DictionaryValue* state = nullptr;
+  if (!component->GetDictionary("state", &state)) {
+    state = new base::DictionaryValue;
+    component->Set("state", state);
+  }
+  state->MergeDictionary(&dict);
+  last_state_change_id_++;
+  auto& queue = state_change_queues_[component_path];
+  if (!queue)
+    queue.reset(new StateChangeQueue{kMaxStateChangeQueueSize});
+  base::Time timestamp = clock_ ? clock_->Now() : base::Time::Now();
+  queue->NotifyPropertiesUpdated(timestamp, dict);
+  for (const auto& cb : on_state_changed_)
+    cb.Run();
+  return true;
+}
+
+bool ComponentManager::SetStatePropertiesFromJson(
+    const std::string& component_path,
+    const std::string& json,
+    ErrorPtr* error) {
+  std::unique_ptr<const base::DictionaryValue> dict = LoadJsonDict(json, error);
+  return dict && SetStateProperties(component_path, *dict, error);
+}
+
+const base::Value* ComponentManager::GetStateProperty(
+    const std::string& component_path,
+    const std::string& name,
+    ErrorPtr* error) const {
+  const base::DictionaryValue* component = FindComponent(component_path, error);
+  if (!component)
+    return false;
+  auto pair = SplitAtFirst(name, ".", true);
+  if (pair.first.empty()) {
+    Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                       errors::commands::kPropertyMissing,
+                       "Empty state package in '%s'", name.c_str());
+    return false;
+  }
+  if (pair.second.empty()) {
+    Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                       errors::commands::kPropertyMissing,
+                       "State property name not specified in '%s'",
+                       name.c_str());
+    return false;
+  }
+  std::string key = base::StringPrintf("state.%s", name.c_str());
+  const base::Value* value = nullptr;
+  if (!component->Get(key, &value)) {
+    Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                       errors::commands::kPropertyMissing,
+                       "State property '%s' not found in component '%s'",
+                       name.c_str(), component_path.c_str());
+  }
+  return value;
+}
+
+bool ComponentManager::SetStateProperty(const std::string& component_path,
+                                        const std::string& name,
+                                        const base::Value& value,
+                                        ErrorPtr* error) {
+  base::DictionaryValue dict;
+  auto pair = SplitAtFirst(name, ".", true);
+  if (pair.first.empty()) {
+    Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                       errors::commands::kPropertyMissing,
+                       "Empty state package in '%s'", name.c_str());
+    return false;
+  }
+  if (pair.second.empty()) {
+    Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                       errors::commands::kPropertyMissing,
+                       "State property name not specified in '%s'",
+                       name.c_str());
+    return false;
+  }
+  dict.Set(name, value.DeepCopy());
+  return SetStateProperties(component_path, dict, error);
+}
+
+ComponentManager::StateSnapshot
+ComponentManager::GetAndClearRecordedStateChanges() {
+  StateSnapshot snapshot;
+  snapshot.update_id = GetLastStateChangeId();
+  for (auto& pair : state_change_queues_) {
+    auto changes = pair.second->GetAndClearRecordedStateChanges();
+    auto component = pair.first;
+    auto conv = [component](weave::StateChange& change) {
+      return ComponentStateChange{change.timestamp, component,
+                                  std::move(change.changed_properties)};
+    };
+    std::transform(changes.begin(), changes.end(),
+                   std::back_inserter(snapshot.state_changes), conv);
+  }
+
+  // Sort events by the timestamp.
+  auto pred = [](const ComponentStateChange& lhs,
+                 const ComponentStateChange& rhs) {
+    return lhs.timestamp < rhs.timestamp;
+  };
+  std::sort(snapshot.state_changes.begin(), snapshot.state_changes.end(), pred);
+  state_change_queues_.clear();
+  return snapshot;
+}
+
+void ComponentManager::NotifyStateUpdatedOnServer(UpdateID id) {
+  on_server_state_updated_.Notify(id);
+}
+
+ComponentManager::Token ComponentManager::AddServerStateUpdatedCallback(
+    const base::Callback<void(UpdateID)>& callback) {
+  if (state_change_queues_.empty())
+    callback.Run(GetLastStateChangeId());
+  return Token{on_server_state_updated_.Add(callback).release()};
+}
+
 base::DictionaryValue* ComponentManager::FindComponentGraftNode(
     const std::string& path, ErrorPtr* error) {
   base::DictionaryValue* root = nullptr;
-  auto component = const_cast<base::DictionaryValue*>(FindComponentAt(
-      &components_, path, error));
+  base::DictionaryValue* component = FindMutableComponent(path, error);
   if (component && !component->GetDictionary("components", &root)) {
     root = new base::DictionaryValue;
     component->Set("components", root);
@@ -278,6 +425,13 @@
   return root;
 }
 
+base::DictionaryValue* ComponentManager::FindMutableComponent(
+    const std::string& path,
+    ErrorPtr* error) {
+  return const_cast<base::DictionaryValue*>(
+      FindComponentAt(&components_, path, error));
+}
+
 const base::DictionaryValue* ComponentManager::FindComponentAt(
     const base::DictionaryValue* root,
     const std::string& path,
diff --git a/src/component_manager.h b/src/component_manager.h
index d9de8ea..2deaa30 100644
--- a/src/component_manager.h
+++ b/src/component_manager.h
@@ -5,21 +5,49 @@
 #ifndef LIBWEAVE_SRC_COMPONENT_MANAGER_H_
 #define LIBWEAVE_SRC_COMPONENT_MANAGER_H_
 
+#include <map>
 #include <memory>
 
+#include <base/callback_list.h>
+#include <base/time/clock.h>
 #include <base/values.h>
 #include <weave/error.h>
 
 #include "src/commands/command_dictionary.h"
 #include "src/commands/command_queue.h"
+#include "src/states/state_change_queue.h"
 
 namespace weave {
 
 class CommandInstance;
 
+// A simple notification record event to track component state changes.
+// The |timestamp| records the time of the state change.
+// |changed_properties| contains a property set with the new property values
+// which were updated at the time the event was recorded.
+struct ComponentStateChange {
+  ComponentStateChange(base::Time time,
+                       const std::string& path,
+                       std::unique_ptr<base::DictionaryValue> properties)
+      : timestamp{time}, component{path},
+        changed_properties{std::move(properties)} {}
+  base::Time timestamp;
+  std::string component;
+  std::unique_ptr<base::DictionaryValue> changed_properties;
+};
+
 class ComponentManager final {
  public:
+  using UpdateID = uint64_t;
+  using Token =
+      std::unique_ptr<base::CallbackList<void(UpdateID)>::Subscription>;
+  struct StateSnapshot {
+    UpdateID update_id;
+    std::vector<ComponentStateChange> state_changes;
+  };
+
   ComponentManager();
+  explicit ComponentManager(base::Clock* clock);
   ~ComponentManager();
 
   // Loads trait definition schema.
@@ -30,7 +58,7 @@
   bool LoadTraits(const std::string& json, ErrorPtr* error);
 
   // Sets callback which is called when new trait definitions are added.
-  void AddTraitDefChanged(const base::Closure& callback);
+  void AddTraitDefChangedCallback(const base::Closure& callback);
 
   // Adds a new component instance to device.
   // |path| is a path to the parent component (or empty string if a root-level
@@ -51,6 +79,9 @@
                              const std::vector<std::string>& traits,
                              ErrorPtr* error);
 
+  // Sets callback which is called when new components are added.
+  void AddComponentTreeChangedCallback(const base::Closure& callback);
+
   // Adds a new command instance to the command queue. The command specified in
   // |command_instance| must be fully initialized and have its name, component,
   // id populated.
@@ -102,11 +133,49 @@
   // Returns the full JSON dictionary containing component instances.
   const base::DictionaryValue& GetComponents() const { return components_; }
 
+  // Component state manipulation methods.
+  bool SetStateProperties(const std::string& component_path,
+                          const base::DictionaryValue& dict,
+                          ErrorPtr* error);
+  bool SetStatePropertiesFromJson(const std::string& component_path,
+                                  const std::string& json,
+                                  ErrorPtr* error);
+  const base::Value* GetStateProperty(const std::string& component_path,
+                                      const std::string& name,
+                                      ErrorPtr* error) const;
+  bool SetStateProperty(const std::string& component_path,
+                        const std::string& name,
+                        const base::Value& value,
+                        ErrorPtr* error);
+
+  void AddStateChangedCallback(const base::Closure& callback);
+
+  // Returns the recorded state changes since last time this method was called.
+  StateSnapshot GetAndClearRecordedStateChanges();
+
+  // Called to notify that the state patch with |id| has been successfully sent
+  // to the server and processed.
+  void NotifyStateUpdatedOnServer(UpdateID id);
+
+  // Returns an ID of last state change update. Each SetStatePropertyNNN()
+  // invocation increments this value by 1.
+  UpdateID GetLastStateChangeId() const { return last_state_change_id_; }
+
+  // Subscribes for device state update notifications from cloud server.
+  // The |callback| will be called every time a state patch with given ID is
+  // successfully received and processed by Weave server.
+  // Returns a subscription token. As soon as this token is destroyed, the
+  // respective callback is removed from the callback list.
+  Token AddServerStateUpdatedCallback(
+      const base::Callback<void(UpdateID)>& callback);
+
  private:
   // A helper method to find a JSON element of component at |path| to add new
   // sub-components to.
   base::DictionaryValue* FindComponentGraftNode(const std::string& path,
                                                 ErrorPtr* error);
+  base::DictionaryValue* FindMutableComponent(const std::string& path,
+                                              ErrorPtr* error);
 
   // Helper method to find a sub-component given a root node and a relative path
   // from the root to the target component.
@@ -115,13 +184,22 @@
       const std::string& path,
       ErrorPtr* error);
 
-
+  base::Clock* clock_{nullptr};
   base::DictionaryValue traits_;  // Trait definitions.
   base::DictionaryValue components_;  // Component instances.
   CommandQueue command_queue_;  // Command queue containing command instances.
-  std::vector<base::Callback<void()>> on_trait_changed_;
+  std::vector<base::Closure> on_trait_changed_;
+  std::vector<base::Closure> on_componet_tree_changed_;
+  std::vector<base::Closure> on_state_changed_;
   uint32_t next_command_id_{0};
 
+  std::map<std::string, std::unique_ptr<StateChangeQueue>> state_change_queues_;
+  // An ID of last state change update. Each NotifyPropertiesUpdated()
+  // invocation increments this value by 1.
+  UpdateID last_state_change_id_{0};
+  // Callback list for state change queue event sinks.
+  base::CallbackList<void(UpdateID)> on_server_state_updated_;
+
   DISALLOW_COPY_AND_ASSIGN(ComponentManager);
 };
 
diff --git a/src/component_manager_unittest.cc b/src/component_manager_unittest.cc
index ba6bc34..83f9933 100644
--- a/src/component_manager_unittest.cc
+++ b/src/component_manager_unittest.cc
@@ -30,6 +30,71 @@
   return false;
 }
 
+// Creates sample trait/component trees:
+// {

+//   "traits": {

+//     "t1": {},

+//     "t2": {},

+//     "t3": {},

+//     "t4": {},

+//     "t5": {},

+//     "t6": {},

+//   },

+//   "components": {

+//     "comp1": {

+//       "traits": [ "t1" ],

+//       "components": {

+//         "comp2": [

+//           { "traits": [ "t2" ] },

+//           {

+//             "traits": [ "t3" ],

+//             "components": {

+//               "comp3": {

+//                 "traits": [ "t4" ],

+//                 "components": {

+//                   "comp4": {

+//                     "traits": [ "t5", "t6" ]

+//                   }

+//                 }

+//               }

+//             }

+//           }

+//         ],

+//       }

+//     }

+//   }

+// }
+void CreateTestComponentTree(ComponentManager* manager) {
+  const char kTraits[] = R"({"t1":{},"t2":{},"t3":{},"t4":{},"t5":{},"t6":{}})";
+  auto json = CreateDictionaryValue(kTraits);
+  ASSERT_TRUE(manager->LoadTraits(*json, nullptr));
+  EXPECT_TRUE(manager->AddComponent("", "comp1", {"t1"}, nullptr));
+  EXPECT_TRUE(manager->AddComponentArrayItem("comp1", "comp2", {"t2"},
+                                             nullptr));
+  EXPECT_TRUE(manager->AddComponentArrayItem("comp1", "comp2", {"t3"},
+                                             nullptr));
+  EXPECT_TRUE(manager->AddComponent("comp1.comp2[1]", "comp3", {"t4"},
+                                    nullptr));
+  EXPECT_TRUE(manager->AddComponent("comp1.comp2[1].comp3", "comp4",
+                                    {"t5", "t6"}, nullptr));
+}
+
+// Test clock class to record predefined time intervals.
+// Implementation from base/test/simple_test_clock.{h|cc}
+class SimpleTestClock : public base::Clock {
+ public:
+  base::Time Now() override { return now_; }
+
+  // Advances the clock by |delta|.
+  void Advance(base::TimeDelta delta) { now_ += delta; }
+
+  // Sets the clock to the given time.
+  void SetNow(base::Time now) { now_ = now; }
+
+ private:
+  base::Time now_;
+};
+
 }  // anonymous namespace
 
 TEST(ComponentManager, Empty) {
@@ -176,6 +241,63 @@
   EXPECT_FALSE(manager.LoadTraits(*json, nullptr));
 }
 
+TEST(ComponentManager, AddTraitDefChangedCallback) {
+  ComponentManager manager;
+  int count = 0;
+  int count2 = 0;
+  manager.AddTraitDefChangedCallback(base::Bind([&count]() { count++; }));
+  manager.AddTraitDefChangedCallback(base::Bind([&count2]() { count2++; }));
+  EXPECT_EQ(1, count);
+  EXPECT_EQ(1, count2);
+  // New definitions.
+  const char kTraits1[] = R"({
+    "trait1": {
+      "state": {
+        "property1": {"type": "boolean"}
+      }
+    },
+    "trait2": {
+      "state": {
+        "property2": {"type": "string"}
+      }
+    }
+  })";
+  auto json = CreateDictionaryValue(kTraits1);
+  EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
+  EXPECT_EQ(2, count);
+  // Duplicate definition, shouldn't call the callback.
+  const char kTraits2[] = R"({
+    "trait1": {
+      "state": {
+        "property1": {"type": "boolean"}
+      }
+    }
+  })";
+  json = CreateDictionaryValue(kTraits2);
+  EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
+  EXPECT_EQ(2, count);
+  // New definition, should call the callback now.
+  const char kTraits3[] = R"({
+    "trait3": {
+      "state": {
+        "property3": {"type": "string"}
+      }
+    }
+  })";
+  json = CreateDictionaryValue(kTraits3);
+  EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
+  EXPECT_EQ(3, count);
+  // Wrong definition, shouldn't call the callback.
+  const char kTraits4[] = R"({
+    "trait4": "foo"
+  })";
+  json = CreateDictionaryValue(kTraits4);
+  EXPECT_FALSE(manager.LoadTraits(*json, nullptr));
+  EXPECT_EQ(3, count);
+  // Make sure both callbacks were called the same number of times.
+  EXPECT_EQ(count2, count);
+}
+
 TEST(ComponentManager, LoadTraitsNotAnObject) {
   ComponentManager manager;
   const char kTraits1[] = R"({"trait1": 0})";
@@ -426,18 +548,32 @@
   EXPECT_FALSE(manager.AddComponent("comp1", "comp2", {}, nullptr));
 }
 
+TEST(ComponentManager, AddComponentTreeChangedCallback) {
+  ComponentManager manager;
+  int count = 0;
+  int count2 = 0;
+  manager.AddComponentTreeChangedCallback(base::Bind([&count]() { count++; }));
+  manager.AddComponentTreeChangedCallback(
+      base::Bind([&count2]() { count2++; }));
+  EXPECT_EQ(1, count);
+  EXPECT_EQ(1, count2);
+  EXPECT_TRUE(manager.AddComponent("", "comp1", {}, nullptr));
+  EXPECT_EQ(2, count);
+  EXPECT_TRUE(manager.AddComponent("comp1", "comp2", {}, nullptr));
+  EXPECT_EQ(3, count);
+  EXPECT_TRUE(manager.AddComponent("comp1.comp2", "comp4", {}, nullptr));
+  EXPECT_EQ(4, count);
+  EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp3", {}, nullptr));
+  EXPECT_EQ(5, count);
+  EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp3", {}, nullptr));
+  EXPECT_EQ(6, count);
+  // Make sure both callbacks were called the same number of times.
+  EXPECT_EQ(count2, count);
+}
+
 TEST(ComponentManager, FindComponent) {
   ComponentManager manager;
-  const char kTraits[] = R"({"t1":{}, "t2":{}, "t3":{}, "t4":{}, "t5":{}})";
-  auto json = CreateDictionaryValue(kTraits);
-  ASSERT_TRUE(manager.LoadTraits(*json, nullptr));
-
-  EXPECT_TRUE(manager.AddComponent("", "comp1", {"t1"}, nullptr));
-  EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp2", {"t2"}, nullptr));
-  EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp2", {"t3"}, nullptr));
-  EXPECT_TRUE(manager.AddComponent("comp1.comp2[1]", "comp3", {"t4"}, nullptr));
-  EXPECT_TRUE(manager.AddComponent("comp1.comp2[1].comp3", "comp4", {"t5"},
-                                   nullptr));
+  CreateTestComponentTree(&manager);
 
   const base::DictionaryValue* comp = manager.FindComponent("comp1", nullptr);
   ASSERT_NE(nullptr, comp);
@@ -606,4 +742,347 @@
   last_tags.clear();
 }
 
+TEST(ComponentManager, SetStateProperties) {
+  ComponentManager manager;
+  CreateTestComponentTree(&manager);
+
+  const char kState1[] = R"({"t1": {"p1": 0, "p2": "foo"}})";
+  auto state1 = CreateDictionaryValue(kState1);
+  ASSERT_TRUE(manager.SetStateProperties("comp1", *state1, nullptr));
+  const char kExpected1[] = R"({
+    "comp1": {

+      "traits": [ "t1" ],

+      "state": {"t1": {"p1": 0, "p2": "foo"}},

+      "components": {

+        "comp2": [

+          {

+            "traits": [ "t2" ]

+          },

+          {

+            "traits": [ "t3" ],

+            "components": {

+              "comp3": {

+                "traits": [ "t4" ],

+                "components": {

+                  "comp4": {

+                    "traits": [ "t5", "t6" ]

+                  }

+                }

+              }

+            }

+          }

+        ]

+      }

+    }

+  })";
+  EXPECT_JSON_EQ(kExpected1, manager.GetComponents());
+
+  const char kState2[] = R"({"t1": {"p1": {"bar": "baz"}}})";
+  auto state2 = CreateDictionaryValue(kState2);
+  ASSERT_TRUE(manager.SetStateProperties("comp1", *state2, nullptr));
+
+  const char kExpected2[] = R"({
+    "comp1": {

+      "traits": [ "t1" ],

+      "state": {"t1": {"p1": {"bar": "baz"}, "p2": "foo"}},

+      "components": {

+        "comp2": [

+          {

+            "traits": [ "t2" ]

+          },

+          {

+            "traits": [ "t3" ],

+            "components": {

+              "comp3": {

+                "traits": [ "t4" ],

+                "components": {

+                  "comp4": {

+                    "traits": [ "t5", "t6" ]

+                  }

+                }

+              }

+            }

+          }

+        ]

+      }

+    }

+  })";
+  EXPECT_JSON_EQ(kExpected2, manager.GetComponents());
+
+  const char kState3[] = R"({"t5": {"p1": 1}})";
+  auto state3 = CreateDictionaryValue(kState3);
+  ASSERT_TRUE(manager.SetStateProperties("comp1.comp2[1].comp3.comp4", *state3,
+                                         nullptr));
+
+  const char kExpected3[] = R"({
+    "comp1": {

+      "traits": [ "t1" ],

+      "state": {"t1": {"p1": {"bar": "baz"}, "p2": "foo"}},

+      "components": {

+        "comp2": [

+          {

+            "traits": [ "t2" ]

+          },

+          {

+            "traits": [ "t3" ],

+            "components": {

+              "comp3": {

+                "traits": [ "t4" ],

+                "components": {

+                  "comp4": {

+                    "traits": [ "t5", "t6" ],

+                    "state": { "t5": { "p1": 1 } }

+                  }

+                }

+              }

+            }

+          }

+        ]

+      }

+    }

+  })";
+  EXPECT_JSON_EQ(kExpected3, manager.GetComponents());
+}
+
+TEST(ComponentManager, SetStatePropertiesFromJson) {
+  ComponentManager manager;
+  CreateTestComponentTree(&manager);
+
+  ASSERT_TRUE(manager.SetStatePropertiesFromJson(
+      "comp1.comp2[1].comp3.comp4", R"({"t5": {"p1": 3}, "t6": {"p2": 5}})",
+      nullptr));
+
+  const char kExpected[] = R"({
+    "comp1": {

+      "traits": [ "t1" ],

+      "components": {

+        "comp2": [

+          {

+            "traits": [ "t2" ]

+          },

+          {

+            "traits": [ "t3" ],

+            "components": {

+              "comp3": {

+                "traits": [ "t4" ],

+                "components": {

+                  "comp4": {

+                    "traits": [ "t5", "t6" ],

+                    "state": {

+                      "t5": { "p1": 3 },

+                      "t6": { "p2": 5 }

+                    }

+                  }

+                }

+              }

+            }

+          }

+        ]

+      }

+    }

+  })";
+  EXPECT_JSON_EQ(kExpected, manager.GetComponents());
+}
+
+TEST(ComponentManager, SetGetStateProperty) {
+  ComponentManager manager;
+  const char kTraits[] = R"({
+    "trait1": {
+      "state": {
+        "prop1": { "type": "string" },
+        "prop2": { "type": "integer" }
+      }
+    },
+    "trait2": {
+      "state": {
+        "prop3": { "type": "string" },
+        "prop4": { "type": "string" }
+      }
+    }
+  })";
+  auto traits = CreateDictionaryValue(kTraits);
+  ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
+  ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1", "trait2"}, nullptr));
+
+  base::StringValue p1("foo");
+  ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop1", p1, nullptr));
+
+  const char kExpected1[] = R"({
+    "comp1": {

+      "traits": [ "trait1", "trait2" ],

+      "state": {

+        "trait1": { "prop1": "foo" }

+      }

+    }

+  })";
+  EXPECT_JSON_EQ(kExpected1, manager.GetComponents());
+
+  base::FundamentalValue p2(2);
+  ASSERT_TRUE(manager.SetStateProperty("comp1", "trait2.prop3", p2, nullptr));
+
+  const char kExpected2[] = R"({
+    "comp1": {

+      "traits": [ "trait1", "trait2" ],

+      "state": {

+        "trait1": { "prop1": "foo" },

+        "trait2": { "prop3": 2 }

+      }

+    }

+  })";
+  EXPECT_JSON_EQ(kExpected2, manager.GetComponents());
+  // Just the package name without property:
+  EXPECT_FALSE(manager.SetStateProperty("comp1", "trait2", p2, nullptr));
+
+  const base::Value* value = manager.GetStateProperty("comp1", "trait1.prop1",
+                                                      nullptr);
+  ASSERT_NE(nullptr, value);
+  EXPECT_TRUE(p1.Equals(value));
+  value = manager.GetStateProperty("comp1", "trait2.prop3", nullptr);
+  ASSERT_NE(nullptr, value);
+  EXPECT_TRUE(p2.Equals(value));
+
+  // Non-existing property:
+  EXPECT_EQ(nullptr, manager.GetStateProperty("comp1", "trait2.p", nullptr));
+  // Non-existing component
+  EXPECT_EQ(nullptr, manager.GetStateProperty("comp2", "trait.prop", nullptr));
+  // Just the package name without property:
+  EXPECT_EQ(nullptr, manager.GetStateProperty("comp1", "trait2", nullptr));
+}
+
+TEST(ComponentManager, AddStateChangedCallback) {
+  SimpleTestClock clock;
+  ComponentManager manager{&clock};
+  const char kTraits[] = R"({
+    "trait1": {
+      "state": {
+        "prop1": { "type": "string" },
+        "prop2": { "type": "string" }
+      }
+    }
+  })";
+  auto traits = CreateDictionaryValue(kTraits);
+  ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
+  ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1"}, nullptr));
+
+  int count = 0;
+  int count2 = 0;
+  manager.AddStateChangedCallback(base::Bind([&count]() { count++; }));
+  manager.AddStateChangedCallback(base::Bind([&count2]() { count2++; }));
+  EXPECT_EQ(1, count);
+  EXPECT_EQ(1, count2);
+  EXPECT_EQ(0, manager.GetLastStateChangeId());
+
+  base::StringValue p1("foo");
+  ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop1", p1, nullptr));
+  EXPECT_EQ(2, count);
+  EXPECT_EQ(2, count2);
+  EXPECT_EQ(1, manager.GetLastStateChangeId());
+
+  ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop2", p1, nullptr));
+  EXPECT_EQ(3, count);
+  EXPECT_EQ(3, count2);
+  EXPECT_EQ(2, manager.GetLastStateChangeId());
+
+  // Fail - no component.
+  ASSERT_FALSE(manager.SetStateProperty("comp2", "trait1.prop2", p1, nullptr));
+  EXPECT_EQ(3, count);
+  EXPECT_EQ(3, count2);
+  EXPECT_EQ(2, manager.GetLastStateChangeId());
+}
+
+TEST(ComponentManager, ComponentStateUpdates) {
+  SimpleTestClock clock;
+  ComponentManager manager{&clock};
+  const char kTraits[] = R"({
+    "trait1": {
+      "state": {
+        "prop1": { "type": "string" },
+        "prop2": { "type": "string" }
+      }
+    },
+    "trait2": {
+      "state": {
+        "prop3": { "type": "string" },
+        "prop4": { "type": "string" }
+      }
+    }
+  })";
+  auto traits = CreateDictionaryValue(kTraits);
+  ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
+  ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1", "trait2"}, nullptr));
+  ASSERT_TRUE(manager.AddComponent("", "comp2", {"trait1", "trait2"}, nullptr));
+
+  std::vector<ComponentManager::UpdateID> updates1;
+  auto callback1 = [&updates1](ComponentManager::UpdateID id) {
+    updates1.push_back(id);
+  };
+  // State change queue is empty, callback should be called immediately.
+  auto token1 = manager.AddServerStateUpdatedCallback(base::Bind(callback1));
+  ASSERT_EQ(1u, updates1.size());
+  EXPECT_EQ(manager.GetLastStateChangeId(), updates1.front());
+  updates1.clear();
+
+  base::StringValue foo("foo");
+  base::Time time1 = base::Time::Now();
+  clock.SetNow(time1);
+  // These three updates should be grouped into two separate state change queue
+  // items, since they all happen at the same time, but for two different
+  // components.
+  ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop1", foo, nullptr));
+  ASSERT_TRUE(manager.SetStateProperty("comp2", "trait2.prop3", foo, nullptr));
+  ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop2", foo, nullptr));
+
+  std::vector<ComponentManager::UpdateID> updates2;
+  auto callback2 = [&updates2](ComponentManager::UpdateID id) {
+    updates2.push_back(id);
+  };
+  // State change queue is not empty, so callback will be called later.
+  auto token2 = manager.AddServerStateUpdatedCallback(base::Bind(callback2));
+  EXPECT_TRUE(updates2.empty());
+
+  base::StringValue bar("bar");
+  base::Time time2 = time1 + base::TimeDelta::FromSeconds(1);
+  clock.SetNow(time2);
+  // Two more update events (as above) but at |time2|.
+  ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop1", bar, nullptr));
+  ASSERT_TRUE(manager.SetStateProperty("comp2", "trait2.prop3", bar, nullptr));
+  ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop2", bar, nullptr));
+
+  auto snapshot = manager.GetAndClearRecordedStateChanges();
+  EXPECT_EQ(manager.GetLastStateChangeId(), snapshot.update_id);
+  ASSERT_EQ(4u, snapshot.state_changes.size());
+
+  EXPECT_EQ("comp1", snapshot.state_changes[0].component);
+  EXPECT_EQ(time1, snapshot.state_changes[0].timestamp);
+  EXPECT_JSON_EQ(R"({"trait1":{"prop1":"foo","prop2":"foo"}})",
+                 *snapshot.state_changes[0].changed_properties);
+
+  EXPECT_EQ("comp2", snapshot.state_changes[1].component);
+  EXPECT_EQ(time1, snapshot.state_changes[1].timestamp);
+  EXPECT_JSON_EQ(R"({"trait2":{"prop3":"foo"}})",
+                 *snapshot.state_changes[1].changed_properties);
+
+  EXPECT_EQ("comp1", snapshot.state_changes[2].component);
+  EXPECT_EQ(time2, snapshot.state_changes[2].timestamp);
+  EXPECT_JSON_EQ(R"({"trait1":{"prop1":"bar","prop2":"bar"}})",
+                 *snapshot.state_changes[2].changed_properties);
+
+  EXPECT_EQ("comp2", snapshot.state_changes[3].component);
+  EXPECT_EQ(time2, snapshot.state_changes[3].timestamp);
+  EXPECT_JSON_EQ(R"({"trait2":{"prop3":"bar"}})",
+                 *snapshot.state_changes[3].changed_properties);
+
+  // Make sure previous GetAndClearRecordedStateChanges() clears the queue.
+  auto snapshot2 = manager.GetAndClearRecordedStateChanges();
+  EXPECT_EQ(manager.GetLastStateChangeId(), snapshot2.update_id);
+  EXPECT_TRUE(snapshot2.state_changes.empty());
+
+  // Now indicate that we have update the changes on the server.
+  manager.NotifyStateUpdatedOnServer(snapshot.update_id);
+  ASSERT_EQ(1u, updates1.size());
+  EXPECT_EQ(snapshot.update_id, updates1.front());
+  ASSERT_EQ(1u, updates2.size());
+  EXPECT_EQ(snapshot.update_id, updates2.front());
+}
+
 }  // namespace weave