普通文本  |  1488行  |  43.66 KB

// Copyright 2015 The Weave 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 "src/component_manager_impl.h"

#include <map>

#include <gtest/gtest.h>
#include <weave/provider/test/fake_task_runner.h>
#include <weave/test/unittest_utils.h>

#include "src/bind_lambda.h"
#include "src/commands/schema_constants.h"
#include "src/mock_component_manager.h"
#include "src/test/mock_clock.h"

namespace weave {

using test::CreateDictionaryValue;
using testing::Return;
using testing::StrictMock;

namespace {

bool HasTrait(const base::DictionaryValue& comp, const std::string& trait) {
  const base::ListValue* list = nullptr;
  if (!comp.GetList("traits", &list))
    return false;
  for (const base::Value* item : *list) {
    std::string value;
    if (item->GetAsString(&value) && value == trait)
      return true;
  }
  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" ]
//                   }
//                 }
//               }
//             }
//           }
//         ],
//       }
//     }
//   }
// }
class ComponentManagerTest : public ::testing::Test {
 protected:
  void SetUp() override {
    EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(base::Time::Now()));
  }

  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));
  }

  StrictMock<provider::test::FakeTaskRunner> task_runner_;
  StrictMock<test::MockClock> clock_;
  ComponentManagerImpl manager_{&task_runner_, &clock_};
};

}  // anonymous namespace

TEST_F(ComponentManagerTest, Empty) {
  EXPECT_TRUE(manager_.GetTraits().empty());
  EXPECT_TRUE(manager_.GetComponents().empty());
}

TEST_F(ComponentManagerTest, LoadTraits) {
  const char kTraits[] = R"({
    "trait1": {
      "commands": {
        "command1": {
          "minimalRole": "user",
          "parameters": {"height": {"type": "integer"}}
        }
      },
      "state": {
        "property1": {"type": "boolean"}
      }
    },
    "trait2": {
      "state": {
        "property2": {"type": "string"}
      }
    }
  })";
  auto json = CreateDictionaryValue(kTraits);
  EXPECT_TRUE(manager_.LoadTraits(*json, nullptr));
  EXPECT_JSON_EQ(kTraits, manager_.GetTraits());
  EXPECT_TRUE(manager_.GetComponents().empty());
}

TEST_F(ComponentManagerTest, LoadTraitsDuplicateIdentical) {
  const char kTraits1[] = R"({
    "trait1": {
      "commands": {
        "command1": {
          "minimalRole": "user",
          "parameters": {"height": {"type": "integer"}}
        }
      },
      "state": {
        "property1": {"type": "boolean"}
      }
    },
    "trait2": {
      "state": {
        "property2": {"type": "string"}
      }
    }
  })";
  auto json = CreateDictionaryValue(kTraits1);
  EXPECT_TRUE(manager_.LoadTraits(*json, nullptr));
  const char kTraits2[] = R"({
    "trait1": {
      "commands": {
        "command1": {
          "minimalRole": "user",
          "parameters": {"height": {"type": "integer"}}
        }
      },
      "state": {
        "property1": {"type": "boolean"}
      }
    },
    "trait3": {
      "state": {
        "property3": {"type": "string"}
      }
    }
  })";
  json = CreateDictionaryValue(kTraits2);
  EXPECT_TRUE(manager_.LoadTraits(*json, nullptr));
  const char kExpected[] = R"({
    "trait1": {
      "commands": {
        "command1": {
          "minimalRole": "user",
          "parameters": {"height": {"type": "integer"}}
        }
      },
      "state": {
        "property1": {"type": "boolean"}
      }
    },
    "trait2": {
      "state": {
        "property2": {"type": "string"}
      }
    },
    "trait3": {
      "state": {
        "property3": {"type": "string"}
      }
    }
  })";
  EXPECT_JSON_EQ(kExpected, manager_.GetTraits());
}

TEST_F(ComponentManagerTest, LoadTraitsDuplicateOverride) {
  const char kTraits1[] = R"({
    "trait1": {
      "commands": {
        "command1": {
          "minimalRole": "user",
          "parameters": {"height": {"type": "integer"}}
        }
      },
      "state": {
        "property1": {"type": "boolean"}
      }
    },
    "trait2": {
      "state": {
        "property2": {"type": "string"}
      }
    }
  })";
  auto json = CreateDictionaryValue(kTraits1);
  EXPECT_TRUE(manager_.LoadTraits(*json, nullptr));
  const char kTraits2[] = R"({
    "trait1": {
      "commands": {
        "command1": {
          "minimalRole": "user",
          "parameters": {"height": {"type": "string"}}
        }
      },
      "state": {
        "property1": {"type": "boolean"}
      }
    },
    "trait3": {
      "state": {
        "property3": {"type": "string"}
      }
    }
  })";
  json = CreateDictionaryValue(kTraits2);
  EXPECT_FALSE(manager_.LoadTraits(*json, nullptr));
}

