unit-test: Create initial associations file

Make interface more unit testable by moving to separate
file and documenting it.

Change-Id: Ia27f33d706c62a0011790ec94185dd6be3922f21
Signed-off-by: Andrew Geissler <geissonator@yahoo.com>
diff --git a/Makefile.am b/Makefile.am
index 62fe99e..668a529 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -26,7 +26,8 @@
 libmapper_la_LDFLAGS = $(SYSTEMD_LIBS) -version-info 1:0:0 -shared
 libmapper_la_CFLAGS = $(SYSTEMD_CFLAGS)
 
-mapperx_SOURCES = src/main.cpp src/argument.cpp src/processing.cpp
+mapperx_SOURCES = src/main.cpp src/argument.cpp src/processing.cpp \
+	src/associations.cpp
 mapperx_LDFLAGS = $(SDBUSPLUS_LIBS) -pthread -ltinyxml2
 mapperx_CXXFLAGS = $(SYSTEMD_CFLAGS) -DBOOST_SYSTEM_NO_DEPRECATED -DBOOST_ERROR_CODE_HEADER_ONLY -DBOOST_ALL_NO_LIB
 
diff --git a/src/associations.cpp b/src/associations.cpp
new file mode 100644
index 0000000..3ae7db4
--- /dev/null
+++ b/src/associations.cpp
@@ -0,0 +1,76 @@
+#include "associations.hpp"
+
+void removeAssociation(const std::string& sourcePath, const std::string& owner,
+                       sdbusplus::asio::object_server& server,
+                       AssociationOwnersType& assocOwners,
+                       AssociationInterfaces& assocInterfaces)
+{
+    // Use associationOwners to find the association paths and endpoints
+    // that the passed in object path and service own.  Remove all of
+    // these endpoints from the actual association D-Bus objects, and if
+    // the endpoints property is then empty, the whole association object
+    // can be removed.  Note there can be multiple services that own an
+    // association, and also that sourcePath is the path of the object
+    // that contains the org.openbmc.Associations interface and not the
+    // association path itself.
+
+    // Find the services that have associations for this object path
+    auto owners = assocOwners.find(sourcePath);
+    if (owners == assocOwners.end())
+    {
+        return;
+    }
+
+    // Find the association paths and endpoints owned by this object
+    // path for this service.
+    auto assocs = owners->second.find(owner);
+    if (assocs == owners->second.end())
+    {
+        return;
+    }
+
+    for (const auto& [assocPath, endpointsToRemove] : assocs->second)
+    {
+        // Get the association D-Bus object for this assocPath
+        auto target = assocInterfaces.find(assocPath);
+        if (target == assocInterfaces.end())
+        {
+            continue;
+        }
+
+        // Remove the entries in the endpoints D-Bus property for this
+        // path/owner/association-path.
+        auto& existingEndpoints = std::get<endpointsPos>(target->second);
+        for (const auto& endpointToRemove : endpointsToRemove)
+        {
+            auto e = std::find(existingEndpoints.begin(),
+                               existingEndpoints.end(), endpointToRemove);
+
+            if (e != existingEndpoints.end())
+            {
+                existingEndpoints.erase(e);
+            }
+        }
+
+        // Remove the association from D-Bus if there are no more endpoints,
+        // otherwise just update the endpoints property.
+        if (existingEndpoints.empty())
+        {
+            server.remove_interface(std::get<ifacePos>(target->second));
+            std::get<ifacePos>(target->second) = nullptr;
+            std::get<endpointsPos>(target->second).clear();
+        }
+        else
+        {
+            std::get<ifacePos>(target->second)
+                ->set_property("endpoints", existingEndpoints);
+        }
+    }
+
+    // Remove the associationOwners entries for this owning path/service.
+    owners->second.erase(assocs);
+    if (owners->second.empty())
+    {
+        assocOwners.erase(owners);
+    }
+}
diff --git a/src/associations.hpp b/src/associations.hpp
new file mode 100644
index 0000000..41df157
--- /dev/null
+++ b/src/associations.hpp
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <memory>
+#include <sdbusplus/asio/object_server.hpp>
+#include <string>
+#include <vector>
+
+//  Associations and some metadata are stored in associationInterfaces.
+//  The fields are:
+//   * ifacePos - holds the D-Bus interface object
+//   * endpointsPos - holds the endpoints array that shadows the property
+static constexpr auto ifacePos = 0;
+static constexpr auto endpointsPos = 1;
+using Endpoints = std::vector<std::string>;
+
+using AssociationInterfaces = boost::container::flat_map<
+    std::string,
+    std::tuple<std::shared_ptr<sdbusplus::asio::dbus_interface>, Endpoints>>;
+
+// The associationOwners map contains information about creators of
+// associations, so that when a org.openbmc.Association interface is
+// removed or its 'associations' property is changed, the mapper owned
+// association objects can be correctly handled.  It is a map of the
+// object path of the org.openbmc.Association owner to a map of the
+// service the path is owned by, to a map of the association objects to
+// their endpoint paths:
+// map[ownerPath : map[service : map[assocPath : [endpoint paths]]]
+// For example:
+// [/logging/entry/1 :
+//   [xyz.openbmc_project.Logging :
+//     [/logging/entry/1/callout : [/system/cpu0],
+//      /system/cpu0/fault : [/logging/entry/1]]]]
+
+using AssociationPaths =
+    boost::container::flat_map<std::string,
+                               boost::container::flat_set<std::string>>;
+
+using AssociationOwnersType = boost::container::flat_map<
+    std::string, boost::container::flat_map<std::string, AssociationPaths>>;
+
+/** @brief Remove input association
+ *
+ * @param[in] sourcePath          - Path of the object that contains the
+ *                                  org.openbmc.Associations
+ * @param[in] owner               - The Dbus service having its associations
+ *                                  removed
+ * @param[in,out] server          - sdbus system object
+ * @param[in,out] assocOwners     - Owners of associations
+ * @param[in,out] assocInterfaces - Associations endpoints
+ *
+ * @return Void, server, assocOwners, and assocInterfaces updated if needed
+ */
+void removeAssociation(const std::string& sourcePath, const std::string& owner,
+                       sdbusplus::asio::object_server& server,
+                       AssociationOwnersType& assocOwners,
+                       AssociationInterfaces& assocInterfaces);
diff --git a/src/main.cpp b/src/main.cpp
index 801d910..e96ffe3 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,3 +1,4 @@
+#include "associations.hpp"
 #include "processing.hpp"
 #include "src/argument.hpp"
 
