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/meson.build b/meson.build
index a7f3a4f..124b37a 100644
--- a/meson.build
+++ b/meson.build
@@ -94,6 +94,7 @@
         'src/main.cpp',
         'src/processing.cpp',
         'src/associations.cpp',
+        'src/handler.cpp',
     ],
     dependencies: [
         boost,
diff --git a/src/handler.cpp b/src/handler.cpp
new file mode 100644
index 0000000..1a362ab
--- /dev/null
+++ b/src/handler.cpp
@@ -0,0 +1,277 @@
+#include "handler.hpp"
+
+#include "types.hpp"
+
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+void addObjectMapResult(std::vector<InterfaceMapType::value_type>& objectMap,
+                        const std::string& objectPath,
+                        const ConnectionNames::value_type& interfaceMap)
+{
+    // Adds an object path/service name/interface list entry to
+    // the results of GetSubTree and GetAncestors.
+    // If an entry for the object path already exists, just add the
+    // service name and interfaces to that entry, otherwise create
+    // a new entry.
+    auto entry = std::find_if(
+        objectMap.begin(), objectMap.end(),
+        [&objectPath](const auto& i) { return objectPath == i.first; });
+
+    if (entry != objectMap.end())
+    {
+        entry->second.emplace(interfaceMap);
+    }
+    else
+    {
+        InterfaceMapType::value_type object;
+        object.first = objectPath;
+        object.second.emplace(interfaceMap);
+        objectMap.push_back(object);
+    }
+}
+
+std::vector<InterfaceMapType::value_type>
+    getAncestors(const InterfaceMapType& interfaceMap, std::string reqPath,
+                 std::vector<std::string>& interfaces)
+{
+    // Interfaces need to be sorted for intersect to function
+    std::sort(interfaces.begin(), interfaces.end());
+
+    if (reqPath.ends_with("/"))
+    {
+        reqPath.pop_back();
+    }
+    if (!reqPath.empty() && interfaceMap.find(reqPath) == interfaceMap.end())
+    {
+        throw sdbusplus::xyz::openbmc_project::Common::Error::
+            ResourceNotFound();
+    }
+
+    std::vector<InterfaceMapType::value_type> ret;
+    for (const auto& objectPath : interfaceMap)
+    {
+        const auto& thisPath = objectPath.first;
+
+        if (reqPath == thisPath)
+        {
+            continue;
+        }
+
+        if (reqPath.starts_with(thisPath))
+        {
+            if (interfaces.empty())
+            {
+                ret.emplace_back(objectPath);
+            }
+            else
+            {
+                for (const auto& interfaceMap : objectPath.second)
+                {
+                    std::vector<std::string> output(std::min(
+                        interfaces.size(), interfaceMap.second.size()));
+                    // Return iterator points at the first output elemtn,
+                    // meaning that there are no intersections.
+                    if (std::set_intersection(interfaces.begin(),
+                                              interfaces.end(),
+                                              interfaceMap.second.begin(),
+                                              interfaceMap.second.end(),
+                                              output.begin()) != output.begin())
+                    {
+                        addObjectMapResult(ret, thisPath, interfaceMap);
+                    }
+                }
+            }
+        }
+    }
+
+    return ret;
+}
+
+ConnectionNames getObject(const InterfaceMapType& interfaceMap,
+                          const std::string& path,
+                          std::vector<std::string>& interfaces)
+{
+    ConnectionNames results;
+
+    // Interfaces need to be sorted for intersect to function
+    std::sort(interfaces.begin(), interfaces.end());
+    auto pathRef = interfaceMap.find(path);
+    if (pathRef == interfaceMap.end())
+    {
+        throw sdbusplus::xyz::openbmc_project::Common::Error::
+            ResourceNotFound();
+    }
+    if (interfaces.empty())
+    {
+        return pathRef->second;
+    }
+    for (const auto& interfaceMap : pathRef->second)
+    {
+        std::vector<std::string> output(
+            std::min(interfaces.size(), interfaceMap.second.size()));
+        // Return iterator points at the first output elemtn,
+        // meaning that there are no intersections.
+        if (std::set_intersection(interfaces.begin(), interfaces.end(),
+                                  interfaceMap.second.begin(),
+                                  interfaceMap.second.end(),
+                                  output.begin()) != output.begin())
+        {
+            results.emplace(interfaceMap.first, interfaceMap.second);
+        }
+    }
+
+    if (results.empty())
+    {
+        throw sdbusplus::xyz::openbmc_project::Common::Error::
+            ResourceNotFound();
+    }
+
+    return results;
+}
+
+std::vector<InterfaceMapType::value_type>
+    getSubTree(const InterfaceMapType& interfaceMap, std::string reqPath,
+               int32_t depth, std::vector<std::string>& interfaces)
+{
+    if (depth <= 0)
+    {
+        depth = std::numeric_limits<int32_t>::max();
+    }
+    // Interfaces need to be sorted for intersect to function
+    std::sort(interfaces.begin(), interfaces.end());
+
+    // reqPath is now guaranteed to have a trailing "/" while reqPathStripped
+    // will be guaranteed not to have a trailing "/"
+    if (!reqPath.ends_with("/"))
+    {
+        reqPath += "/";
+    }
+    std::string_view reqPathStripped =
+        std::string_view(reqPath).substr(0, reqPath.size() - 1);
+
+    if (!reqPathStripped.empty() &&
+        interfaceMap.find(reqPathStripped) == interfaceMap.end())
+    {
+        throw sdbusplus::xyz::openbmc_project::Common::Error::
+            ResourceNotFound();
+    }
+
+    std::vector<InterfaceMapType::value_type> ret;
+    for (const auto& objectPath : interfaceMap)
+    {
+        const auto& thisPath = objectPath.first;
+
+        // Skip exact match on stripped search term
+        if (thisPath == reqPathStripped)
+        {
+            continue;
+        }
+
+        if (thisPath.starts_with(reqPath))
+        {
+            // count the number of slashes past the stripped search term
+            int32_t thisDepth = std::count(
+                thisPath.begin() + reqPathStripped.size(), thisPath.end(), '/');
+            if (thisDepth <= depth)
+            {
+                for (const auto& interfaceMap : objectPath.second)
+                {
+                    std::vector<std::string> output(std::min(
+                        interfaces.size(), interfaceMap.second.size()));
+                    // Return iterator points at the first output elemtn,
+                    // meaning that there are no intersections.
+                    if (std::set_intersection(
+                            interfaces.begin(), interfaces.end(),
+                            interfaceMap.second.begin(),
+                            interfaceMap.second.end(),
+                            output.begin()) != output.begin() ||
+                        interfaces.empty())
+                    {
+                        addObjectMapResult(ret, thisPath, interfaceMap);
+                    }
+                }
+            }
+        }
+    }
+
+    return ret;
+}
+
+std::vector<std::string> getSubTreePaths(const InterfaceMapType& interfaceMap,
+                                         std::string reqPath, int32_t depth,
+                                         std::vector<std::string>& interfaces)
+{
+    if (depth <= 0)
+    {
+        depth = std::numeric_limits<int32_t>::max();
+    }
+    // Interfaces need to be sorted for intersect to function
+    std::sort(interfaces.begin(), interfaces.end());
+
+    // reqPath is now guaranteed to have a trailing "/" while reqPathStripped
+    // will be guaranteed not to have a trailing "/"
+    if (!reqPath.ends_with("/"))
+    {
+        reqPath += "/";
+    }
+    std::string_view reqPathStripped =
+        std::string_view(reqPath).substr(0, reqPath.size() - 1);
+
+    if (!reqPathStripped.empty() &&
+        interfaceMap.find(reqPathStripped) == interfaceMap.end())
+    {
+        throw sdbusplus::xyz::openbmc_project::Common::Error::
+            ResourceNotFound();
+    }
+
+    std::vector<std::string> ret;
+    for (const auto& objectPath : interfaceMap)
+    {
+        const auto& thisPath = objectPath.first;
+
+        // Skip exact match on stripped search term
+        if (thisPath == reqPathStripped)
+        {
+            continue;
+        }
+
+        if (thisPath.starts_with(reqPath))
+        {
+            // count the number of slashes past the stripped search term
+            int thisDepth = std::count(
+                thisPath.begin() + reqPathStripped.size(), thisPath.end(), '/');
+            if (thisDepth <= depth)
+            {
+                bool add = interfaces.empty();
+                for (const auto& interfaceMap : objectPath.second)
+                {
+                    std::vector<std::string> output(std::min(
+                        interfaces.size(), interfaceMap.second.size()));
+                    // Return iterator points at the first output elemtn,
+                    // meaning that there are no intersections.
+                    if (std::set_intersection(interfaces.begin(),
+                                              interfaces.end(),
+                                              interfaceMap.second.begin(),
+                                              interfaceMap.second.end(),
+                                              output.begin()) != output.begin())
+                    {
+                        add = true;
+                        break;
+                    }
+                }
+                if (add)
+                {
+                    // TODO(ed) this is a copy
+                    ret.emplace_back(thisPath);
+                }
+            }
+        }
+    }
+
+    return ret;
+}
diff --git a/src/handler.hpp b/src/handler.hpp
new file mode 100644
index 0000000..74cb234
--- /dev/null
+++ b/src/handler.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "types.hpp"
+
+#include <string>
+#include <vector>
+
+void addObjectMapResult(std::vector<InterfaceMapType::value_type>& objectMap,
+                        const std::string& objectPath,
+                        const ConnectionNames::value_type& interfaceMap);
+
+std::vector<InterfaceMapType::value_type>
+    getAncestors(const InterfaceMapType& interfaceMap, std::string reqPath,
+                 std::vector<std::string>& interfaces);
+
+ConnectionNames getObject(const InterfaceMapType& interfaceMap,
+                          const std::string& path,
+                          std::vector<std::string>& interfaces);
+
+std::vector<InterfaceMapType::value_type>
+    getSubTree(const InterfaceMapType& interfaceMap, std::string reqPath,
+               int32_t depth, std::vector<std::string>& interfaces);
+
+std::vector<std::string> getSubTreePaths(const InterfaceMapType& interfaceMap,
+                                         std::string reqPath, int32_t depth,
+                                         std::vector<std::string>& interfaces);
diff --git a/src/main.cpp b/src/main.cpp
index 991852e..5f43767 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,4 +1,5 @@
 #include "associations.hpp"
+#include "handler.hpp"
 #include "processing.hpp"
 #include "types.hpp"
 
@@ -279,27 +280,6 @@
     }
 }
 