TEST_F(ComponentManagerTest, AddTraitDefChangedCallback) {
  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_F(ComponentManagerTest, LoadTraitsNotAnObject) {
  const char kTraits1[] = R"({"trait1": 0})";
  auto json = CreateDictionaryValue(kTraits1);
  ErrorPtr error;
  EXPECT_FALSE(manager_.LoadTraits(*json, &error));
  EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
}

TEST_F(ComponentManagerTest, FindTraitDefinition) {
  const char kTraits[] = R"({
    "trait1": {
      "commands": {
        "command1": {
          "minimalRole": "user",
          "parameters": {"height": {"type": "integer"}}
        }
      },
      "state": {
        "property1": {"type": "boolean"}
      }
    },
    "trait2": {
      "state": {
        "property2": {"type": "string"}
      }
    }
  })";
  auto json = CreateDictionaryValue(kTraits);
  ASSERT_TRUE(manager_.LoadTraits(*json, nullptr));

  const base::DictionaryValue* trait = manager_.FindTraitDefinition("trait1");
  ASSERT_NE(nullptr, trait);
  const char kExpected1[] = R"({
    "commands": {
      "command1": {
        "minimalRole": "user",
        "parameters": {"height": {"type": "integer"}}
      }
    },
    "state": {
      "property1": {"type": "boolean"}
    }
  })";
  EXPECT_JSON_EQ(kExpected1, *trait);

  trait = manager_.FindTraitDefinition("trait2");
  ASSERT_NE(nullptr, trait);
  const char kExpected2[] = R"({
    "state": {
      "property2": {"type": "string"}
    }
  })";
  EXPECT_JSON_EQ(kExpected2, *trait);

  EXPECT_EQ(nullptr, manager_.FindTraitDefinition("trait3"));
}

TEST_F(ComponentManagerTest, FindCommandDefinition) {
  const char kTraits[] = R"({
    "trait1": {
      "commands": {
        "command1": {
          "minimalRole": "user",
          "parameters": {"height": {"type": "integer"}}
        }
      }
    },
    "trait2": {
      "commands": {
        "command1": {
          "minimalRole": "manager"
        },
        "command2": {
          "minimalRole": "owner"
        }
      }
    }
  })";
  auto json = CreateDictionaryValue(kTraits);
  ASSERT_TRUE(manager_.LoadTraits(*json, nullptr));

  const auto* cmd_def = manager_.FindCommandDefinition("trait1.command1");
  ASSERT_NE(nullptr, cmd_def);
  const char kExpected1[] = R"({
    "minimalRole": "user",
    "parameters": {"height": {"type": "integer"}}
  })";
  EXPECT_JSON_EQ(kExpected1, *cmd_def);

  cmd_def = manager_.FindCommandDefinition("trait2.command1");
  ASSERT_NE(nullptr, cmd_def);
  const char kExpected2[] = R"({
    "minimalRole": "manager"
  })";
  EXPECT_JSON_EQ(kExpected2, *cmd_def);

  cmd_def = manager_.FindCommandDefinition("trait2.command2");
  ASSERT_NE(nullptr, cmd_def);
  const char kExpected3[] = R"({
    "minimalRole": "owner"
  })";
  EXPECT_JSON_EQ(kExpected3, *cmd_def);

  EXPECT_EQ(nullptr, manager_.FindTraitDefinition("trait1.command2"));
  EXPECT_EQ(nullptr, manager_.FindTraitDefinition("trait3.command1"));
  EXPECT_EQ(nullptr, manager_.FindTraitDefinition("trait"));
  EXPECT_EQ(nullptr,
            manager_.FindTraitDefinition("trait1.command1.parameters"));
}

