blob: 0c3404168dd946410d9ae8fb464c15f7e80e9b0e [file] [log] [blame]
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -08001// Copyright 2015 The Weave Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Alex Vakulenkoba981152015-12-05 13:58:22 -08005#include "src/component_manager_impl.h"
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -08006
7#include <map>
8
9#include <gtest/gtest.h>
10#include <weave/test/unittest_utils.h>
11
12#include "src/bind_lambda.h"
13#include "src/commands/schema_constants.h"
Alex Vakulenkoba981152015-12-05 13:58:22 -080014#include "src/mock_component_manager.h"
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -080015
16namespace weave {
17
18using test::CreateDictionaryValue;
19
20namespace {
21
22bool HasTrait(const base::DictionaryValue& comp, const std::string& trait) {
23 const base::ListValue* list = nullptr;
24 if (!comp.GetList("traits", &list))
25 return false;
26 for (const base::Value* item : *list) {
27 std::string value;
28 if (item->GetAsString(&value) && value == trait)
29 return true;
30 }
31 return false;
32}
33
Alex Vakulenko7b588fc2015-12-04 16:03:59 -080034// Creates sample trait/component trees:
Alex Vakulenkoa3c5e6d2015-12-04 17:54:38 -080035// {
36// "traits": {
37// "t1": {},
38// "t2": {},
39// "t3": {},
40// "t4": {},
41// "t5": {},
42// "t6": {},
43// },
44// "components": {
45// "comp1": {
46// "traits": [ "t1" ],
47// "components": {
48// "comp2": [
49// { "traits": [ "t2" ] },
50// {
51// "traits": [ "t3" ],
52// "components": {
53// "comp3": {
54// "traits": [ "t4" ],
55// "components": {
56// "comp4": {
57// "traits": [ "t5", "t6" ]
58// }
59// }
60// }
61// }
62// }
63// ],
64// }
65// }
66// }
Alex Vakulenko7b588fc2015-12-04 16:03:59 -080067// }
68void CreateTestComponentTree(ComponentManager* manager) {
69 const char kTraits[] = R"({"t1":{},"t2":{},"t3":{},"t4":{},"t5":{},"t6":{}})";
70 auto json = CreateDictionaryValue(kTraits);
71 ASSERT_TRUE(manager->LoadTraits(*json, nullptr));
72 EXPECT_TRUE(manager->AddComponent("", "comp1", {"t1"}, nullptr));
73 EXPECT_TRUE(manager->AddComponentArrayItem("comp1", "comp2", {"t2"},
74 nullptr));
75 EXPECT_TRUE(manager->AddComponentArrayItem("comp1", "comp2", {"t3"},
76 nullptr));
77 EXPECT_TRUE(manager->AddComponent("comp1.comp2[1]", "comp3", {"t4"},
78 nullptr));
79 EXPECT_TRUE(manager->AddComponent("comp1.comp2[1].comp3", "comp4",
80 {"t5", "t6"}, nullptr));
81}
82
83// Test clock class to record predefined time intervals.
84// Implementation from base/test/simple_test_clock.{h|cc}
85class SimpleTestClock : public base::Clock {
86 public:
87 base::Time Now() override { return now_; }
88
89 // Advances the clock by |delta|.
90 void Advance(base::TimeDelta delta) { now_ += delta; }
91
92 // Sets the clock to the given time.
93 void SetNow(base::Time now) { now_ = now; }
94
95 private:
96 base::Time now_;
97};
98
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -080099} // anonymous namespace
100
101TEST(ComponentManager, Empty) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800102 ComponentManagerImpl manager;
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800103 EXPECT_TRUE(manager.GetTraits().empty());
104 EXPECT_TRUE(manager.GetComponents().empty());
105}
106
107TEST(ComponentManager, LoadTraits) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800108 ComponentManagerImpl manager;
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800109 const char kTraits[] = R"({
110 "trait1": {
111 "commands": {
112 "command1": {
113 "minimalRole": "user",
114 "parameters": {"height": {"type": "integer"}}
115 }
116 },
117 "state": {
118 "property1": {"type": "boolean"}
119 }
120 },
121 "trait2": {
122 "state": {
123 "property2": {"type": "string"}
124 }
125 }
126 })";
127 auto json = CreateDictionaryValue(kTraits);
128 EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
129 EXPECT_JSON_EQ(kTraits, manager.GetTraits());
130 EXPECT_TRUE(manager.GetComponents().empty());
131}
132
133TEST(ComponentManager, LoadTraitsDuplicateIdentical) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800134 ComponentManagerImpl manager;
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800135 const char kTraits1[] = R"({
136 "trait1": {
137 "commands": {
138 "command1": {
139 "minimalRole": "user",
140 "parameters": {"height": {"type": "integer"}}
141 }
142 },
143 "state": {
144 "property1": {"type": "boolean"}
145 }
146 },
147 "trait2": {
148 "state": {
149 "property2": {"type": "string"}
150 }
151 }
152 })";
153 auto json = CreateDictionaryValue(kTraits1);
154 EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
155 const char kTraits2[] = R"({
156 "trait1": {
157 "commands": {
158 "command1": {
159 "minimalRole": "user",
160 "parameters": {"height": {"type": "integer"}}
161 }
162 },
163 "state": {
164 "property1": {"type": "boolean"}
165 }
166 },
167 "trait3": {
168 "state": {
169 "property3": {"type": "string"}
170 }
171 }
172 })";
173 json = CreateDictionaryValue(kTraits2);
174 EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
175 const char kExpected[] = R"({
176 "trait1": {
177 "commands": {
178 "command1": {
179 "minimalRole": "user",
180 "parameters": {"height": {"type": "integer"}}
181 }
182 },
183 "state": {
184 "property1": {"type": "boolean"}
185 }
186 },
187 "trait2": {
188 "state": {
189 "property2": {"type": "string"}
190 }
191 },
192 "trait3": {
193 "state": {
194 "property3": {"type": "string"}
195 }
196 }
197 })";
198 EXPECT_JSON_EQ(kExpected, manager.GetTraits());
199}
200
201TEST(ComponentManager, LoadTraitsDuplicateOverride) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800202 ComponentManagerImpl manager;
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800203 const char kTraits1[] = R"({
204 "trait1": {
205 "commands": {
206 "command1": {
207 "minimalRole": "user",
208 "parameters": {"height": {"type": "integer"}}
209 }
210 },
211 "state": {
212 "property1": {"type": "boolean"}
213 }
214 },
215 "trait2": {
216 "state": {
217 "property2": {"type": "string"}
218 }
219 }
220 })";
221 auto json = CreateDictionaryValue(kTraits1);
222 EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
223 const char kTraits2[] = R"({
224 "trait1": {
225 "commands": {
226 "command1": {
227 "minimalRole": "user",
228 "parameters": {"height": {"type": "string"}}
229 }
230 },
231 "state": {
232 "property1": {"type": "boolean"}
233 }
234 },
235 "trait3": {
236 "state": {
237 "property3": {"type": "string"}
238 }
239 }
240 })";
241 json = CreateDictionaryValue(kTraits2);
242 EXPECT_FALSE(manager.LoadTraits(*json, nullptr));
243}
244
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800245TEST(ComponentManager, AddTraitDefChangedCallback) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800246 ComponentManagerImpl manager;
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800247 int count = 0;
248 int count2 = 0;
249 manager.AddTraitDefChangedCallback(base::Bind([&count]() { count++; }));
250 manager.AddTraitDefChangedCallback(base::Bind([&count2]() { count2++; }));
251 EXPECT_EQ(1, count);
252 EXPECT_EQ(1, count2);
253 // New definitions.
254 const char kTraits1[] = R"({
255 "trait1": {
256 "state": {
257 "property1": {"type": "boolean"}
258 }
259 },
260 "trait2": {
261 "state": {
262 "property2": {"type": "string"}
263 }
264 }
265 })";
266 auto json = CreateDictionaryValue(kTraits1);
267 EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
268 EXPECT_EQ(2, count);
269 // Duplicate definition, shouldn't call the callback.
270 const char kTraits2[] = R"({
271 "trait1": {
272 "state": {
273 "property1": {"type": "boolean"}
274 }
275 }
276 })";
277 json = CreateDictionaryValue(kTraits2);
278 EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
279 EXPECT_EQ(2, count);
280 // New definition, should call the callback now.
281 const char kTraits3[] = R"({
282 "trait3": {
283 "state": {
284 "property3": {"type": "string"}
285 }
286 }
287 })";
288 json = CreateDictionaryValue(kTraits3);
289 EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
290 EXPECT_EQ(3, count);
291 // Wrong definition, shouldn't call the callback.
292 const char kTraits4[] = R"({
293 "trait4": "foo"
294 })";
295 json = CreateDictionaryValue(kTraits4);
296 EXPECT_FALSE(manager.LoadTraits(*json, nullptr));
297 EXPECT_EQ(3, count);
298 // Make sure both callbacks were called the same number of times.
299 EXPECT_EQ(count2, count);
300}
301
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800302TEST(ComponentManager, LoadTraitsNotAnObject) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800303 ComponentManagerImpl manager;
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800304 const char kTraits1[] = R"({"trait1": 0})";
305 auto json = CreateDictionaryValue(kTraits1);
306 ErrorPtr error;
307 EXPECT_FALSE(manager.LoadTraits(*json, &error));
308 EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
309}
310
311TEST(ComponentManager, FindTraitDefinition) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800312 ComponentManagerImpl manager;
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800313 const char kTraits[] = R"({
314 "trait1": {
315 "commands": {
316 "command1": {
317 "minimalRole": "user",
318 "parameters": {"height": {"type": "integer"}}
319 }
320 },
321 "state": {
322 "property1": {"type": "boolean"}
323 }
324 },
325 "trait2": {
326 "state": {
327 "property2": {"type": "string"}
328 }
329 }
330 })";
331 auto json = CreateDictionaryValue(kTraits);
332 ASSERT_TRUE(manager.LoadTraits(*json, nullptr));
333
334 const base::DictionaryValue* trait = manager.FindTraitDefinition("trait1");
335 ASSERT_NE(nullptr, trait);
336 const char kExpected1[] = R"({
337 "commands": {
338 "command1": {
339 "minimalRole": "user",
340 "parameters": {"height": {"type": "integer"}}
341 }
342 },
343 "state": {
344 "property1": {"type": "boolean"}
345 }
346 })";
347 EXPECT_JSON_EQ(kExpected1, *trait);
348
349 trait = manager.FindTraitDefinition("trait2");
350 ASSERT_NE(nullptr, trait);
351 const char kExpected2[] = R"({
352 "state": {
353 "property2": {"type": "string"}
354 }
355 })";
356 EXPECT_JSON_EQ(kExpected2, *trait);
357
358 EXPECT_EQ(nullptr, manager.FindTraitDefinition("trait3"));
359}
360
361TEST(ComponentManager, FindCommandDefinition) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800362 ComponentManagerImpl manager;
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800363 const char kTraits[] = R"({
364 "trait1": {
365 "commands": {
366 "command1": {
367 "minimalRole": "user",
368 "parameters": {"height": {"type": "integer"}}
369 }
370 }
371 },
372 "trait2": {
373 "commands": {
374 "command1": {
375 "minimalRole": "manager"
376 },
377 "command2": {
378 "minimalRole": "owner"
379 }
380 }
381 }
382 })";
383 auto json = CreateDictionaryValue(kTraits);
384 ASSERT_TRUE(manager.LoadTraits(*json, nullptr));
385
386 const auto* cmd_def = manager.FindCommandDefinition("trait1.command1");
387 ASSERT_NE(nullptr, cmd_def);
388 const char kExpected1[] = R"({
389 "minimalRole": "user",
390 "parameters": {"height": {"type": "integer"}}
391 })";
392 EXPECT_JSON_EQ(kExpected1, *cmd_def);
393
394 cmd_def = manager.FindCommandDefinition("trait2.command1");
395 ASSERT_NE(nullptr, cmd_def);
396 const char kExpected2[] = R"({
397 "minimalRole": "manager"
398 })";
399 EXPECT_JSON_EQ(kExpected2, *cmd_def);
400
401 cmd_def = manager.FindCommandDefinition("trait2.command2");
402 ASSERT_NE(nullptr, cmd_def);
403 const char kExpected3[] = R"({
404 "minimalRole": "owner"
405 })";
406 EXPECT_JSON_EQ(kExpected3, *cmd_def);
407
408 EXPECT_EQ(nullptr, manager.FindTraitDefinition("trait1.command2"));
409 EXPECT_EQ(nullptr, manager.FindTraitDefinition("trait3.command1"));
410 EXPECT_EQ(nullptr, manager.FindTraitDefinition("trait"));
411 EXPECT_EQ(nullptr, manager.FindTraitDefinition("trait1.command1.parameters"));
412}
413
414TEST(ComponentManager, GetMinimalRole) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800415 ComponentManagerImpl manager;
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800416 const char kTraits[] = R"({
417 "trait1": {
418 "commands": {
419 "command1": { "minimalRole": "user" },
420 "command2": { "minimalRole": "viewer" }
421 }
422 },
423 "trait2": {
424 "commands": {
425 "command1": { "minimalRole": "manager" },
426 "command2": { "minimalRole": "owner" }
427 }
428 }
429 })";
430 auto json = CreateDictionaryValue(kTraits);
431 ASSERT_TRUE(manager.LoadTraits(*json, nullptr));
432
433 UserRole role;
434 ASSERT_TRUE(manager.GetMinimalRole("trait1.command1", &role, nullptr));
435 EXPECT_EQ(UserRole::kUser, role);
436
437 ASSERT_TRUE(manager.GetMinimalRole("trait1.command2", &role, nullptr));
438 EXPECT_EQ(UserRole::kViewer, role);
439
440 ASSERT_TRUE(manager.GetMinimalRole("trait2.command1", &role, nullptr));
441 EXPECT_EQ(UserRole::kManager, role);
442
443 ASSERT_TRUE(manager.GetMinimalRole("trait2.command2", &role, nullptr));
444 EXPECT_EQ(UserRole::kOwner, role);
445
446 EXPECT_FALSE(manager.GetMinimalRole("trait1.command3", &role, nullptr));
447}
448
449TEST(ComponentManager, AddComponent) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800450 ComponentManagerImpl manager;
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800451 const char kTraits[] = R"({"trait1": {}, "trait2": {}, "trait3": {}})";
452 auto json = CreateDictionaryValue(kTraits);
453 ASSERT_TRUE(manager.LoadTraits(*json, nullptr));
454 EXPECT_TRUE(manager.AddComponent("", "comp1", {"trait1", "trait2"}, nullptr));
455 EXPECT_TRUE(manager.AddComponent("", "comp2", {"trait3"}, nullptr));
456 const char kExpected[] = R"({
457 "comp1": {
458 "traits": ["trait1", "trait2"]
459 },
460 "comp2": {
461 "traits": ["trait3"]
462 }
463 })";
464 EXPECT_JSON_EQ(kExpected, manager.GetComponents());
465
466 // 'trait4' is undefined, so can't add a component referring to it.
467 EXPECT_FALSE(manager.AddComponent("", "comp3", {"trait4"}, nullptr));
468}
469
470TEST(ComponentManager, AddSubComponent) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800471 ComponentManagerImpl manager;
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800472 EXPECT_TRUE(manager.AddComponent("", "comp1", {}, nullptr));
473 EXPECT_TRUE(manager.AddComponent("comp1", "comp2", {}, nullptr));
474 EXPECT_TRUE(manager.AddComponent("comp1", "comp3", {}, nullptr));
475 EXPECT_TRUE(manager.AddComponent("comp1.comp2", "comp4", {}, nullptr));
476 const char kExpected[] = R"({
477 "comp1": {
478 "traits": [],
479 "components": {
480 "comp2": {
481 "traits": [],
482 "components": {
483 "comp4": {
484 "traits": []
485 }
486 }
487 },
488 "comp3": {
489 "traits": []
490 }
491 }
492 }
493 })";
494 EXPECT_JSON_EQ(kExpected, manager.GetComponents());
495}
496
497TEST(ComponentManager, AddComponentArrayItem) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800498 ComponentManagerImpl manager;
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800499 const char kTraits[] = R"({"foo": {}, "bar": {}})";
500 auto json = CreateDictionaryValue(kTraits);
501 ASSERT_TRUE(manager.LoadTraits(*json, nullptr));
502
503 EXPECT_TRUE(manager.AddComponent("", "comp1", {}, nullptr));
504 EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp2", {"foo"},
505 nullptr));
506 EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp2", {"bar"},
507 nullptr));
508 EXPECT_TRUE(manager.AddComponent("comp1.comp2[1]", "comp3", {}, nullptr));
509 EXPECT_TRUE(manager.AddComponent("comp1.comp2[1].comp3", "comp4", {},
510 nullptr));
511 const char kExpected[] = R"({
512 "comp1": {
513 "traits": [],
514 "components": {
515 "comp2": [
516 {
517 "traits": ["foo"]
518 },
519 {
520 "traits": ["bar"],
521 "components": {
522 "comp3": {
523 "traits": [],
524 "components": {
525 "comp4": {
526 "traits": []
527 }
528 }
529 }
530 }
531 }
532 ]
533 }
534 }
535 })";
536 EXPECT_JSON_EQ(kExpected, manager.GetComponents());
537}
538
539TEST(ComponentManager, AddComponentExist) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800540 ComponentManagerImpl manager;
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800541 EXPECT_TRUE(manager.AddComponent("", "comp1", {}, nullptr));
542 EXPECT_FALSE(manager.AddComponent("", "comp1", {}, nullptr));
543 EXPECT_TRUE(manager.AddComponent("comp1", "comp2", {}, nullptr));
544 EXPECT_FALSE(manager.AddComponent("comp1", "comp2", {}, nullptr));
545}
546
547TEST(ComponentManager, AddComponentDoesNotExist) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800548 ComponentManagerImpl manager;
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800549 EXPECT_FALSE(manager.AddComponent("comp1", "comp2", {}, nullptr));
550}
551
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800552TEST(ComponentManager, AddComponentTreeChangedCallback) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800553 ComponentManagerImpl manager;
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800554 int count = 0;
555 int count2 = 0;
556 manager.AddComponentTreeChangedCallback(base::Bind([&count]() { count++; }));
557 manager.AddComponentTreeChangedCallback(
558 base::Bind([&count2]() { count2++; }));
559 EXPECT_EQ(1, count);
560 EXPECT_EQ(1, count2);
561 EXPECT_TRUE(manager.AddComponent("", "comp1", {}, nullptr));
562 EXPECT_EQ(2, count);
563 EXPECT_TRUE(manager.AddComponent("comp1", "comp2", {}, nullptr));
564 EXPECT_EQ(3, count);
565 EXPECT_TRUE(manager.AddComponent("comp1.comp2", "comp4", {}, nullptr));
566 EXPECT_EQ(4, count);
567 EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp3", {}, nullptr));
568 EXPECT_EQ(5, count);
569 EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp3", {}, nullptr));
570 EXPECT_EQ(6, count);
571 // Make sure both callbacks were called the same number of times.
572 EXPECT_EQ(count2, count);
573}
574
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800575TEST(ComponentManager, FindComponent) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800576 ComponentManagerImpl manager;
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800577 CreateTestComponentTree(&manager);
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800578
579 const base::DictionaryValue* comp = manager.FindComponent("comp1", nullptr);
580 ASSERT_NE(nullptr, comp);
581 EXPECT_TRUE(HasTrait(*comp, "t1"));
582
583 comp = manager.FindComponent("comp1.comp2[0]", nullptr);
584 ASSERT_NE(nullptr, comp);
585 EXPECT_TRUE(HasTrait(*comp, "t2"));
586
587 comp = manager.FindComponent("comp1.comp2[1]", nullptr);
588 ASSERT_NE(nullptr, comp);
589 EXPECT_TRUE(HasTrait(*comp, "t3"));
590
591 comp = manager.FindComponent("comp1.comp2[1].comp3", nullptr);
592 ASSERT_NE(nullptr, comp);
593 EXPECT_TRUE(HasTrait(*comp, "t4"));
594
595 comp = manager.FindComponent("comp1.comp2[1].comp3.comp4", nullptr);
596 ASSERT_NE(nullptr, comp);
597 EXPECT_TRUE(HasTrait(*comp, "t5"));
598
599 // Some whitespaces don't hurt.
600 comp = manager.FindComponent(" comp1 . comp2 [ \t 1 ] . comp3.comp4 ",
601 nullptr);
602 EXPECT_NE(nullptr, comp);
603
604 // Now check some failure cases.
605 ErrorPtr error;
606 EXPECT_EQ(nullptr, manager.FindComponent("", &error));
607 EXPECT_NE(nullptr, error.get());
608 // 'comp2' doesn't exist:
609 EXPECT_EQ(nullptr, manager.FindComponent("comp2", nullptr));
610 // 'comp1.comp2' is an array, not a component:
611 EXPECT_EQ(nullptr, manager.FindComponent("comp1.comp2", nullptr));
612 // 'comp1.comp2[3]' doesn't exist:
613 EXPECT_EQ(nullptr, manager.FindComponent("comp1.comp2[3]", nullptr));
614 // Empty component names:
615 EXPECT_EQ(nullptr, manager.FindComponent(".comp2[1]", nullptr));
616 EXPECT_EQ(nullptr, manager.FindComponent("comp1.[1]", nullptr));
617 // Invalid array indices:
618 EXPECT_EQ(nullptr, manager.FindComponent("comp1.comp2[s]", nullptr));
619 EXPECT_EQ(nullptr, manager.FindComponent("comp1.comp2[-2]", nullptr));
620 EXPECT_EQ(nullptr, manager.FindComponent("comp1.comp2[1e1]", nullptr));
621 EXPECT_EQ(nullptr, manager.FindComponent("comp1.comp2[1", nullptr));
622}
623
624TEST(ComponentManager, AddCommand) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800625 ComponentManagerImpl manager;
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800626 const char kTraits[] = R"({
627 "trait1": {
628 "commands": {
629 "command1": { "minimalRole": "user" },
630 "command2": { "minimalRole": "viewer" }
631 }
632 },
633 "trait2": {
634 "commands": {
635 "command1": { "minimalRole": "manager" },
636 "command2": { "minimalRole": "owner" }
637 }
638 }
639 })";
640 auto traits = CreateDictionaryValue(kTraits);
641 ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
642 ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1"}, nullptr));
643 ASSERT_TRUE(manager.AddComponent("", "comp2", {"trait2"}, nullptr));
644
645 std::string id;
646 const char kCommand1[] = R"({
647 "name": "trait1.command1",
648 "component": "comp1",
649 "parameters": {}
650 })";
651 auto command1 = CreateDictionaryValue(kCommand1);
652 EXPECT_TRUE(manager.AddCommand(*command1, UserRole::kUser, &id, nullptr));
653 // Not enough access rights
654 EXPECT_FALSE(manager.AddCommand(*command1, UserRole::kViewer, &id, nullptr));
655
656 const char kCommand2[] = R"({
657 "name": "trait1.command3",
658 "component": "comp1",
659 "parameters": {}
660 })";
661 auto command2 = CreateDictionaryValue(kCommand2);
662 // trait1.command3 doesn't exist
663 EXPECT_FALSE(manager.AddCommand(*command2, UserRole::kOwner, &id, nullptr));
664
665 const char kCommand3[] = R"({
666 "name": "trait2.command1",
667 "component": "comp1",
668 "parameters": {}
669 })";
670 auto command3 = CreateDictionaryValue(kCommand3);
671 // Component comp1 doesn't have trait2.
672 EXPECT_FALSE(manager.AddCommand(*command3, UserRole::kOwner, &id, nullptr));
673
Alex Vakulenkoa3c5e6d2015-12-04 17:54:38 -0800674 // No component specified, find the suitable component
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800675 const char kCommand4[] = R"({
676 "name": "trait1.command1",
677 "parameters": {}
678 })";
679 auto command4 = CreateDictionaryValue(kCommand4);
680 EXPECT_TRUE(manager.AddCommand(*command4, UserRole::kOwner, &id, nullptr));
681 auto cmd = manager.FindCommand(id);
682 ASSERT_NE(nullptr, cmd);
683 EXPECT_EQ("comp1", cmd->GetComponent());
Alex Vakulenkoa3c5e6d2015-12-04 17:54:38 -0800684
685 const char kCommand5[] = R"({
686 "name": "trait2.command1",
687 "parameters": {}
688 })";
689 auto command5 = CreateDictionaryValue(kCommand5);
690 EXPECT_TRUE(manager.AddCommand(*command5, UserRole::kOwner, &id, nullptr));
691 cmd = manager.FindCommand(id);
692 ASSERT_NE(nullptr, cmd);
693 EXPECT_EQ("comp2", cmd->GetComponent());
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800694}
695
696TEST(ComponentManager, AddCommandHandler) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800697 ComponentManagerImpl manager;
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -0800698 const char kTraits[] = R"({
699 "trait1": {
700 "commands": {
701 "command1": { "minimalRole": "user" }
702 }
703 },
704 "trait2": {
705 "commands": {
706 "command2": { "minimalRole": "user" }
707 }
708 }
709 })";
710 auto traits = CreateDictionaryValue(kTraits);
711 ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
712 ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1"}, nullptr));
713 ASSERT_TRUE(manager.AddComponent("", "comp2", {"trait1", "trait2"}, nullptr));
714
715 std::string last_tags;
716 auto handler = [&last_tags](int tag, const std::weak_ptr<Command>& command) {
717 if (!last_tags.empty())
718 last_tags += ',';
719 last_tags += std::to_string(tag);
720 };
721
722 manager.AddCommandHandler("comp1", "trait1.command1", base::Bind(handler, 1));
723 manager.AddCommandHandler("comp2", "trait1.command1", base::Bind(handler, 2));
724 manager.AddCommandHandler("comp2", "trait2.command2", base::Bind(handler, 3));
725 EXPECT_TRUE(last_tags.empty());
726
727 const char kCommand1[] = R"({
728 "name": "trait1.command1",
729 "component": "comp1"
730 })";
731 auto command1 = CreateDictionaryValue(kCommand1);
732 EXPECT_TRUE(manager.AddCommand(*command1, UserRole::kUser, nullptr, nullptr));
733 EXPECT_EQ("1", last_tags);
734 last_tags.clear();
735
736 const char kCommand2[] = R"({
737 "name": "trait1.command1",
738 "component": "comp2"
739 })";
740 auto command2 = CreateDictionaryValue(kCommand2);
741 EXPECT_TRUE(manager.AddCommand(*command2, UserRole::kUser, nullptr, nullptr));
742 EXPECT_EQ("2", last_tags);
743 last_tags.clear();
744
745 const char kCommand3[] = R"({
746 "name": "trait2.command2",
747 "component": "comp2",
748 "parameters": {}
749 })";
750 auto command3 = CreateDictionaryValue(kCommand3);
751 EXPECT_TRUE(manager.AddCommand(*command3, UserRole::kUser, nullptr, nullptr));
752 EXPECT_EQ("3", last_tags);
753 last_tags.clear();
754}
755
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800756TEST(ComponentManager, SetStateProperties) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800757 ComponentManagerImpl manager;
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800758 CreateTestComponentTree(&manager);
759
760 const char kState1[] = R"({"t1": {"p1": 0, "p2": "foo"}})";
761 auto state1 = CreateDictionaryValue(kState1);
762 ASSERT_TRUE(manager.SetStateProperties("comp1", *state1, nullptr));
763 const char kExpected1[] = R"({
Alex Vakulenkoa3c5e6d2015-12-04 17:54:38 -0800764 "comp1": {
765 "traits": [ "t1" ],
766 "state": {"t1": {"p1": 0, "p2": "foo"}},
767 "components": {
768 "comp2": [
769 {
770 "traits": [ "t2" ]
771 },
772 {
773 "traits": [ "t3" ],
774 "components": {
775 "comp3": {
776 "traits": [ "t4" ],
777 "components": {
778 "comp4": {
779 "traits": [ "t5", "t6" ]
780 }
781 }
782 }
783 }
784 }
785 ]
786 }
787 }
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800788 })";
789 EXPECT_JSON_EQ(kExpected1, manager.GetComponents());
790
791 const char kState2[] = R"({"t1": {"p1": {"bar": "baz"}}})";
792 auto state2 = CreateDictionaryValue(kState2);
793 ASSERT_TRUE(manager.SetStateProperties("comp1", *state2, nullptr));
794
795 const char kExpected2[] = R"({
Alex Vakulenkoa3c5e6d2015-12-04 17:54:38 -0800796 "comp1": {
797 "traits": [ "t1" ],
798 "state": {"t1": {"p1": {"bar": "baz"}, "p2": "foo"}},
799 "components": {
800 "comp2": [
801 {
802 "traits": [ "t2" ]
803 },
804 {
805 "traits": [ "t3" ],
806 "components": {
807 "comp3": {
808 "traits": [ "t4" ],
809 "components": {
810 "comp4": {
811 "traits": [ "t5", "t6" ]
812 }
813 }
814 }
815 }
816 }
817 ]
818 }
819 }
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800820 })";
821 EXPECT_JSON_EQ(kExpected2, manager.GetComponents());
822
823 const char kState3[] = R"({"t5": {"p1": 1}})";
824 auto state3 = CreateDictionaryValue(kState3);
825 ASSERT_TRUE(manager.SetStateProperties("comp1.comp2[1].comp3.comp4", *state3,
826 nullptr));
827
828 const char kExpected3[] = R"({
Alex Vakulenkoa3c5e6d2015-12-04 17:54:38 -0800829 "comp1": {
830 "traits": [ "t1" ],
831 "state": {"t1": {"p1": {"bar": "baz"}, "p2": "foo"}},
832 "components": {
833 "comp2": [
834 {
835 "traits": [ "t2" ]
836 },
837 {
838 "traits": [ "t3" ],
839 "components": {
840 "comp3": {
841 "traits": [ "t4" ],
842 "components": {
843 "comp4": {
844 "traits": [ "t5", "t6" ],
845 "state": { "t5": { "p1": 1 } }
846 }
847 }
848 }
849 }
850 }
851 ]
852 }
853 }
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800854 })";
855 EXPECT_JSON_EQ(kExpected3, manager.GetComponents());
856}
857
858TEST(ComponentManager, SetStatePropertiesFromJson) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800859 ComponentManagerImpl manager;
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800860 CreateTestComponentTree(&manager);
861
862 ASSERT_TRUE(manager.SetStatePropertiesFromJson(
863 "comp1.comp2[1].comp3.comp4", R"({"t5": {"p1": 3}, "t6": {"p2": 5}})",
864 nullptr));
865
866 const char kExpected[] = R"({
Alex Vakulenkoa3c5e6d2015-12-04 17:54:38 -0800867 "comp1": {
868 "traits": [ "t1" ],
869 "components": {
870 "comp2": [
871 {
872 "traits": [ "t2" ]
873 },
874 {
875 "traits": [ "t3" ],
876 "components": {
877 "comp3": {
878 "traits": [ "t4" ],
879 "components": {
880 "comp4": {
881 "traits": [ "t5", "t6" ],
882 "state": {
883 "t5": { "p1": 3 },
884 "t6": { "p2": 5 }
885 }
886 }
887 }
888 }
889 }
890 }
891 ]
892 }
893 }
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800894 })";
895 EXPECT_JSON_EQ(kExpected, manager.GetComponents());
896}
897
898TEST(ComponentManager, SetGetStateProperty) {
Alex Vakulenkoba981152015-12-05 13:58:22 -0800899 ComponentManagerImpl manager;
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800900 const char kTraits[] = R"({
901 "trait1": {
902 "state": {
903 "prop1": { "type": "string" },
904 "prop2": { "type": "integer" }
905 }
906 },
907 "trait2": {
908 "state": {
909 "prop3": { "type": "string" },
910 "prop4": { "type": "string" }
911 }
912 }
913 })";
914 auto traits = CreateDictionaryValue(kTraits);
915 ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
916 ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1", "trait2"}, nullptr));
917
918 base::StringValue p1("foo");
919 ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop1", p1, nullptr));
920
921 const char kExpected1[] = R"({
Alex Vakulenkoa3c5e6d2015-12-04 17:54:38 -0800922 "comp1": {
923 "traits": [ "trait1", "trait2" ],
924 "state": {
925 "trait1": { "prop1": "foo" }
926 }
927 }
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800928 })";
929 EXPECT_JSON_EQ(kExpected1, manager.GetComponents());
930
931 base::FundamentalValue p2(2);
932 ASSERT_TRUE(manager.SetStateProperty("comp1", "trait2.prop3", p2, nullptr));
933
934 const char kExpected2[] = R"({
Alex Vakulenkoa3c5e6d2015-12-04 17:54:38 -0800935 "comp1": {
936 "traits": [ "trait1", "trait2" ],
937 "state": {
938 "trait1": { "prop1": "foo" },
939 "trait2": { "prop3": 2 }
940 }
941 }
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800942 })";
943 EXPECT_JSON_EQ(kExpected2, manager.GetComponents());
944 // Just the package name without property:
945 EXPECT_FALSE(manager.SetStateProperty("comp1", "trait2", p2, nullptr));
946
947 const base::Value* value = manager.GetStateProperty("comp1", "trait1.prop1",
948 nullptr);
949 ASSERT_NE(nullptr, value);
950 EXPECT_TRUE(p1.Equals(value));
951 value = manager.GetStateProperty("comp1", "trait2.prop3", nullptr);
952 ASSERT_NE(nullptr, value);
953 EXPECT_TRUE(p2.Equals(value));
954
955 // Non-existing property:
956 EXPECT_EQ(nullptr, manager.GetStateProperty("comp1", "trait2.p", nullptr));
957 // Non-existing component
958 EXPECT_EQ(nullptr, manager.GetStateProperty("comp2", "trait.prop", nullptr));
959 // Just the package name without property:
960 EXPECT_EQ(nullptr, manager.GetStateProperty("comp1", "trait2", nullptr));
961}
962
963TEST(ComponentManager, AddStateChangedCallback) {
964 SimpleTestClock clock;
Alex Vakulenkoba981152015-12-05 13:58:22 -0800965 ComponentManagerImpl manager{&clock};
Alex Vakulenko7b588fc2015-12-04 16:03:59 -0800966 const char kTraits[] = R"({
967 "trait1": {
968 "state": {
969 "prop1": { "type": "string" },
970 "prop2": { "type": "string" }
971 }
972 }
973 })";
974 auto traits = CreateDictionaryValue(kTraits);
975 ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
976 ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1"}, nullptr));
977
978 int count = 0;
979 int count2 = 0;
980 manager.AddStateChangedCallback(base::Bind([&count]() { count++; }));
981 manager.AddStateChangedCallback(base::Bind([&count2]() { count2++; }));
982 EXPECT_EQ(1, count);
983 EXPECT_EQ(1, count2);
984 EXPECT_EQ(0, manager.GetLastStateChangeId());
985
986 base::StringValue p1("foo");
987 ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop1", p1, nullptr));
988 EXPECT_EQ(2, count);
989 EXPECT_EQ(2, count2);
990 EXPECT_EQ(1, manager.GetLastStateChangeId());
991
992 ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop2", p1, nullptr));
993 EXPECT_EQ(3, count);
994 EXPECT_EQ(3, count2);
995 EXPECT_EQ(2, manager.GetLastStateChangeId());
996
997 // Fail - no component.
998 ASSERT_FALSE(manager.SetStateProperty("comp2", "trait1.prop2", p1, nullptr));
999 EXPECT_EQ(3, count);
1000 EXPECT_EQ(3, count2);
1001 EXPECT_EQ(2, manager.GetLastStateChangeId());
1002}
1003
1004TEST(ComponentManager, ComponentStateUpdates) {
1005 SimpleTestClock clock;
Alex Vakulenkoba981152015-12-05 13:58:22 -08001006 ComponentManagerImpl manager{&clock};
Alex Vakulenko7b588fc2015-12-04 16:03:59 -08001007 const char kTraits[] = R"({
1008 "trait1": {
1009 "state": {
1010 "prop1": { "type": "string" },
1011 "prop2": { "type": "string" }
1012 }
1013 },
1014 "trait2": {
1015 "state": {
1016 "prop3": { "type": "string" },
1017 "prop4": { "type": "string" }
1018 }
1019 }
1020 })";
1021 auto traits = CreateDictionaryValue(kTraits);
1022 ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
1023 ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1", "trait2"}, nullptr));
1024 ASSERT_TRUE(manager.AddComponent("", "comp2", {"trait1", "trait2"}, nullptr));
1025
1026 std::vector<ComponentManager::UpdateID> updates1;
1027 auto callback1 = [&updates1](ComponentManager::UpdateID id) {
1028 updates1.push_back(id);
1029 };
1030 // State change queue is empty, callback should be called immediately.
1031 auto token1 = manager.AddServerStateUpdatedCallback(base::Bind(callback1));
1032 ASSERT_EQ(1u, updates1.size());
1033 EXPECT_EQ(manager.GetLastStateChangeId(), updates1.front());
1034 updates1.clear();
1035
1036 base::StringValue foo("foo");
1037 base::Time time1 = base::Time::Now();
1038 clock.SetNow(time1);
1039 // These three updates should be grouped into two separate state change queue
1040 // items, since they all happen at the same time, but for two different
1041 // components.
1042 ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop1", foo, nullptr));
1043 ASSERT_TRUE(manager.SetStateProperty("comp2", "trait2.prop3", foo, nullptr));
1044 ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop2", foo, nullptr));
1045
1046 std::vector<ComponentManager::UpdateID> updates2;
1047 auto callback2 = [&updates2](ComponentManager::UpdateID id) {
1048 updates2.push_back(id);
1049 };
1050 // State change queue is not empty, so callback will be called later.
1051 auto token2 = manager.AddServerStateUpdatedCallback(base::Bind(callback2));
1052 EXPECT_TRUE(updates2.empty());
1053
1054 base::StringValue bar("bar");
1055 base::Time time2 = time1 + base::TimeDelta::FromSeconds(1);
1056 clock.SetNow(time2);
1057 // Two more update events (as above) but at |time2|.
1058 ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop1", bar, nullptr));
1059 ASSERT_TRUE(manager.SetStateProperty("comp2", "trait2.prop3", bar, nullptr));
1060 ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop2", bar, nullptr));
1061
1062 auto snapshot = manager.GetAndClearRecordedStateChanges();
1063 EXPECT_EQ(manager.GetLastStateChangeId(), snapshot.update_id);
1064 ASSERT_EQ(4u, snapshot.state_changes.size());
1065
1066 EXPECT_EQ("comp1", snapshot.state_changes[0].component);
1067 EXPECT_EQ(time1, snapshot.state_changes[0].timestamp);
1068 EXPECT_JSON_EQ(R"({"trait1":{"prop1":"foo","prop2":"foo"}})",
1069 *snapshot.state_changes[0].changed_properties);
1070
1071 EXPECT_EQ("comp2", snapshot.state_changes[1].component);
1072 EXPECT_EQ(time1, snapshot.state_changes[1].timestamp);
1073 EXPECT_JSON_EQ(R"({"trait2":{"prop3":"foo"}})",
1074 *snapshot.state_changes[1].changed_properties);
1075
1076 EXPECT_EQ("comp1", snapshot.state_changes[2].component);
1077 EXPECT_EQ(time2, snapshot.state_changes[2].timestamp);
1078 EXPECT_JSON_EQ(R"({"trait1":{"prop1":"bar","prop2":"bar"}})",
1079 *snapshot.state_changes[2].changed_properties);
1080
1081 EXPECT_EQ("comp2", snapshot.state_changes[3].component);
1082 EXPECT_EQ(time2, snapshot.state_changes[3].timestamp);
1083 EXPECT_JSON_EQ(R"({"trait2":{"prop3":"bar"}})",
1084 *snapshot.state_changes[3].changed_properties);
1085
1086 // Make sure previous GetAndClearRecordedStateChanges() clears the queue.
1087 auto snapshot2 = manager.GetAndClearRecordedStateChanges();
1088 EXPECT_EQ(manager.GetLastStateChangeId(), snapshot2.update_id);
1089 EXPECT_TRUE(snapshot2.state_changes.empty());
1090
1091 // Now indicate that we have update the changes on the server.
1092 manager.NotifyStateUpdatedOnServer(snapshot.update_id);
1093 ASSERT_EQ(1u, updates1.size());
1094 EXPECT_EQ(snapshot.update_id, updates1.front());
1095 ASSERT_EQ(1u, updates2.size());
1096 EXPECT_EQ(snapshot.update_id, updates2.front());
1097}
1098
Alex Vakulenkoa3c5e6d2015-12-04 17:54:38 -08001099TEST(ComponentManager, FindComponentWithTrait) {
Alex Vakulenkoba981152015-12-05 13:58:22 -08001100 ComponentManagerImpl manager;
Alex Vakulenkoa3c5e6d2015-12-04 17:54:38 -08001101 const char kTraits[] = R"({
1102 "trait1": {},
1103 "trait2": {},
1104 "trait3": {}
1105 })";
1106 auto traits = CreateDictionaryValue(kTraits);
1107 ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
1108 ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1", "trait2"}, nullptr));
1109 ASSERT_TRUE(manager.AddComponent("", "comp2", {"trait3"}, nullptr));
1110
1111 EXPECT_EQ("comp1", manager.FindComponentWithTrait("trait1"));
1112 EXPECT_EQ("comp1", manager.FindComponentWithTrait("trait2"));
1113 EXPECT_EQ("comp2", manager.FindComponentWithTrait("trait3"));
1114 EXPECT_EQ("", manager.FindComponentWithTrait("trait4"));
1115}
1116
Alex Vakulenkoba981152015-12-05 13:58:22 -08001117TEST(ComponentManager, TestMockComponentManager) {
1118 // Check that all the virtual methods are mocked out.
1119 MockComponentManager mock;
1120}
1121
Alex Vakulenko44c1dbe2015-12-03 15:35:09 -08001122} // namespace weave