-// TODO(ed) replace with std::set_intersection once c++17 is available
-template <class InputIt1, class InputIt2>
-bool intersect(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2)
-{
-    while (first1 != last1 && first2 != last2)
-    {
-        if (*first1 < *first2)
-        {
-            ++first1;
-            continue;
-        }
-        if (*first2 < *first1)
-        {
-            ++first2;
-            continue;
-        }
-        return true;
-    }
-    return false;
-}
-
 void doListNames(
     boost::asio::io_context& io, InterfaceMapType& interfaceMap,
     sdbusplus::asio::connection* systemBus,
@@ -342,32 +322,6 @@
         "ListNames");
 }
 
-void addObjectMapResult(std::vector<InterfaceMapType::value_type>& objectMap,
-                        const std::string& objectPath,
-                        const ConnectionNames::value_type& interfaceMap)
-{
-    // Adds an object path/service name/interface list entry to
-    // the results of GetSubTree and GetAncestors.
-    // If an entry for the object path already exists, just add the
-    // service name and interfaces to that entry, otherwise create
-    // a new entry.
-    auto entry = std::find_if(
-        objectMap.begin(), objectMap.end(),
-        [&objectPath](const auto& i) { return objectPath == i.first; });
-
-    if (entry != objectMap.end())
-    {
-        entry->second.emplace(interfaceMap);
-    }
-    else
-    {
-        InterfaceMapType::value_type object;
-        object.first = objectPath;
-        object.second.emplace(interfaceMap);
-        objectMap.push_back(object);
-    }
-}
-
 // Remove parents of the passed in path that:
 // 1) Only have the 3 default interfaces on them
 //    - Means D-Bus created these, not application code,