TEST_F(ComponentManagerTest, GetMinimalRole) {
  const char kTraits[] = R"({
    "trait1": {
      "commands": {
        "command1": { "minimalRole": "user" },
        "command2": { "minimalRole": "viewer" }
      }
    },
    "trait2": {
      "commands": {
        "command1": { "minimalRole": "manager" },
        "command2": { "minimalRole": "owner" }
      }
    }
  })";
  auto json = CreateDictionaryValue(kTraits);
  ASSERT_TRUE(manager_.LoadTraits(*json, nullptr));

  UserRole role;
  ASSERT_TRUE(manager_.GetMinimalRole("trait1.command1", &role, nullptr));
  EXPECT_EQ(UserRole::kUser, role);

  ASSERT_TRUE(manager_.GetMinimalRole("trait1.command2", &role, nullptr));
  EXPECT_EQ(UserRole::kViewer, role);

  ASSERT_TRUE(manager_.GetMinimalRole("trait2.command1", &role, nullptr));
  EXPECT_EQ(UserRole::kManager, role);

  ASSERT_TRUE(manager_.GetMinimalRole("trait2.command2", &role, nullptr));
  EXPECT_EQ(UserRole::kOwner, role);

  EXPECT_FALSE(manager_.GetMinimalRole("trait1.command3", &role, nullptr));
}

TEST_F(ComponentManagerTest, AddComponent) {
  const char kTraits[] = R"({"trait1": {}, "trait2": {}, "trait3": {}})";
  auto json = CreateDictionaryValue(kTraits);
  ASSERT_TRUE(manager_.LoadTraits(*json, nullptr));
  EXPECT_TRUE(
      manager_.AddComponent("", "comp1", {"trait1", "trait2"}, nullptr));
  EXPECT_TRUE(manager_.AddComponent("", "comp2", {"trait3"}, nullptr));
  const char kExpected[] = R"({
    "comp1": {
      "traits": ["trait1", "trait2"]
    },
    "comp2": {
      "traits": ["trait3"]
    }
  })";
  EXPECT_JSON_EQ(kExpected, manager_.GetComponents());

  // 'trait4' is undefined, so can't add a component referring to it.
  EXPECT_FALSE(manager_.AddComponent("", "comp3", {"trait4"}, nullptr));
}

TEST_F(ComponentManagerTest, AddSubComponent) {
  EXPECT_TRUE(manager_.AddComponent("", "comp1", {}, nullptr));
  EXPECT_TRUE(manager_.AddComponent("comp1", "comp2", {}, nullptr));
  EXPECT_TRUE(manager_.AddComponent("comp1", "comp3", {}, nullptr));
  EXPECT_TRUE(manager_.AddComponent("comp1.comp2", "comp4", {}, nullptr));
  const char kExpected[] = R"({
    "comp1": {
      "traits": [],
      "components": {
        "comp2": {
          "traits": [],
          "components": {
            "comp4": {
              "traits": []
            }
          }
        },
        "comp3": {
          "traits": []
        }
      }
    }
  })";
  EXPECT_JSON_EQ(kExpected, manager_.GetComponents());
}

TEST_F(ComponentManagerTest, AddComponentArrayItem) {
  const char kTraits[] = R"({"foo": {}, "bar": {}})";
  auto json = CreateDictionaryValue(kTraits);
  ASSERT_TRUE(manager_.LoadTraits(*json, nullptr));

  EXPECT_TRUE(manager_.AddComponent("", "comp1", {}, nullptr));
  EXPECT_TRUE(
      manager_.AddComponentArrayItem("comp1", "comp2", {"foo"}, nullptr));
  EXPECT_TRUE(
      manager_.AddComponentArrayItem("comp1", "comp2", {"bar"}, nullptr));
  EXPECT_TRUE(manager_.AddComponent("comp1.comp2[1]", "comp3", {}, nullptr));
  EXPECT_TRUE(
      manager_.AddComponent("comp1.comp2[1].comp3", "comp4", {}, nullptr));
  const char kExpected[] = R"({
    "comp1": {
      "traits": [],
      "components": {
        "comp2": [
          {
            "traits": ["foo"]
          },
          {
            "traits": ["bar"],
            "components": {
              "comp3": {
                "traits": [],
                "components": {
                  "comp4": {
                    "traits": []
                  }
                }
              }
            }
          }
        ]
      }
    }
  })";
  EXPECT_JSON_EQ(kExpected, manager_.GetComponents());
}

