1 #include "src/handler.hpp"
2 
3 #include "src/types.hpp"
4 
5 #include <xyz/openbmc_project/Common/error.hpp>
6 
7 #include <span>
8 #include <utility>
9 #include <vector>
10 
11 #include <gmock/gmock.h>
12 #include <gtest/gtest.h>
13 
14 using ::testing::ElementsAre;
15 
16 class TestHandler : public testing::Test
17 {
18   protected:
19     InterfaceMapType interfaceMap = {
20         {
21             "/test/object_path_0",
22             {{"test_object_connection_0", {"test_interface_0"}}},
23         },
24         {
25             "/test/object_path_0/child",
26             {{"test_object_connection_1", {"test_interface_1"}}},
27         },
28         {
29             "/test/object_path_0/child/grandchild",
30             {{"test_object_connection_2", {"test_interface_2"}}},
31         },
32         {
33             "/test/object_path_0/child/grandchild/dog",
34             {{"test_object_connection_3", {"test_interface_3"}}},
35         }};
36 
37     AssociationMaps associationMap = {
38         .ifaces =
39             {
40                 {
41                     "/test/object_path_0/descendent",
42                     {
43                         std::shared_ptr<sdbusplus::asio::dbus_interface>(),
44                         {
45                             "/test/object_path_0/child",
46                             "/test/object_path_0/child/grandchild",
47                         },
48                     },
49                 },
50                 {
51                     "/test/object_path_0/child/descendent",
52                     {
53                         std::shared_ptr<sdbusplus::asio::dbus_interface>(),
54                         {
55                             "/test/object_path_0/child/grandchild",
56                         },
57                     },
58                 },
59             },
60         .owners = {},
61         .pending = {},
62     };
63 };
64 
65 TEST_F(TestHandler, AddObjectMapResult)
66 {
67     std::vector<InterfaceMapType::value_type> interfaceMaps;
68     addObjectMapResult(interfaceMaps, "test_object_path",
69                        std::pair<std::string, InterfaceNames>(
70                            "test_object_connection_0", {
71                                                            "test_interface_0",
72                                                            "test_interface_1",
73                                                        }));
74 
75     addObjectMapResult(interfaceMaps, "test_object_path",
76                        std::pair<std::string, InterfaceNames>(
77                            "test_object_connection_1", {
78                                                            "test_interface_0",
79                                                            "test_interface_1",
80                                                        }));
81     ASSERT_EQ(interfaceMaps.size(), 1);
82 
83     auto entry = std::find_if(
84         interfaceMaps.begin(), interfaceMaps.end(),
85         [](const auto& i) { return "test_object_path" == i.first; });
86     ASSERT_NE(entry, interfaceMap.end());
87     for (const auto& [_, interfaces] : entry->second)
88     {
89         ASSERT_THAT(interfaces,
90                     ElementsAre("test_interface_0", "test_interface_1"));
91     }
92 
93     // Change the interface, but expect it to be unchanged
94     addObjectMapResult(interfaceMaps, "test_object_path",
95                        std::pair<std::string, InterfaceNames>(
96                            "test_object_connection_0", {"test_interface_2"}));
97     addObjectMapResult(interfaceMaps, "test_object_path",
98                        std::pair<std::string, InterfaceNames>(
99                            "test_object_connection_1", {"test_interface_2"}));
100     entry = std::find_if(
101         interfaceMaps.begin(), interfaceMaps.end(),
102         [](const auto& i) { return "test_object_path" == i.first; });
103     ASSERT_NE(entry, interfaceMaps.end());
104     for (const auto& [_, interfaces] : entry->second)
105     {
106         ASSERT_THAT(interfaces,
107                     ElementsAre("test_interface_0", "test_interface_1"));
108     }
109 }
110 
111 TEST_F(TestHandler, getAncestorsBad)
112 {
113     std::string path = "/test/object_path_0/child/grandchild";
114     std::vector<std::string> interfaces = {"bad_interface"};
115     std::vector<InterfaceMapType::value_type> ancestors =
116         getAncestors(interfaceMap, path, interfaces);
117     ASSERT_TRUE(ancestors.empty());
118 
119     path = "/invalid_path";
120     EXPECT_THROW(
121         getAncestors(interfaceMap, path, interfaces),
122         sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
123 }
124 
125 TEST_F(TestHandler, getAncestorsGood)
126 {
127     std::string path = "/test/object_path_0/child/grandchild";
128     std::vector<std::string> interfaces = {"test_interface_0",
129                                            "test_interface_1"};
130     std::vector<InterfaceMapType::value_type> ancestors =
131         getAncestors(interfaceMap, path, interfaces);
132     ASSERT_EQ(ancestors.size(), 2);
133 
134     // Grand Parent
135     EXPECT_EQ(ancestors[0].first, "/test/object_path_0");
136     ASSERT_EQ(ancestors[0].second.size(), 1);
137     auto grandParent = ancestors[0].second.find("test_object_connection_0");
138     ASSERT_NE(grandParent, ancestors[0].second.end());
139     ASSERT_THAT(grandParent->second, ElementsAre("test_interface_0"));
140 
141     // Parent
142     ASSERT_EQ(ancestors[1].first, "/test/object_path_0/child");
143     ASSERT_EQ(ancestors[1].second.size(), 1);
144     auto parent = ancestors[1].second.find("test_object_connection_1");
145     ASSERT_NE(parent, ancestors[1].second.end());
146     ASSERT_THAT(parent->second, ElementsAre("test_interface_1"));
147 }
148 
149 TEST_F(TestHandler, getObjectBad)
150 {
151     std::string path = "/test/object_path_0";
152     std::vector<std::string> interfaces = {"bad_interface"};
153     EXPECT_THROW(
154         getObject(interfaceMap, path, interfaces),
155         sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
156 
157     path = "/invalid_path";
158     EXPECT_THROW(
159         getObject(interfaceMap, path, interfaces),
160         sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
161 
162     path = "/";
163     EXPECT_THROW(
164         getObject(interfaceMap, path, interfaces),
165         sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
166 }
167 
168 TEST_F(TestHandler, getObjectGood)
169 {
170     std::string path = "/test/object_path_0";
171     std::vector<std::string> interfaces = {"test_interface_0",
172                                            "test_interface_1"};
173     ConnectionNames connection = getObject(interfaceMap, path, interfaces);
174     auto object = connection.find("test_object_connection_0");
175     ASSERT_NE(object, connection.end());
176     ASSERT_THAT(object->second, ElementsAre("test_interface_0"));
177 
178     path = "/test/object_path_0/child";
179     connection = getObject(interfaceMap, path, interfaces);
180     object = connection.find("test_object_connection_1");
181     ASSERT_NE(object, connection.end());
182     ASSERT_THAT(object->second, ElementsAre("test_interface_1"));
183 }
184 
185 TEST_F(TestHandler, getSubTreeBad)
186 {
187     std::string path = "/test/object_path_0";
188     std::vector<std::string> interfaces = {"bad_interface"};
189     std::vector<InterfaceMapType::value_type> subtree =
190         getSubTree(interfaceMap, path, 0, interfaces);
191     ASSERT_TRUE(subtree.empty());
192 
193     path = "/invalid_path";
194     EXPECT_THROW(
195         getSubTree(interfaceMap, path, 0, interfaces),
196         sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
197 }
198 
199 void verifySubtree(std::span<InterfaceMapType::value_type> subtree)
200 {
201     ASSERT_EQ(subtree.size(), 2);
202     ConnectionNames connection = subtree[0].second;
203     auto object = connection.find("test_object_connection_1");
204     ASSERT_NE(object, connection.end());
205     ASSERT_THAT(object->second, ElementsAre("test_interface_1"));
206 
207     connection = subtree[1].second;
208     object = connection.find("test_object_connection_3");
209     ASSERT_NE(object, connection.end());
210     ASSERT_THAT(object->second, ElementsAre("test_interface_3"));
211 }
212 
213 TEST_F(TestHandler, getSubTreeGood)
214 {
215     std::string path0 = "/test/object_path_0";
216     std::string path1 = "/test/object_path_0/child/grandchild";
217     std::vector<std::string> interfaces = {"test_interface_1",
218                                            "test_interface_3"};
219     // Root
220     std::vector<InterfaceMapType::value_type> subtree =
221         getSubTree(interfaceMap, "/", 0, interfaces);
222     verifySubtree(subtree);
223 
224     // Path0
225     subtree = getSubTree(interfaceMap, path0, 0, interfaces);
226     verifySubtree(subtree);
227 
228     // Path0 with Depth path of 1
229     subtree = getSubTree(interfaceMap, path0, 1, interfaces);
230     ASSERT_EQ(subtree.size(), 1);
231     ConnectionNames connection = subtree[0].second;
232     auto object = connection.find("test_object_connection_1");
233     ASSERT_NE(object, connection.end());
234     ASSERT_THAT(object->second, ElementsAre("test_interface_1"));
235 
236     // Path1
237     subtree = getSubTree(interfaceMap, path1, 0, interfaces);
238     ASSERT_EQ(subtree.size(), 1);
239     connection = subtree[0].second;
240     object = connection.find("test_object_connection_3");
241     ASSERT_NE(object, connection.end());
242     ASSERT_THAT(object->second, ElementsAre("test_interface_3"));
243 }
244 
245 TEST_F(TestHandler, getSubTreePathsBad)
246 {
247     std::string path = "/test/object_path_0";
248     std::vector<std::string> interfaces = {"bad_interface"};
249     std::vector<std::string> subtreePath = getSubTreePaths(interfaceMap, path,
250                                                            0, interfaces);
251     ASSERT_TRUE(subtreePath.empty());
252 
253     path = "/invalid_path";
254     EXPECT_THROW(
255         getSubTreePaths(interfaceMap, path, 0, interfaces),
256         sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
257 }
258 
259 TEST_F(TestHandler, getSubTreePathsGood)
260 {
261     std::string path0 = "/test/object_path_0";
262     std::string path1 = "/test/object_path_0/child/grandchild";
263     std::vector<std::string> interfaces = {"test_interface_1",
264                                            "test_interface_3"};
265     // Root
266     std::vector<std::string> subtreePath = getSubTreePaths(interfaceMap, "/", 0,
267                                                            interfaces);
268     ASSERT_THAT(subtreePath,
269                 ElementsAre("/test/object_path_0/child",
270                             "/test/object_path_0/child/grandchild/dog"));
271 
272     // Path0
273     subtreePath = getSubTreePaths(interfaceMap, path0, 0, interfaces);
274     ASSERT_THAT(subtreePath,
275                 ElementsAre("/test/object_path_0/child",
276                             "/test/object_path_0/child/grandchild/dog"));
277 
278     // Path0 + Depth path of 1
279     subtreePath = getSubTreePaths(interfaceMap, path0, 1, interfaces);
280     ASSERT_THAT(subtreePath, ElementsAre("/test/object_path_0/child"));
281 
282     // Path1
283     subtreePath = getSubTreePaths(interfaceMap, path1, 0, interfaces);
284     ASSERT_THAT(subtreePath,
285                 ElementsAre("/test/object_path_0/child/grandchild/dog"));
286 }
287 
288 TEST_F(TestHandler, getAssociatedSubTreeBad)
289 {
290     sdbusplus::message::object_path path("/test/object_path_0");
291     sdbusplus::message::object_path validAssociatedPath = path / "descendent";
292     std::vector<std::string> invalidInterfaces = {"test_interface_3"};
293     std::vector<std::string> validInterfaces = {"test_interface_1",
294                                                 "test_interface_2"};
295     // Associated path, but invalid interface
296     ASSERT_TRUE(getAssociatedSubTree(interfaceMap, associationMap,
297                                      validAssociatedPath, path, 0,
298                                      invalidInterfaces)
299                     .empty());
300 
301     // Valid interface, not associated
302     ASSERT_TRUE(getAssociatedSubTree(interfaceMap, associationMap, path / "dog",
303                                      path, 0, validInterfaces)
304                     .empty());
305 
306     // Invalid path, with valid association
307     path = sdbusplus::message::object_path("/invalid_path");
308     EXPECT_THROW(
309         getAssociatedSubTree(interfaceMap, associationMap, validAssociatedPath,
310                              path, 0, validInterfaces),
311         sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
312 }
313 
314 TEST_F(TestHandler, getAssociatedSubTreeGood)
315 {
316     sdbusplus::message::object_path path0("/test/object_path_0");
317     sdbusplus::message::object_path path1("/test/object_path_0/child");
318     sdbusplus::message::object_path associatedPath = path0 / "descendent";
319     std::vector<std::string> interfaces = {"test_interface_1",
320                                            "test_interface_2",
321                                            // Not associated to path
322                                            "test_interface_3"};
323 
324     // Path0
325     std::vector<InterfaceMapType::value_type> subtree = getAssociatedSubTree(
326         interfaceMap, associationMap, associatedPath, path0, 0, interfaces);
327     ASSERT_EQ(subtree.size(), 2);
328     ConnectionNames connection = subtree[0].second;
329     auto object = connection.find("test_object_connection_1");
330     ASSERT_NE(object, connection.end());
331     ASSERT_THAT(object->second, ElementsAre("test_interface_1"));
332 
333     connection = subtree[1].second;
334     object = connection.find("test_object_connection_2");
335     ASSERT_NE(object, connection.end());
336     ASSERT_THAT(object->second, ElementsAre("test_interface_2"));
337 
338     // Path0 with Depth path of 1
339     subtree = getAssociatedSubTree(interfaceMap, associationMap, associatedPath,
340                                    path0, 1, interfaces);
341     ASSERT_EQ(subtree.size(), 1);
342     connection = subtree[0].second;
343     object = connection.find("test_object_connection_1");
344     ASSERT_NE(object, connection.end());
345     ASSERT_THAT(object->second, ElementsAre("test_interface_1"));
346 
347     // Path1
348     subtree = getAssociatedSubTree(interfaceMap, associationMap,
349                                    path1 / "descendent", path1, 0, interfaces);
350     ASSERT_EQ(subtree.size(), 1);
351     connection = subtree[0].second;
352     object = connection.find("test_object_connection_2");
353     ASSERT_NE(object, connection.end());
354     ASSERT_THAT(object->second, ElementsAre("test_interface_2"));
355 }
356 
357 TEST_F(TestHandler, getAssociatedSubTreePathsBad)
358 {
359     sdbusplus::message::object_path path("/test/object_path_0");
360     sdbusplus::message::object_path validAssociatedPath = path / "descendent";
361     std::vector<std::string> invalidInterfaces = {"test_interface_3"};
362     std::vector<std::string> validInterfaces = {"test_interface_1",
363                                                 "test_interface_2"};
364     // Associated path, but invalid interface
365     ASSERT_TRUE(getAssociatedSubTreePaths(interfaceMap, associationMap,
366                                           validAssociatedPath, path, 0,
367                                           invalidInterfaces)
368                     .empty());
369 
370     // Valid interface, not associated
371     ASSERT_TRUE(getAssociatedSubTreePaths(interfaceMap, associationMap,
372                                           path / "dog", path, 0,
373                                           validInterfaces)
374                     .empty());
375 
376     // Invalid path, with valid association
377     path = sdbusplus::message::object_path("/invalid_path");
378     EXPECT_THROW(
379         getAssociatedSubTreePaths(interfaceMap, associationMap,
380                                   validAssociatedPath, path, 0,
381                                   validInterfaces),
382         sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
383 }
384 
385 TEST_F(TestHandler, getAssociatedSubTreePathsGood)
386 {
387     sdbusplus::message::object_path path0("/test/object_path_0");
388     sdbusplus::message::object_path path1("/test/object_path_0/child");
389     sdbusplus::message::object_path associatedPath = path0 / "descendent";
390     std::vector<std::string> interfaces = {"test_interface_1",
391                                            "test_interface_2",
392                                            // Not associated to path
393                                            "test_interface_3"};
394 
395     // Path0
396     std::vector<std::string> subtreePath = getAssociatedSubTreePaths(
397         interfaceMap, associationMap, associatedPath, path0, 0, interfaces);
398     ASSERT_THAT(subtreePath,
399                 ElementsAre("/test/object_path_0/child",
400                             "/test/object_path_0/child/grandchild"));
401 
402     // Path0 with Depth path of 1
403     subtreePath = getAssociatedSubTreePaths(
404         interfaceMap, associationMap, associatedPath, path0, 1, interfaces);
405     ASSERT_THAT(subtreePath, ElementsAre("/test/object_path_0/child"));
406 
407     // Path1
408     subtreePath = getAssociatedSubTreePaths(interfaceMap, associationMap,
409                                             path1 / "descendent", path1, 0,
410                                             interfaces);
411     ASSERT_THAT(subtreePath,
412                 ElementsAre("/test/object_path_0/child/grandchild"));
413 }
414