@@ -430,223 +384,6 @@
     }
 }
 
-std::vector<InterfaceMapType::value_type>
-    getAncestors(const InterfaceMapType& interfaceMap, std::string reqPath,
-                 std::vector<std::string>& interfaces)
-{
-    // Interfaces need to be sorted for intersect to function
-    std::sort(interfaces.begin(), interfaces.end());
-
-    if (reqPath.ends_with("/"))
-    {
-        reqPath.pop_back();
-    }
-    if (!reqPath.empty() && interfaceMap.find(reqPath) == interfaceMap.end())
-    {
-        throw sdbusplus::xyz::openbmc_project::Common::Error::
-            ResourceNotFound();
-    }
-
-    std::vector<InterfaceMapType::value_type> ret;
-    for (const auto& objectPath : interfaceMap)
-    {
-        const auto& thisPath = objectPath.first;
-
-        if (reqPath == thisPath)
-        {
-            continue;
-        }
-
-        if (reqPath.starts_with(thisPath))
-        {
-            if (interfaces.empty())
-            {
-                ret.emplace_back(objectPath);
-            }
-            else
-            {
-                for (const auto& interfaceMap : objectPath.second)
-                {
-                    if (intersect(interfaces.begin(), interfaces.end(),
-                                  interfaceMap.second.begin(),
-                                  interfaceMap.second.end()))
-                    {
-                        addObjectMapResult(ret, thisPath, interfaceMap);
-                    }
-                }
-            }
-        }
-    }
-
-    return ret;
-}
-
-ConnectionNames getObject(const InterfaceMapType& interfaceMap,
-                          const std::string& path,
-                          std::vector<std::string>& interfaces)
-{
-    ConnectionNames results;
-
-    // Interfaces need to be sorted for intersect to function
-    std::sort(interfaces.begin(), interfaces.end());
-    auto pathRef = interfaceMap.find(path);
-    if (pathRef == interfaceMap.end())
-    {
-        throw sdbusplus::xyz::openbmc_project::Common::Error::
-            ResourceNotFound();
-    }
-    if (interfaces.empty())
-    {
-        return pathRef->second;
-    }
-    for (const auto& interfaceMap : pathRef->second)
-    {
-        if (intersect(interfaces.begin(), interfaces.end(),
-                      interfaceMap.second.begin(), interfaceMap.second.end()))
-        {
-            results.emplace(interfaceMap.first, interfaceMap.second);
-        }
-    }
-
-    if (results.empty())
-    {
-        throw sdbusplus::xyz::openbmc_project::Common::Error::
-            ResourceNotFound();
-    }
-
-    return results;
-}
-
-std::vector<InterfaceMapType::value_type>
-    getSubTree(const InterfaceMapType& interfaceMap, std::string reqPath,
-               int32_t depth, std::vector<std::string>& interfaces)
-{
-    if (depth <= 0)
-    {
-        depth = std::numeric_limits<int32_t>::max();
-    }
-    // Interfaces need to be sorted for intersect to function
-    std::sort(interfaces.begin(), interfaces.end());
-
-    // reqPath is now guaranteed to have a trailing "/" while reqPathStripped
-    // will be guaranteed not to have a trailing "/"
-    if (!reqPath.ends_with("/"))
-    {
-        reqPath += "/";
-    }
-    std::string_view reqPathStripped =
-        std::string_view(reqPath).substr(0, reqPath.size() - 1);
-
-    if (!reqPathStripped.empty() &&
-        interfaceMap.find(reqPathStripped) == interfaceMap.end())
-    {
-        throw sdbusplus::xyz::openbmc_project::Common::Error::
-            ResourceNotFound();
-    }
-
-    std::vector<InterfaceMapType::value_type> ret;
-    for (const auto& objectPath : interfaceMap)
-    {
-        const auto& thisPath = objectPath.first;
-
-        // Skip exact match on stripped search term
-        if (thisPath == reqPathStripped)
-        {
-            continue;
-        }
-
-        if (thisPath.starts_with(reqPath))
-        {
-            // count the number of slashes past the stripped search term
-            int32_t thisDepth = std::count(
-                thisPath.begin() + reqPathStripped.size(), thisPath.end(), '/');
-            if (thisDepth <= depth)
-            {
-                for (const auto& interfaceMap : objectPath.second)
-                {
-                    if (intersect(interfaces.begin(), interfaces.end(),
-                                  interfaceMap.second.begin(),
-                                  interfaceMap.second.end()) ||
-                        interfaces.empty())
-                    {
-                        addObjectMapResult(ret, thisPath, interfaceMap);
-                    }
-                }
-            }
-        }
-    }
-
-    return ret;
-}
-
-std::vector<std::string> getSubTreePaths(const InterfaceMapType& interfaceMap,
-                                         std::string reqPath, int32_t depth,
-                                         std::vector<std::string>& interfaces)
-{
-    if (depth <= 0)
-    {
-        depth = std::numeric_limits<int32_t>::max();
-    }
-    // Interfaces need to be sorted for intersect to function
-    std::sort(interfaces.begin(), interfaces.end());
-
-    // reqPath is now guaranteed to have a trailing "/" while reqPathStripped
-    // will be guaranteed not to have a trailing "/"
-    if (!reqPath.ends_with("/"))
-    {
-        reqPath += "/";
-    }
-    std::string_view reqPathStripped =
-        std::string_view(reqPath).substr(0, reqPath.size() - 1);
-
-    if (!reqPathStripped.empty() &&
-        interfaceMap.find(reqPathStripped) == interfaceMap.end())
-    {
-        throw sdbusplus::xyz::openbmc_project::Common::Error::
-            ResourceNotFound();
-    }
-
-    std::vector<std::string> ret;
-    for (const auto& objectPath : interfaceMap)
-    {
-        const auto& thisPath = objectPath.first;
-
-        // Skip exact match on stripped search term
-        if (thisPath == reqPathStripped)
-        {
-            continue;
-        }
-
-        if (thisPath.starts_with(reqPath))
-        {
-            // count the number of slashes past the stripped search term
-            int thisDepth = std::count(
-                thisPath.begin() + reqPathStripped.size(), thisPath.end(), '/');
-            if (thisDepth <= depth)
-            {
-                bool add = interfaces.empty();
-                for (const auto& interfaceMap : objectPath.second)
-                {
-                    if (intersect(interfaces.begin(), interfaces.end(),
-                                  interfaceMap.second.begin(),
-                                  interfaceMap.second.end()))
-                    {
-                        add = true;
-                        break;
-                    }
-                }
-                if (add)
-                {
-                    // TODO(ed) this is a copy
-                    ret.emplace_back(thisPath);
-                }
-            }
-        }
-    }
-
-    return ret;
-}
-
 int main()
 {
     boost::asio::io_context io;
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