TEST_F(ComponentManagerTest, RemoveComponent) {
  CreateTestComponentTree(&manager_);
  EXPECT_TRUE(manager_.RemoveComponent("comp1.comp2[1].comp3", "comp4",
                                       nullptr));
  const char kExpected1[] = R"({
    "comp1": {
      "traits": [ "t1" ],
      "components": {
        "comp2": [
          {
            "traits": [ "t2" ]
          },
          {
            "traits": [ "t3" ],
            "components": {
              "comp3": {
                "traits": [ "t4" ],
                "components": {}
              }
            }
          }
        ]
      }
    }
  })";
  EXPECT_JSON_EQ(kExpected1, manager_.GetComponents());
  EXPECT_TRUE(manager_.RemoveComponentArrayItem("comp1", "comp2", 1, nullptr));
  const char kExpected2[] = R"({
    "comp1": {
      "traits": [ "t1" ],
      "components": {
        "comp2": [
          {
            "traits": [ "t2" ]
          }
        ]
      }
    }
  })";
  EXPECT_JSON_EQ(kExpected2, manager_.GetComponents());
}

TEST_F(ComponentManagerTest, AddComponentExist) {
  EXPECT_TRUE(manager_.AddComponent("", "comp1", {}, nullptr));
  EXPECT_FALSE(manager_.AddComponent("", "comp1", {}, nullptr));
  EXPECT_TRUE(manager_.AddComponent("comp1", "comp2", {}, nullptr));
  EXPECT_FALSE(manager_.AddComponent("comp1", "comp2", {}, nullptr));
}

TEST_F(ComponentManagerTest, AddComponentDoesNotExist) {
  EXPECT_FALSE(manager_.AddComponent("comp1", "comp2", {}, nullptr));
}

TEST_F(ComponentManagerTest, AddComponentTreeChangedCallback) {
  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);
  EXPECT_TRUE(manager_.RemoveComponentArrayItem("comp1", "comp3", 1, nullptr));
  EXPECT_EQ(7, count);
  EXPECT_TRUE(manager_.RemoveComponent("", "comp1", nullptr));
  EXPECT_EQ(8, count);
  // Make sure both callbacks were called the same number of times.
  EXPECT_EQ(count2, count);
}

TEST_F(ComponentManagerTest, FindComponent) {
  CreateTestComponentTree(&manager_);

  const base::DictionaryValue* comp = manager_.FindComponent("comp1", nullptr);
  ASSERT_NE(nullptr, comp);
  EXPECT_TRUE(HasTrait(*comp, "t1"));

  comp = manager_.FindComponent("comp1.comp2[0]", nullptr);
  ASSERT_NE(nullptr, comp);
  EXPECT_TRUE(HasTrait(*comp, "t2"));

  comp = manager_.FindComponent("comp1.comp2[1]", nullptr);
  ASSERT_NE(nullptr, comp);
  EXPECT_TRUE(HasTrait(*comp, "t3"));

  comp = manager_.FindComponent("comp1.comp2[1].comp3", nullptr);
  ASSERT_NE(nullptr, comp);
  EXPECT_TRUE(HasTrait(*comp, "t4"));

  comp = manager_.FindComponent("comp1.comp2[1].comp3.comp4", nullptr);
  ASSERT_NE(nullptr, comp);
  EXPECT_TRUE(HasTrait(*comp, "t5"));

  // Some whitespaces don't hurt.
  comp = manager_.FindComponent(" comp1 . comp2 [  \t 1 ] .   comp3.comp4 ",
                                nullptr);
  EXPECT_NE(nullptr, comp);

  // Now check some failure cases.
  ErrorPtr error;
  EXPECT_EQ(nullptr, manager_.FindComponent("", &error));
  EXPECT_NE(nullptr, error.get());
  // 'comp2' doesn't exist:
  EXPECT_EQ(nullptr, manager_.FindComponent("comp2", nullptr));
  // 'comp1.comp2' is an array, not a component:
  EXPECT_EQ(nullptr, manager_.FindComponent("comp1.comp2", nullptr));
  // 'comp1.comp2[3]' doesn't exist:
  EXPECT_EQ(nullptr, manager_.FindComponent("comp1.comp2[3]", nullptr));
  // Empty component names:
  EXPECT_EQ(nullptr, manager_.FindComponent(".comp2[1]", nullptr));
  EXPECT_EQ(nullptr, manager_.FindComponent("comp1.[1]", nullptr));
  // Invalid array indices:
  EXPECT_EQ(nullptr, manager_.FindComponent("comp1.comp2[s]", nullptr));
  EXPECT_EQ(nullptr, manager_.FindComponent("comp1.comp2[-2]", nullptr));
  EXPECT_EQ(nullptr, manager_.FindComponent("comp1.comp2[1e1]", nullptr));
  EXPECT_EQ(nullptr, manager_.FindComponent("comp1.comp2[1", nullptr));
}

