test: Add dbus handler unit tests

Created unit tests for all of the object mapper dbus handlers.

Refactor the handlers to handler.cpp to allow unit testing.
Also updated to use std::set_intersection.

Change-Id: Ib87ab81f949d27f7dab55899d3e8d380e43bda04
Signed-off-by: Willy Tu <wltu@google.com>
diff --git a/src/test/handler.cpp b/src/test/handler.cpp
new file mode 100644
index 0000000..693ab21
--- /dev/null
+++ b/src/test/handler.cpp
@@ -0,0 +1,259 @@
+#include "src/handler.hpp"
+
+#include "src/types.hpp"
+
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <span>
+#include <utility>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using ::testing::ElementsAre;
+
+class TestHandler : public testing::Test
+{
+  protected:
+    InterfaceMapType interfaceMap = {
+        {
+            "/test/object_path_0",
+            {{"test_object_connection_0", {"test_interface_0"}}},
+        },
+        {
+            "/test/object_path_0/child",
+            {{"test_object_connection_1", {"test_interface_1"}}},
+        },
+        {
+            "/test/object_path_0/child/grandchild",
+            {{"test_object_connection_2", {"test_interface_2"}}},
+        },
+        {
+            "/test/object_path_0/child/grandchild/dog",
+            {{"test_object_connection_3", {"test_interface_3"}}},
+        }};
+};
+
+TEST_F(TestHandler, AddObjectMapResult)
+{
+    std::vector<InterfaceMapType::value_type> interfaceMaps;
+    addObjectMapResult(interfaceMaps, "test_object_path",
+                       std::pair<std::string, InterfaceNames>(
+                           "test_object_connection_0", {
+                                                           "test_interface_0",
+                                                           "test_interface_1",
+                                                       }));
+
+    addObjectMapResult(interfaceMaps, "test_object_path",
+                       std::pair<std::string, InterfaceNames>(
+                           "test_object_connection_1", {
+                                                           "test_interface_0",
+                                                           "test_interface_1",
+                                                       }));
+    ASSERT_EQ(interfaceMaps.size(), 1);
+
+    auto entry = std::find_if(
+        interfaceMaps.begin(), interfaceMaps.end(),
+        [](const auto& i) { return "test_object_path" == i.first; });
+    ASSERT_NE(entry, interfaceMap.end());
+    for (const auto& [_, interfaces] : entry->second)
+    {
+        ASSERT_THAT(interfaces,
+                    ElementsAre("test_interface_0", "test_interface_1"));
+    }
+
+    // Change the interface, but expect it to be unchanged
+    addObjectMapResult(interfaceMaps, "test_object_path",
+                       std::pair<std::string, InterfaceNames>(
+                           "test_object_connection_0", {"test_interface_2"}));
+    addObjectMapResult(interfaceMaps, "test_object_path",
+                       std::pair<std::string, InterfaceNames>(
+                           "test_object_connection_1", {"test_interface_2"}));
+    entry = std::find_if(
+        interfaceMaps.begin(), interfaceMaps.end(),
+        [](const auto& i) { return "test_object_path" == i.first; });
+    ASSERT_NE(entry, interfaceMaps.end());
+    for (const auto& [_, interfaces] : entry->second)
+    {
+        ASSERT_THAT(interfaces,
+                    ElementsAre("test_interface_0", "test_interface_1"));
+    }
+}
+
+TEST_F(TestHandler, getAncestorsBad)
+{
+    std::string path = "/test/object_path_0/child/grandchild";
+    std::vector<std::string> interfaces = {"bad_interface"};
+    std::vector<InterfaceMapType::value_type> ancestors =
+        getAncestors(interfaceMap, path, interfaces);
+    ASSERT_TRUE(ancestors.empty());
+
+    path = "/invalid_path";
+    EXPECT_THROW(
+        getAncestors(interfaceMap, path, interfaces),
+        sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+}
+
+TEST_F(TestHandler, getAncestorsGood)
+{
+    std::string path = "/test/object_path_0/child/grandchild";
+    std::vector<std::string> interfaces = {"test_interface_0",
+                                           "test_interface_1"};
+    std::vector<InterfaceMapType::value_type> ancestors =
+        getAncestors(interfaceMap, path, interfaces);
+    ASSERT_EQ(ancestors.size(), 2);
+
+    // Grand Parent
+    EXPECT_EQ(ancestors[0].first, "/test/object_path_0");
+    ASSERT_EQ(ancestors[0].second.size(), 1);
+    auto grandParent = ancestors[0].second.find("test_object_connection_0");
+    ASSERT_NE(grandParent, ancestors[0].second.end());
+    ASSERT_THAT(grandParent->second, ElementsAre("test_interface_0"));
+
+    // Parent
+    ASSERT_EQ(ancestors[1].first, "/test/object_path_0/child");
+    ASSERT_EQ(ancestors[1].second.size(), 1);
+    auto parent = ancestors[1].second.find("test_object_connection_1");
+    ASSERT_NE(parent, ancestors[1].second.end());
+    ASSERT_THAT(parent->second, ElementsAre("test_interface_1"));
+}
+
+TEST_F(TestHandler, getObjectBad)
+{
+    std::string path = "/test/object_path_0";
+    std::vector<std::string> interfaces = {"bad_interface"};
+    EXPECT_THROW(
+        getObject(interfaceMap, path, interfaces),
+        sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+
+    path = "/invalid_path";
+    EXPECT_THROW(
+        getObject(interfaceMap, path, interfaces),
+        sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+
+    path = "/";
+    EXPECT_THROW(
+        getObject(interfaceMap, path, interfaces),
+        sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+}
+
+TEST_F(TestHandler, getObjectGood)
+{
+    std::string path = "/test/object_path_0";
+    std::vector<std::string> interfaces = {"test_interface_0",
+                                           "test_interface_1"};
+    ConnectionNames connection = getObject(interfaceMap, path, interfaces);
+    auto object = connection.find("test_object_connection_0");
+    ASSERT_NE(object, connection.end());
+    ASSERT_THAT(object->second, ElementsAre("test_interface_0"));
+
+    path = "/test/object_path_0/child";
+    connection = getObject(interfaceMap, path, interfaces);
+    object = connection.find("test_object_connection_1");
+    ASSERT_NE(object, connection.end());
+    ASSERT_THAT(object->second, ElementsAre("test_interface_1"));
+}
+
+TEST_F(TestHandler, getSubTreeBad)
+{
+    std::string path = "/test/object_path_0";
+    std::vector<std::string> interfaces = {"bad_interface"};
+    std::vector<InterfaceMapType::value_type> subtree =
+        getSubTree(interfaceMap, path, 0, interfaces);
+    ASSERT_TRUE(subtree.empty());
+
+    path = "/invalid_path";
+    EXPECT_THROW(
+        getSubTree(interfaceMap, path, 0, interfaces),
+        sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+}
+
+void verifySubtree(std::span<InterfaceMapType::value_type> subtree)
+{
+    ASSERT_EQ(subtree.size(), 2);
+    ConnectionNames connection = subtree[0].second;
+    auto object = connection.find("test_object_connection_1");
+    ASSERT_NE(object, connection.end());
+    ASSERT_THAT(object->second, ElementsAre("test_interface_1"));
+
+    connection = subtree[1].second;
+    object = connection.find("test_object_connection_3");
+    ASSERT_NE(object, connection.end());
+    ASSERT_THAT(object->second, ElementsAre("test_interface_3"));
+}
+
+TEST_F(TestHandler, getSubTreeGood)
+{
+    std::string path0 = "/test/object_path_0";
+    std::string path1 = "/test/object_path_0/child/grandchild";
+    std::vector<std::string> interfaces = {"test_interface_1",
+                                           "test_interface_3"};
+    // Root
+    std::vector<InterfaceMapType::value_type> subtree =
+        getSubTree(interfaceMap, "/", 0, interfaces);
+    verifySubtree(subtree);
+
+    // Path0
+    subtree = getSubTree(interfaceMap, path0, 0, interfaces);
+    verifySubtree(subtree);
+
+    // Path0 with Depth path of 1
+    subtree = getSubTree(interfaceMap, path0, 1, interfaces);
+    ASSERT_EQ(subtree.size(), 1);
+    ConnectionNames connection = subtree[0].second;
+    auto object = connection.find("test_object_connection_1");
+    ASSERT_NE(object, connection.end());
+    ASSERT_THAT(object->second, ElementsAre("test_interface_1"));
+
+    // Path1
+    subtree = getSubTree(interfaceMap, path1, 0, interfaces);
+    ASSERT_EQ(subtree.size(), 1);
+    connection = subtree[0].second;
+    object = connection.find("test_object_connection_3");
+    ASSERT_NE(object, connection.end());
+    ASSERT_THAT(object->second, ElementsAre("test_interface_3"));
+}
+
+TEST_F(TestHandler, getSubTreePathsBad)
+{
+    std::string path = "/test/object_path_0";
+    std::vector<std::string> interfaces = {"bad_interface"};
+    std::vector<std::string> subtreePath =
+        getSubTreePaths(interfaceMap, path, 0, interfaces);
+    ASSERT_TRUE(subtreePath.empty());
+
+    path = "/invalid_path";
+    EXPECT_THROW(
+        getSubTreePaths(interfaceMap, path, 0, interfaces),
+        sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+}
+
+TEST_F(TestHandler, getSubTreePathsGood)
+{
+    std::string path0 = "/test/object_path_0";
+    std::string path1 = "/test/object_path_0/child/grandchild";
+    std::vector<std::string> interfaces = {"test_interface_1",
+                                           "test_interface_3"};
+    // Root
+    std::vector<std::string> subtreePath =
+        getSubTreePaths(interfaceMap, "/", 0, interfaces);
+    ASSERT_THAT(subtreePath,
+                ElementsAre("/test/object_path_0/child",
+                            "/test/object_path_0/child/grandchild/dog"));
+
+    // Path0
+    subtreePath = getSubTreePaths(interfaceMap, path0, 0, interfaces);
+    ASSERT_THAT(subtreePath,
+                ElementsAre("/test/object_path_0/child",
+                            "/test/object_path_0/child/grandchild/dog"));
+
+    // Path0 + Depth path of 1
+    subtreePath = getSubTreePaths(interfaceMap, path0, 1, interfaces);
+    ASSERT_THAT(subtreePath, ElementsAre("/test/object_path_0/child"));
+
+    // Path1
+    subtreePath = getSubTreePaths(interfaceMap, path1, 0, interfaces);
+    ASSERT_THAT(subtreePath,
+                ElementsAre("/test/object_path_0/child/grandchild/dog"));
+}
diff --git a/src/test/meson.build b/src/test/meson.build
index 07ef230..c377a64 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -1,5 +1,6 @@
 processing_cpp_dep = declare_dependency(sources: '../processing.cpp')
 associations_cpp_dep = declare_dependency(sources: '../associations.cpp')
+handler_cpp_dep = declare_dependency(sources: '../handler.cpp')
 
 tests = [
   [ 'well_known', [ associations_cpp_dep, processing_cpp_dep ]],
@@ -7,6 +8,7 @@
   [ 'associations', [ associations_cpp_dep ]],
   [ 'name_change', [ associations_cpp_dep, processing_cpp_dep ]],
   [ 'interfaces_added', [ associations_cpp_dep, processing_cpp_dep ]],
+  [ 'handler', [ handler_cpp_dep, sdbusplus, phosphor_dbus_interfaces ]],
 ]
 
 foreach t : tests