blob: ba6bc342a95e893fefc6413ad026a2d178b09f47 [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
5#include "src/component_manager.h"
6
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"
14
15namespace weave {
16
17using test::CreateDictionaryValue;
18
19namespace {
20
21bool HasTrait(const base::DictionaryValue& comp, const std::string& trait) {
22 const base::ListValue* list = nullptr;
23 if (!comp.GetList("traits", &list))
24 return false;
25 for (const base::Value* item : *list) {
26 std::string value;
27 if (item->GetAsString(&value) && value == trait)
28 return true;
29 }
30 return false;
31}
32
33} // anonymous namespace
34
35TEST(ComponentManager, Empty) {
36 ComponentManager manager;
37 EXPECT_TRUE(manager.GetTraits().empty());
38 EXPECT_TRUE(manager.GetComponents().empty());
39}
40
41TEST(ComponentManager, LoadTraits) {
42 ComponentManager manager;
43 const char kTraits[] = R"({
44 "trait1": {
45 "commands": {
46 "command1": {
47 "minimalRole": "user",
48 "parameters": {"height": {"type": "integer"}}
49 }
50 },
51 "state": {
52 "property1": {"type": "boolean"}
53 }
54 },
55 "trait2": {
56 "state": {
57 "property2": {"type": "string"}
58 }
59 }
60 })";
61 auto json = CreateDictionaryValue(kTraits);
62 EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
63 EXPECT_JSON_EQ(kTraits, manager.GetTraits());
64 EXPECT_TRUE(manager.GetComponents().empty());
65}
66
67TEST(ComponentManager, LoadTraitsDuplicateIdentical) {
68 ComponentManager manager;
69 const char kTraits1[] = R"({
70 "trait1": {
71 "commands": {
72 "command1": {
73 "minimalRole": "user",
74 "parameters": {"height": {"type": "integer"}}
75 }
76 },
77 "state": {
78 "property1": {"type": "boolean"}
79 }
80 },
81 "trait2": {
82 "state": {
83 "property2": {"type": "string"}
84 }
85 }
86 })";
87 auto json = CreateDictionaryValue(kTraits1);
88 EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
89 const char kTraits2[] = R"({
90 "trait1": {
91 "commands": {
92 "command1": {
93 "minimalRole": "user",
94 "parameters": {"height": {"type": "integer"}}
95 }
96 },
97 "state": {
98 "property1": {"type": "boolean"}
99 }
100 },
101 "trait3": {
102 "state": {
103 "property3": {"type": "string"}
104 }
105 }
106 })";
107 json = CreateDictionaryValue(kTraits2);
108 EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
109 const char kExpected[] = 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 "trait3": {
127 "state": {
128 "property3": {"type": "string"}
129 }
130 }
131 })";
132 EXPECT_JSON_EQ(kExpected, manager.GetTraits());
133}
134
135TEST(ComponentManager, LoadTraitsDuplicateOverride) {
136 ComponentManager manager;
137 const char kTraits1[] = R"({
138 "trait1": {
139 "commands": {
140 "command1": {
141 "minimalRole": "user",
142 "parameters": {"height": {"type": "integer"}}
143 }
144 },
145 "state": {
146 "property1": {"type": "boolean"}
147 }
148 },
149 "trait2": {
150 "state": {
151 "property2": {"type": "string"}
152 }
153 }
154 })";
155 auto json = CreateDictionaryValue(kTraits1);
156 EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
157 const char kTraits2[] = R"({
158 "trait1": {
159 "commands": {
160 "command1": {
161 "minimalRole": "user",
162 "parameters": {"height": {"type": "string"}}
163 }
164 },
165 "state": {
166 "property1": {"type": "boolean"}
167 }
168 },
169 "trait3": {
170 "state": {
171 "property3": {"type": "string"}
172 }
173 }
174 })";
175 json = CreateDictionaryValue(kTraits2);
176 EXPECT_FALSE(manager.LoadTraits(*json, nullptr));
177}
178
179TEST(ComponentManager, LoadTraitsNotAnObject) {
180 ComponentManager manager;
181 const char kTraits1[] = R"({"trait1": 0})";
182 auto json = CreateDictionaryValue(kTraits1);
183 ErrorPtr error;
184 EXPECT_FALSE(manager.LoadTraits(*json, &error));
185 EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
186}
187
188TEST(ComponentManager, FindTraitDefinition) {
189 ComponentManager manager;
190 const char kTraits[] = R"({
191 "trait1": {
192 "commands": {
193 "command1": {
194 "minimalRole": "user",
195 "parameters": {"height": {"type": "integer"}}
196 }
197 },
198 "state": {
199 "property1": {"type": "boolean"}
200 }
201 },
202 "trait2": {
203 "state": {
204 "property2": {"type": "string"}
205 }
206 }
207 })";
208 auto json = CreateDictionaryValue(kTraits);
209 ASSERT_TRUE(manager.LoadTraits(*json, nullptr));
210
211 const base::DictionaryValue* trait = manager.FindTraitDefinition("trait1");
212 ASSERT_NE(nullptr, trait);
213 const char kExpected1[] = R"({
214 "commands": {
215 "command1": {
216 "minimalRole": "user",
217 "parameters": {"height": {"type": "integer"}}
218 }
219 },
220 "state": {
221 "property1": {"type": "boolean"}
222 }
223 })";
224 EXPECT_JSON_EQ(kExpected1, *trait);
225
226 trait = manager.FindTraitDefinition("trait2");
227 ASSERT_NE(nullptr, trait);
228 const char kExpected2[] = R"({
229 "state": {
230 "property2": {"type": "string"}
231 }
232 })";
233 EXPECT_JSON_EQ(kExpected2, *trait);
234
235 EXPECT_EQ(nullptr, manager.FindTraitDefinition("trait3"));
236}
237
238TEST(ComponentManager, FindCommandDefinition) {
239 ComponentManager manager;
240 const char kTraits[] = R"({
241 "trait1": {
242 "commands": {
243 "command1": {
244 "minimalRole": "user",
245 "parameters": {"height": {"type": "integer"}}
246 }
247 }
248 },
249 "trait2": {
250 "commands": {
251 "command1": {
252 "minimalRole": "manager"
253 },
254 "command2": {
255 "minimalRole": "owner"
256 }
257 }
258 }
259 })";
260 auto json = CreateDictionaryValue(kTraits);
261 ASSERT_TRUE(manager.LoadTraits(*json, nullptr));
262
263 const auto* cmd_def = manager.FindCommandDefinition("trait1.command1");
264 ASSERT_NE(nullptr, cmd_def);
265 const char kExpected1[] = R"({
266 "minimalRole": "user",
267 "parameters": {"height": {"type": "integer"}}
268 })";
269 EXPECT_JSON_EQ(kExpected1, *cmd_def);
270
271 cmd_def = manager.FindCommandDefinition("trait2.command1");
272 ASSERT_NE(nullptr, cmd_def);
273 const char kExpected2[] = R"({
274 "minimalRole": "manager"
275 })";
276 EXPECT_JSON_EQ(kExpected2, *cmd_def);
277
278 cmd_def = manager.FindCommandDefinition("trait2.command2");
279 ASSERT_NE(nullptr, cmd_def);
280 const char kExpected3[] = R"({
281 "minimalRole": "owner"
282 })";
283 EXPECT_JSON_EQ(kExpected3, *cmd_def);
284
285 EXPECT_EQ(nullptr, manager.FindTraitDefinition("trait1.command2"));
286 EXPECT_EQ(nullptr, manager.FindTraitDefinition("trait3.command1"));
287 EXPECT_EQ(nullptr, manager.FindTraitDefinition("trait"));
288 EXPECT_EQ(nullptr, manager.FindTraitDefinition("trait1.command1.parameters"));
289}
290
291TEST(ComponentManager, GetMinimalRole) {
292 ComponentManager manager;
293 const char kTraits[] = R"({
294 "trait1": {
295 "commands": {
296 "command1": { "minimalRole": "user" },
297 "command2": { "minimalRole": "viewer" }
298 }
299 },
300 "trait2": {
301 "commands": {
302 "command1": { "minimalRole": "manager" },
303 "command2": { "minimalRole": "owner" }
304 }
305 }
306 })";
307 auto json = CreateDictionaryValue(kTraits);
308 ASSERT_TRUE(manager.LoadTraits(*json, nullptr));
309
310 UserRole role;
311 ASSERT_TRUE(manager.GetMinimalRole("trait1.command1", &role, nullptr));
312 EXPECT_EQ(UserRole::kUser, role);
313
314 ASSERT_TRUE(manager.GetMinimalRole("trait1.command2", &role, nullptr));
315 EXPECT_EQ(UserRole::kViewer, role);
316
317 ASSERT_TRUE(manager.GetMinimalRole("trait2.command1", &role, nullptr));
318 EXPECT_EQ(UserRole::kManager, role);
319
320 ASSERT_TRUE(manager.GetMinimalRole("trait2.command2", &role, nullptr));
321 EXPECT_EQ(UserRole::kOwner, role);
322
323 EXPECT_FALSE(manager.GetMinimalRole("trait1.command3", &role, nullptr));
324}
325
326TEST(ComponentManager, AddComponent) {
327 ComponentManager manager;
328 const char kTraits[] = R"({"trait1": {}, "trait2": {}, "trait3": {}})";
329 auto json = CreateDictionaryValue(kTraits);
330 ASSERT_TRUE(manager.LoadTraits(*json, nullptr));
331 EXPECT_TRUE(manager.AddComponent("", "comp1", {"trait1", "trait2"}, nullptr));
332 EXPECT_TRUE(manager.AddComponent("", "comp2", {"trait3"}, nullptr));
333 const char kExpected[] = R"({
334 "comp1": {
335 "traits": ["trait1", "trait2"]
336 },
337 "comp2": {
338 "traits": ["trait3"]
339 }
340 })";
341 EXPECT_JSON_EQ(kExpected, manager.GetComponents());
342
343 // 'trait4' is undefined, so can't add a component referring to it.
344 EXPECT_FALSE(manager.AddComponent("", "comp3", {"trait4"}, nullptr));
345}
346
347TEST(ComponentManager, AddSubComponent) {
348 ComponentManager manager;
349 EXPECT_TRUE(manager.AddComponent("", "comp1", {}, nullptr));
350 EXPECT_TRUE(manager.AddComponent("comp1", "comp2", {}, nullptr));
351 EXPECT_TRUE(manager.AddComponent("comp1", "comp3", {}, nullptr));
352 EXPECT_TRUE(manager.AddComponent("comp1.comp2", "comp4", {}, nullptr));
353 const char kExpected[] = R"({
354 "comp1": {
355 "traits": [],
356 "components": {
357 "comp2": {
358 "traits": [],
359 "components": {
360 "comp4": {
361 "traits": []
362 }
363 }
364 },
365 "comp3": {
366 "traits": []
367 }
368 }
369 }
370 })";
371 EXPECT_JSON_EQ(kExpected, manager.GetComponents());
372}
373
374TEST(ComponentManager, AddComponentArrayItem) {
375 ComponentManager manager;
376 const char kTraits[] = R"({"foo": {}, "bar": {}})";
377 auto json = CreateDictionaryValue(kTraits);
378 ASSERT_TRUE(manager.LoadTraits(*json, nullptr));
379
380 EXPECT_TRUE(manager.AddComponent("", "comp1", {}, nullptr));
381 EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp2", {"foo"},
382 nullptr));
383 EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp2", {"bar"},
384 nullptr));
385 EXPECT_TRUE(manager.AddComponent("comp1.comp2[1]", "comp3", {}, nullptr));
386 EXPECT_TRUE(manager.AddComponent("comp1.comp2[1].comp3", "comp4", {},
387 nullptr));
388 const char kExpected[] = R"({
389 "comp1": {
390 "traits": [],
391 "components": {
392 "comp2": [
393 {
394 "traits": ["foo"]
395 },
396 {
397 "traits": ["bar"],
398 "components": {
399 "comp3": {
400 "traits": [],
401 "components": {
402 "comp4": {
403 "traits": []
404 }
405 }
406 }
407 }
408 }
409 ]
410 }
411 }
412 })";
413 EXPECT_JSON_EQ(kExpected, manager.GetComponents());
414}
415
416TEST(ComponentManager, AddComponentExist) {
417 ComponentManager manager;
418 EXPECT_TRUE(manager.AddComponent("", "comp1", {}, nullptr));
419 EXPECT_FALSE(manager.AddComponent("", "comp1", {}, nullptr));
420 EXPECT_TRUE(manager.AddComponent("comp1", "comp2", {}, nullptr));
421 EXPECT_FALSE(manager.AddComponent("comp1", "comp2", {}, nullptr));
422}
423
424TEST(ComponentManager, AddComponentDoesNotExist) {
425 ComponentManager manager;
426 EXPECT_FALSE(manager.AddComponent("comp1", "comp2", {}, nullptr));
427}
428
429TEST(ComponentManager, FindComponent) {
430 ComponentManager manager;
431 const char kTraits[] = R"({"t1":{}, "t2":{}, "t3":{}, "t4":{}, "t5":{}})";
432 auto json = CreateDictionaryValue(kTraits);
433 ASSERT_TRUE(manager.LoadTraits(*json, nullptr));
434
435 EXPECT_TRUE(manager.AddComponent("", "comp1", {"t1"}, nullptr));
436 EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp2", {"t2"}, nullptr));
437 EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp2", {"t3"}, nullptr));
438 EXPECT_TRUE(manager.AddComponent("comp1.comp2[1]", "comp3", {"t4"}, nullptr));
439 EXPECT_TRUE(manager.AddComponent("comp1.comp2[1].comp3", "comp4", {"t5"},
440 nullptr));
441
442 const base::DictionaryValue* comp = manager.FindComponent("comp1", nullptr);
443 ASSERT_NE(nullptr, comp);
444 EXPECT_TRUE(HasTrait(*comp, "t1"));
445
446 comp = manager.FindComponent("comp1.comp2[0]", nullptr);
447 ASSERT_NE(nullptr, comp);
448 EXPECT_TRUE(HasTrait(*comp, "t2"));
449
450 comp = manager.FindComponent("comp1.comp2[1]", nullptr);
451 ASSERT_NE(nullptr, comp);
452 EXPECT_TRUE(HasTrait(*comp, "t3"));
453
454 comp = manager.FindComponent("comp1.comp2[1].comp3", nullptr);
455 ASSERT_NE(nullptr, comp);
456 EXPECT_TRUE(HasTrait(*comp, "t4"));
457
458 comp = manager.FindComponent("comp1.comp2[1].comp3.comp4", nullptr);
459 ASSERT_NE(nullptr, comp);
460 EXPECT_TRUE(HasTrait(*comp, "t5"));
461
462 // Some whitespaces don't hurt.
463 comp = manager.FindComponent(" comp1 . comp2 [ \t 1 ] . comp3.comp4 ",
464 nullptr);
465 EXPECT_NE(nullptr, comp);
466
467 // Now check some failure cases.
468 ErrorPtr error;
469 EXPECT_EQ(nullptr, manager.FindComponent("", &error));
470 EXPECT_NE(nullptr, error.get());
471 // 'comp2' doesn't exist:
472 EXPECT_EQ(nullptr, manager.FindComponent("comp2", nullptr));
473 // 'comp1.comp2' is an array, not a component:
474 EXPECT_EQ(nullptr, manager.FindComponent("comp1.comp2", nullptr));
475 // 'comp1.comp2[3]' doesn't exist:
476 EXPECT_EQ(nullptr, manager.FindComponent("comp1.comp2[3]", nullptr));
477 // Empty component names:
478 EXPECT_EQ(nullptr, manager.FindComponent(".comp2[1]", nullptr));
479 EXPECT_EQ(nullptr, manager.FindComponent("comp1.[1]", nullptr));
480 // Invalid array indices:
481 EXPECT_EQ(nullptr, manager.FindComponent("comp1.comp2[s]", nullptr));
482 EXPECT_EQ(nullptr, manager.FindComponent("comp1.comp2[-2]", nullptr));
483 EXPECT_EQ(nullptr, manager.FindComponent("comp1.comp2[1e1]", nullptr));
484 EXPECT_EQ(nullptr, manager.FindComponent("comp1.comp2[1", nullptr));
485}
486
487TEST(ComponentManager, AddCommand) {
488 ComponentManager manager;
489 const char kTraits[] = R"({
490 "trait1": {
491 "commands": {
492 "command1": { "minimalRole": "user" },
493 "command2": { "minimalRole": "viewer" }
494 }
495 },
496 "trait2": {
497 "commands": {
498 "command1": { "minimalRole": "manager" },
499 "command2": { "minimalRole": "owner" }
500 }
501 }
502 })";
503 auto traits = CreateDictionaryValue(kTraits);
504 ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
505 ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1"}, nullptr));
506 ASSERT_TRUE(manager.AddComponent("", "comp2", {"trait2"}, nullptr));
507
508 std::string id;
509 const char kCommand1[] = R"({
510 "name": "trait1.command1",
511 "component": "comp1",
512 "parameters": {}
513 })";
514 auto command1 = CreateDictionaryValue(kCommand1);
515 EXPECT_TRUE(manager.AddCommand(*command1, UserRole::kUser, &id, nullptr));
516 // Not enough access rights
517 EXPECT_FALSE(manager.AddCommand(*command1, UserRole::kViewer, &id, nullptr));
518
519 const char kCommand2[] = R"({
520 "name": "trait1.command3",
521 "component": "comp1",
522 "parameters": {}
523 })";
524 auto command2 = CreateDictionaryValue(kCommand2);
525 // trait1.command3 doesn't exist
526 EXPECT_FALSE(manager.AddCommand(*command2, UserRole::kOwner, &id, nullptr));
527
528 const char kCommand3[] = R"({
529 "name": "trait2.command1",
530 "component": "comp1",
531 "parameters": {}
532 })";
533 auto command3 = CreateDictionaryValue(kCommand3);
534 // Component comp1 doesn't have trait2.
535 EXPECT_FALSE(manager.AddCommand(*command3, UserRole::kOwner, &id, nullptr));
536
537 // No component specified, use the first top-level component (comp1)
538 const char kCommand4[] = R"({
539 "name": "trait1.command1",
540 "parameters": {}
541 })";
542 auto command4 = CreateDictionaryValue(kCommand4);
543 EXPECT_TRUE(manager.AddCommand(*command4, UserRole::kOwner, &id, nullptr));
544 auto cmd = manager.FindCommand(id);
545 ASSERT_NE(nullptr, cmd);
546 EXPECT_EQ("comp1", cmd->GetComponent());
547}
548
549TEST(ComponentManager, AddCommandHandler) {
550 ComponentManager manager;
551 const char kTraits[] = R"({
552 "trait1": {
553 "commands": {
554 "command1": { "minimalRole": "user" }
555 }
556 },
557 "trait2": {
558 "commands": {
559 "command2": { "minimalRole": "user" }
560 }
561 }
562 })";
563 auto traits = CreateDictionaryValue(kTraits);
564 ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
565 ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1"}, nullptr));
566 ASSERT_TRUE(manager.AddComponent("", "comp2", {"trait1", "trait2"}, nullptr));
567
568 std::string last_tags;
569 auto handler = [&last_tags](int tag, const std::weak_ptr<Command>& command) {
570 if (!last_tags.empty())
571 last_tags += ',';
572 last_tags += std::to_string(tag);
573 };
574
575 manager.AddCommandHandler("comp1", "trait1.command1", base::Bind(handler, 1));
576 manager.AddCommandHandler("comp2", "trait1.command1", base::Bind(handler, 2));
577 manager.AddCommandHandler("comp2", "trait2.command2", base::Bind(handler, 3));
578 EXPECT_TRUE(last_tags.empty());
579
580 const char kCommand1[] = R"({
581 "name": "trait1.command1",
582 "component": "comp1"
583 })";
584 auto command1 = CreateDictionaryValue(kCommand1);
585 EXPECT_TRUE(manager.AddCommand(*command1, UserRole::kUser, nullptr, nullptr));
586 EXPECT_EQ("1", last_tags);
587 last_tags.clear();
588
589 const char kCommand2[] = R"({
590 "name": "trait1.command1",
591 "component": "comp2"
592 })";
593 auto command2 = CreateDictionaryValue(kCommand2);
594 EXPECT_TRUE(manager.AddCommand(*command2, UserRole::kUser, nullptr, nullptr));
595 EXPECT_EQ("2", last_tags);
596 last_tags.clear();
597
598 const char kCommand3[] = R"({
599 "name": "trait2.command2",
600 "component": "comp2",
601 "parameters": {}
602 })";
603 auto command3 = CreateDictionaryValue(kCommand3);
604 EXPECT_TRUE(manager.AddCommand(*command3, UserRole::kUser, nullptr, nullptr));
605 EXPECT_EQ("3", last_tags);
606 last_tags.clear();
607}
608
609} // namespace weave