// 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