@@ -29,40 +30,7 @@
 
 using Association = std::tuple<std::string, std::string, std::string>;
 
-//  Associations and some metadata are stored in associationInterfaces.
-//  The fields are:
-//   * ifacePos - holds the D-Bus interface object
-//   * endpointsPos - holds the endpoints array that shadows the property
-static constexpr auto ifacePos = 0;
-static constexpr auto endpointsPos = 1;
-using Endpoints = std::vector<std::string>;
-using AssociationInterfaces = boost::container::flat_map<
-    std::string,
-    std::tuple<std::shared_ptr<sdbusplus::asio::dbus_interface>, Endpoints>>;
-
 AssociationInterfaces associationInterfaces;
-
-// The associationOwners map contains information about creators of
-// associations, so that when a org.openbmc.Association interface is
-// removed or its 'associations' property is changed, the mapper owned
-// association objects can be correctly handled.  It is a map of the
-// object path of the org.openbmc.Association owner to a map of the
-// service the path is owned by, to a map of the association objects to
-// their endpoint paths:
-// map[ownerPath : map[service : map[assocPath : [endpoint paths]]]
-// For example:
-// [/logging/entry/1 :
-//   [xyz.openbmc_project.Logging :
-//     [/logging/entry/1/callout : [/system/cpu0],
-//      /system/cpu0/fault : [/logging/entry/1]]]]
-
-using AssociationPaths =
-    boost::container::flat_map<std::string,
-                               boost::container::flat_set<std::string>>;
-
-using AssociationOwnersType = boost::container::flat_map<
-    std::string, boost::container::flat_map<std::string, AssociationPaths>>;
-
 AssociationOwnersType associationOwners;
 
 static WhiteBlackList service_whitelist;
@@ -365,81 +333,6 @@
     }
 }
 
-void removeAssociation(const std::string& sourcePath, const std::string& owner,
-                       sdbusplus::asio::object_server& server,
-                       AssociationOwnersType& assocOwners,
-                       AssociationInterfaces& assocInterfaces)
-{
-    // Use associationOwners to find the association paths and endpoints
-    // that the passed in object path and service own.  Remove all of
-    // these endpoints from the actual association D-Bus objects, and if
-    // the endpoints property is then empty, the whole association object
-    // can be removed.  Note there can be multiple services that own an
-    // association, and also that sourcePath is the path of the object
-    // that contains the org.openbmc.Associations interface and not the
-    // association path itself.
-
-    // Find the services that have associations for this object path
-    auto owners = assocOwners.find(sourcePath);
-    if (owners == assocOwners.end())
-    {
-        return;
-    }
-
-    // Find the association paths and endpoints owned by this object
-    // path for this service.
-    auto assocs = owners->second.find(owner);
-    if (assocs == owners->second.end())
-    {
-        return;
-    }
-
-    for (const auto& [assocPath, endpointsToRemove] : assocs->second)
-    {
-        // Get the association D-Bus object for this assocPath
-        auto target = assocInterfaces.find(assocPath);
-        if (target == assocInterfaces.end())
-        {
-            continue;
-        }
-
-        // Remove the entries in the endpoints D-Bus property for this
-        // path/owner/association-path.
-        auto& existingEndpoints = std::get<endpointsPos>(target->second);
-        for (const auto& endpointToRemove : endpointsToRemove)
-        {
-            auto e = std::find(existingEndpoints.begin(),
-                               existingEndpoints.end(), endpointToRemove);
-
-            if (e != existingEndpoints.end())
-            {
-                existingEndpoints.erase(e);
-            }
-        }
-
-        // Remove the association from D-Bus if there are no more endpoints,
-        // otherwise just update the endpoints property.
-        if (existingEndpoints.empty())
-        {
-            server.remove_interface(std::get<ifacePos>(target->second));
-            std::get<ifacePos>(target->second) = nullptr;
-            std::get<endpointsPos>(target->second).clear();
-        }
-        else
-        {
-            std::get<ifacePos>(target->second)
-                ->set_property("endpoints", existingEndpoints);
-        }
-    }
-
-    // Remove the associationOwners entries for this owning path/service.
-    owners->second.erase(assocs);
-    if (owners->second.empty())
-    {
-        associationOwners.erase(owners);
-    }
-}
-
 void do_associations(sdbusplus::asio::connection* system_bus,
                      sdbusplus::asio::object_server& objectServer,
                      const std::string& processName, const std::string& path)