TEST_F(ComponentManagerTest, ParseCommandInstance) {
  const char kTraits[] = R"({
    "trait1": {
      "commands": {
        "command1": { "minimalRole": "user" },
        "command2": { "minimalRole": "viewer" }
      }
    },
    "trait2": {
      "commands": {
        "command1": { "minimalRole": "manager" },
        "command2": { "minimalRole": "owner" }
      }
    },
    "trait3": {
      "commands": {
        "command1": { "minimalRole": "manager" },
        "command2": { "minimalRole": "owner" }
      }
    }
  })";
  auto traits = CreateDictionaryValue(kTraits);
  ASSERT_TRUE(manager_.LoadTraits(*traits, nullptr));
  ASSERT_TRUE(manager_.AddComponent("", "comp1", {"trait1"}, nullptr));
  ASSERT_TRUE(manager_.AddComponent("", "comp2", {"trait2"}, nullptr));

  std::string id;
  const char kCommand1[] = R"({
    "name": "trait1.command1",
    "id": "1234-12345",
    "component": "comp1",
    "parameters": {}
  })";
  auto command1 = CreateDictionaryValue(kCommand1);
  EXPECT_NE(nullptr,
            manager_.ParseCommandInstance(*command1, Command::Origin::kLocal,
                                          UserRole::kUser, &id, nullptr)
                .get());
  EXPECT_EQ("1234-12345", id);
  // Not enough access rights
  EXPECT_EQ(nullptr,
            manager_.ParseCommandInstance(*command1, Command::Origin::kLocal,
                                          UserRole::kViewer, &id, nullptr)
                .get());

  const char kCommand2[] = R"({
    "name": "trait1.command3",
    "component": "comp1",
    "parameters": {}
  })";
  auto command2 = CreateDictionaryValue(kCommand2);
  // trait1.command3 doesn't exist
  EXPECT_EQ(nullptr,
            manager_.ParseCommandInstance(*command2, Command::Origin::kLocal,
                                          UserRole::kOwner, &id, nullptr)
                .get());
  EXPECT_TRUE(id.empty());

  const char kCommand3[] = R"({
    "name": "trait2.command1",
    "component": "comp1",
    "parameters": {}
  })";
  auto command3 = CreateDictionaryValue(kCommand3);
  // Component comp1 doesn't have trait2.
  EXPECT_EQ(nullptr,
            manager_.ParseCommandInstance(*command3, Command::Origin::kLocal,
                                          UserRole::kOwner, &id, nullptr)
                .get());

  // No component specified, find the suitable component
  const char kCommand4[] = R"({
    "name": "trait1.command1",
    "parameters": {}
  })";
  auto command4 = CreateDictionaryValue(kCommand4);
  auto command_instance = manager_.ParseCommandInstance(
      *command4, Command::Origin::kLocal, UserRole::kOwner, &id, nullptr);
  EXPECT_NE(nullptr, command_instance.get());
  EXPECT_EQ("comp1", command_instance->GetComponent());

  const char kCommand5[] = R"({
    "name": "trait2.command1",
    "parameters": {}
  })";
  auto command5 = CreateDictionaryValue(kCommand5);
  command_instance = manager_.ParseCommandInstance(
      *command5, Command::Origin::kLocal, UserRole::kOwner, &id, nullptr);
  EXPECT_NE(nullptr, command_instance.get());
  EXPECT_EQ("comp2", command_instance->GetComponent());

  // Cannot route the command, no component with 'trait3'.
  const char kCommand6[] = R"({
    "name": "trait3.command1",
    "parameters": {}
  })";
  auto command6 = CreateDictionaryValue(kCommand6);
  EXPECT_EQ(nullptr,
            manager_.ParseCommandInstance(*command6, Command::Origin::kLocal,
                                          UserRole::kOwner, &id, nullptr)
                .get());
}

TEST_F(ComponentManagerTest, AddCommand) {
  const char kTraits[] = R"({
    "trait1": {
      "commands": {
        "command1": { "minimalRole": "user" }
      }
    }
  })";
  auto traits = CreateDictionaryValue(kTraits);
  ASSERT_TRUE(manager_.LoadTraits(*traits, nullptr));
  ASSERT_TRUE(manager_.AddComponent("", "comp1", {"trait1"}, nullptr));

  std::string id;
  const char kCommand[] = R"({
    "name": "trait1.command1",
    "id": "1234-12345",
    "component": "comp1",
    "parameters": {}
  })";
  auto command = CreateDictionaryValue(kCommand);
  auto command_instance = manager_.ParseCommandInstance(
      *command, Command::Origin::kLocal, UserRole::kUser, &id, nullptr);
  ASSERT_NE(nullptr, command_instance.get());
  manager_.AddCommand(std::move(command_instance));
  const auto* queued_command = manager_.FindCommand(id);
  ASSERT_NE(nullptr, queued_command);
  EXPECT_EQ("trait1.command1", queued_command->GetName());
}

TEST_F(ComponentManagerTest, AddCommandHandler) {
  const char kTraits[] = R"({
    "trait1": {
      "commands": {
        "command1": { "minimalRole": "user" }
      }
    },
    "trait2": {
      "commands": {
        "command2": { "minimalRole": "user" }
      }
    }
  })";
  auto traits = CreateDictionaryValue(kTraits);
  ASSERT_TRUE(manager_.LoadTraits(*traits, nullptr));
  ASSERT_TRUE(manager_.AddComponent("", "comp1", {"trait1"}, nullptr));
  ASSERT_TRUE(
      manager_.AddComponent("", "comp2", {"trait1", "trait2"}, nullptr));

  std::string last_tags;
  auto handler = [&last_tags](int tag, const std::weak_ptr<Command>& command) {
    if (!last_tags.empty())
      last_tags += ',';
    last_tags += std::to_string(tag);
  };

  manager_.AddCommandHandler("comp1", "trait1.command1",
                             base::Bind(handler, 1));
  manager_.AddCommandHandler("comp2", "trait1.command1",
                             base::Bind(handler, 2));
  manager_.AddCommandHandler("comp2", "trait2.command2",
                             base::Bind(handler, 3));
  EXPECT_TRUE(last_tags.empty());

  const char kCommand1[] = R"({
    "name": "trait1.command1",
    "component": "comp1"
  })";
  auto command1 = CreateDictionaryValue(kCommand1);
  auto command_instance = manager_.ParseCommandInstance(
      *command1, Command::Origin::kCloud, UserRole::kUser, nullptr, nullptr);
  ASSERT_NE(nullptr, command_instance.get());
  manager_.AddCommand(std::move(command_instance));
  EXPECT_EQ("1", last_tags);
  last_tags.clear();

  const char kCommand2[] = R"({
    "name": "trait1.command1",
    "component": "comp2"
  })";
  auto command2 = CreateDictionaryValue(kCommand2);
  command_instance = manager_.ParseCommandInstance(
      *command2, Command::Origin::kCloud, UserRole::kUser, nullptr, nullptr);
  ASSERT_NE(nullptr, command_instance.get());
  manager_.AddCommand(std::move(command_instance));
  EXPECT_EQ("2", last_tags);
  last_tags.clear();

  const char kCommand3[] = R"({
    "name": "trait2.command2",
    "component": "comp2",
    "parameters": {}
  })";
  auto command3 = CreateDictionaryValue(kCommand3);
  command_instance = manager_.ParseCommandInstance(
      *command3, Command::Origin::kCloud, UserRole::kUser, nullptr, nullptr);
  ASSERT_NE(nullptr, command_instance.get());
  manager_.AddCommand(std::move(command_instance));
  EXPECT_EQ("3", last_tags);
  last_tags.clear();
}

TEST_F(ComponentManagerTest, AddDefaultCommandHandler) {
  const char kTraits[] = R"({
    "trait1": {
      "commands": {
        "command1": { "minimalRole": "user" }
      }
    },
    "trait2": {
      "commands": {
        "command2": { "minimalRole": "user" }
      }
    }
  })";
  auto traits = CreateDictionaryValue(kTraits);
  ASSERT_TRUE(manager_.LoadTraits(*traits, nullptr));
  ASSERT_TRUE(manager_.AddComponent("", "comp", {"trait1", "trait2"}, nullptr));

  int count = 0;
  auto handler = [&count](int tag, const std::weak_ptr<Command>& command) {
    count++;
  };

  manager_.AddCommandHandler("", "", base::Bind(handler, 1));
  EXPECT_EQ(0, count);

  const char kCommand1[] = R"({
    "name": "trait1.command1",
    "component": "comp"
  })";
  auto command1 = CreateDictionaryValue(kCommand1);
  auto command_instance = manager_.ParseCommandInstance(
      *command1, Command::Origin::kCloud, UserRole::kUser, nullptr, nullptr);
  ASSERT_NE(nullptr, command_instance.get());
  manager_.AddCommand(std::move(command_instance));
  EXPECT_EQ(1, count);

  const char kCommand2[] = R"({
    "name": "trait2.command2",
    "component": "comp"
  })";
  auto command2 = CreateDictionaryValue(kCommand2);
  command_instance = manager_.ParseCommandInstance(
      *command2, Command::Origin::kCloud, UserRole::kUser, nullptr, nullptr);
  ASSERT_NE(nullptr, command_instance.get());
  manager_.AddCommand(std::move(command_instance));
  EXPECT_EQ(2, count);
}

TEST_F(ComponentManagerTest, SetStateProperties) {
  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_F(ComponentManagerTest, SetStatePropertiesFromJson) {
  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_F(ComponentManagerTest, SetGetStateProperty) {
  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_F(ComponentManagerTest, AddStateChangedCallback) {
  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(0u, 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(1u, manager_.GetLastStateChangeId());

  ASSERT_TRUE(manager_.SetStateProperty("comp1", "trait1.prop2", p1, nullptr));
  EXPECT_EQ(3, count);
  EXPECT_EQ(3, count2);
  EXPECT_EQ(2u, manager_.GetLastStateChangeId());

  // Fail - no component.
  ASSERT_FALSE(manager_.SetStateProperty("comp2", "trait1.prop2", p1, nullptr));
  EXPECT_EQ(3, count);
  EXPECT_EQ(3, count2);
  EXPECT_EQ(2u, manager_.GetLastStateChangeId());
}

TEST_F(ComponentManagerTest, ComponentStateUpdates) {
  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();
  EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(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);
  EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(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());
}

TEST_F(ComponentManagerTest, FindComponentWithTrait) {
  const char kTraits[] = R"({
    "trait1": {},
    "trait2": {},
    "trait3": {}
  })";
  auto traits = CreateDictionaryValue(kTraits);
  ASSERT_TRUE(manager_.LoadTraits(*traits, nullptr));
  ASSERT_TRUE(
      manager_.AddComponent("", "comp1", {"trait1", "trait2"}, nullptr));
  ASSERT_TRUE(manager_.AddComponent("", "comp2", {"trait3"}, nullptr));

  EXPECT_EQ("comp1", manager_.FindComponentWithTrait("trait1"));
  EXPECT_EQ("comp1", manager_.FindComponentWithTrait("trait2"));
  EXPECT_EQ("comp2", manager_.FindComponentWithTrait("trait3"));
  EXPECT_EQ("", manager_.FindComponentWithTrait("trait4"));
}

TEST_F(ComponentManagerTest, AddLegacyCommandAndStateDefinitions) {
  const char kCommandDefs1[] = R"({
    "package1": {
      "command1": {
        "minimalRole": "user",
        "parameters": {"height": {"type": "integer"}}
      },
      "command2": {
        "minimalRole": "owner",
        "parameters": {}
      }
    },
    "package2": {
      "command1": { "minimalRole": "user" },
      "command2": { "minimalRole": "owner" }
    }
  })";
  auto json = CreateDictionaryValue(kCommandDefs1);
  EXPECT_TRUE(manager_.AddLegacyCommandDefinitions(*json, nullptr));
  const char kExpected1[] = R"({
    "package1": {
      "commands": {
        "command1": {
          "minimalRole": "user",
          "parameters": {"height": {"type": "integer"}}
        },
        "command2": {
          "minimalRole": "owner",
          "parameters": {}
        }
      }
    },
    "package2": {
      "commands": {
        "command1": { "minimalRole": "user" },
        "command2": { "minimalRole": "owner" }
      }
    }
  })";
  EXPECT_JSON_EQ(kExpected1, manager_.GetTraits());
  const char kExpectedComponents1[] = R"({
    "__weave__": { "traits": ["package1", "package2"] }
  })";
  EXPECT_JSON_EQ(kExpectedComponents1, manager_.GetComponents());

  const char kCommandDefs2[] = R"({
    "package2": {
      "command3": { "minimalRole": "user" }
    },
    "package3": {
      "command1": { "minimalRole": "user" },
      "command2": { "minimalRole": "owner" }
    }
  })";
  json = CreateDictionaryValue(kCommandDefs2);
  EXPECT_TRUE(manager_.AddLegacyCommandDefinitions(*json, nullptr));
  const char kExpected2[] = R"({
    "package1": {
      "commands": {
        "command1": {
          "minimalRole": "user",
          "parameters": {"height": {"type": "integer"}}
        },
        "command2": {
          "minimalRole": "owner",
          "parameters": {}
        }
      }
    },
    "package2": {
      "commands": {
        "command1": { "minimalRole": "user" },
        "command2": { "minimalRole": "owner" },
        "command3": { "minimalRole": "user" }
      }
    },
    "package3": {
      "commands": {
        "command1": { "minimalRole": "user" },
        "command2": { "minimalRole": "owner" }
      }
    }
  })";
  EXPECT_JSON_EQ(kExpected2, manager_.GetTraits());
  const char kExpectedComponents2[] = R"({
    "__weave__": { "traits": ["package1", "package2", "package3"] }
  })";
  EXPECT_JSON_EQ(kExpectedComponents2, manager_.GetComponents());

  // Redefining existing commands.
  EXPECT_FALSE(manager_.AddLegacyCommandDefinitions(*json, nullptr));

  const char kStateDefs1[] = R"({
    "package1": {
      "prop1": { "type": "string" },
      "prop2": { "type": "string" }
    },
    "package4": {
      "prop3": { "type": "string" },
      "prop4": { "type": "string" }
    }
  })";
  json = CreateDictionaryValue(kStateDefs1);
  EXPECT_TRUE(manager_.AddLegacyStateDefinitions(*json, nullptr));
  const char kExpectedComponents3[] = R"({
    "__weave__": { "traits": ["package1", "package2", "package3", "package4"] }
  })";
  EXPECT_JSON_EQ(kExpectedComponents3, manager_.GetComponents());

  const char kExpected3[] = R"({
    "package1": {
      "commands": {
        "command1": {
          "minimalRole": "user",
          "parameters": {"height": {"type": "integer"}}
        },
        "command2": {
          "minimalRole": "owner",
          "parameters": {}
        }
      },
      "state": {
        "prop1": { "type": "string" },
        "prop2": { "type": "string" }
      }
    },
    "package2": {
      "commands": {
        "command1": { "minimalRole": "user" },
        "command2": { "minimalRole": "owner" },
        "command3": { "minimalRole": "user" }
      }
    },
    "package3": {
      "commands": {
        "command1": { "minimalRole": "user" },
        "command2": { "minimalRole": "owner" }
      }
    },
    "package4": {
      "state": {
        "prop3": { "type": "string" },
        "prop4": { "type": "string" }
      }
    }
  })";
  EXPECT_JSON_EQ(kExpected3, manager_.GetTraits());
  const char kExpectedComponents4[] = R"({
    "__weave__": { "traits": ["package1", "package2", "package3", "package4"] }
  })";
  EXPECT_JSON_EQ(kExpectedComponents4, manager_.GetComponents());

  // Redefining existing commands.
  EXPECT_FALSE(manager_.AddLegacyStateDefinitions(*json, nullptr));

  const char kExpected4[] = R"({
    "package1": {
      "command1": {
        "minimalRole": "user",
        "parameters": {"height": {"type": "integer"}}
      },
      "command2": {
        "minimalRole": "owner",
        "parameters": {}
      }
    },
    "package2": {
      "command1": { "minimalRole": "user" },
      "command2": { "minimalRole": "owner" },
      "command3": { "minimalRole": "user" }
    },
    "package3": {
      "command1": { "minimalRole": "user" },
      "command2": { "minimalRole": "owner" }
    }
  })";
  EXPECT_JSON_EQ(kExpected4, manager_.GetLegacyCommandDefinitions());
}

TEST_F(ComponentManagerTest, GetLegacyState) {
  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"}, nullptr));
  ASSERT_TRUE(manager_.AddComponent("", "comp2", {"trait2"}, nullptr));

  ASSERT_TRUE(manager_.SetStatePropertiesFromJson(
      "comp1", R"({"trait1": {"prop1": "foo", "prop2": "bar"}})", nullptr));
  ASSERT_TRUE(manager_.SetStatePropertiesFromJson(
      "comp2", R"({"trait2": {"prop3": "baz", "prop4": "quux"}})", nullptr));

  const char kExpected[] = R"({
    "trait1": {
      "prop1": "foo",
      "prop2": "bar"
    },
    "trait2": {
      "prop3": "baz",
      "prop4": "quux"
    }
  })";
  EXPECT_JSON_EQ(kExpected, manager_.GetLegacyState());
}

TEST_F(ComponentManagerTest, TestMockComponentManager) {
  // Check that all the virtual methods are mocked out.
  MockComponentManager mock;
}

}  // namespace weave