unit-test: Test removeAssociations interface

Testing: Verified code coverage shows 100% of new interface

Change-Id: I517acc02b06bbff971921e66a697fb297fde45c6
Signed-off-by: Andrew Geissler <geissonator@yahoo.com>
diff --git a/src/associations.hpp b/src/associations.hpp
index 41df157..8ebef43 100644
--- a/src/associations.hpp
+++ b/src/associations.hpp
@@ -5,6 +5,7 @@
 #include <memory>
 #include <sdbusplus/asio/object_server.hpp>
 #include <string>
+#include <tuple>
 #include <vector>
 
 //  Associations and some metadata are stored in associationInterfaces.
@@ -15,6 +16,7 @@
 static constexpr auto endpointsPos = 1;
 using Endpoints = std::vector<std::string>;
 
+// map[interface path: tuple[dbus_interface,vector[endpoint paths]]]
 using AssociationInterfaces = boost::container::flat_map<
     std::string,
     std::tuple<std::shared_ptr<sdbusplus::asio::dbus_interface>, Endpoints>>;
diff --git a/src/test/Makefile.am.include b/src/test/Makefile.am.include
index 23ba45a..b54ddbf 100644
--- a/src/test/Makefile.am.include
+++ b/src/test/Makefile.am.include
@@ -3,6 +3,10 @@
 src_test_need_to_introspect_SOURCES = %reldir%/need_to_introspect.cpp \
 	src/processing.cpp
 
+src_test_associations_SOURCES = %reldir%/associations.cpp \
+    src/associations.cpp
+
 check_PROGRAMS += \
 	%reldir%/well_known \
-	%reldir%/need_to_introspect
+	%reldir%/need_to_introspect \
+	%reldir%/associations
diff --git a/src/test/associations.cpp b/src/test/associations.cpp
new file mode 100644
index 0000000..d5f86f4
--- /dev/null
+++ b/src/test/associations.cpp
@@ -0,0 +1,162 @@
+#include "src/associations.hpp"
+
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <gtest/gtest.h>
+
+class TestAssociations : public testing::Test
+{
+  protected:
+    // Make this global to the whole test suite since we want to share
+    // the asio::object_server accross the test cases
+    // NOTE - latest googltest changed to SetUpTestSuite()
+    static void SetUpTestCase()
+    {
+        boost::asio::io_context io;
+        auto conn = std::make_shared<sdbusplus::asio::connection>(io);
+
+        conn->request_name("xyz.openbmc_project.ObjMgr.Test");
+        server = new sdbusplus::asio::object_server(conn);
+    }
+
+    // NOTE - latest googltest changed to TearDownTestSuite()
+    static void TearDownTestCase()
+    {
+        delete server;
+        server = nullptr;
+    }
+
+    static sdbusplus::asio::object_server* server;
+};
+
+sdbusplus::asio::object_server* TestAssociations::server = nullptr;
+
+const std::string DEFAULT_SOURCE_PATH = "/logging/entry/1";
+const std::string DEFAULT_DBUS_SVC = "xyz.openbmc_project.New.Interface";
+const std::string DEFAULT_FWD_PATH = {DEFAULT_SOURCE_PATH + "/" + "inventory"};
+const std::string DEFAULT_ENDPOINT =
+    "/xyz/openbmc_project/inventory/system/chassis";
+const std::string DEFAULT_REV_PATH = {DEFAULT_ENDPOINT + "/" + "error"};
+const std::string EXTRA_ENDPOINT = "/xyz/openbmc_project/differnt/endpoint";
+
+// Create a default AssociationOwnersType object
+AssociationOwnersType createDefaultOwnerAssociation()
+{
+    AssociationPaths assocPathMap = {{DEFAULT_FWD_PATH, {DEFAULT_ENDPOINT}},
+                                     {DEFAULT_REV_PATH, {DEFAULT_SOURCE_PATH}}};
+    boost::container::flat_map<std::string, AssociationPaths> serviceMap = {
+        {DEFAULT_DBUS_SVC, assocPathMap}};
+    AssociationOwnersType ownerAssoc = {{DEFAULT_SOURCE_PATH, serviceMap}};
+    return ownerAssoc;
+}
+
+// Create a default AssociationInterfaces object
+AssociationInterfaces
+    createDefaultInterfaceAssociation(sdbusplus::asio::object_server* server)
+{
+    AssociationInterfaces interfaceAssoc;
+
+    auto& iface = interfaceAssoc[DEFAULT_FWD_PATH];
+    auto& endpoints = std::get<endpointsPos>(iface);
+    endpoints.push_back(DEFAULT_ENDPOINT);
+    server->add_interface(DEFAULT_FWD_PATH, DEFAULT_DBUS_SVC);
+
+    auto& iface2 = interfaceAssoc[DEFAULT_REV_PATH];
+    auto& endpoints2 = std::get<endpointsPos>(iface2);
+    endpoints2.push_back(DEFAULT_SOURCE_PATH);
+    server->add_interface(DEFAULT_REV_PATH, DEFAULT_DBUS_SVC);
+
+    return interfaceAssoc;
+}
+
+// Just add an extra endpoint to the first association
+void addEndpointToInterfaceAssociation(AssociationInterfaces& interfaceAssoc)
+{
+    auto iface = interfaceAssoc[DEFAULT_FWD_PATH];
+    auto endpoints = std::get<endpointsPos>(iface);
+    endpoints.push_back(EXTRA_ENDPOINT);
+}
+
+// Verify call when path is not in associated owners
+TEST_F(TestAssociations, SourcePathNotInAssociations)
+{
+    EXPECT_NE(nullptr, server);
+    std::string sourcePath = "/xyz/openbmc_project/no/association";
+    AssociationOwnersType assocOwners;
+    AssociationInterfaces assocInterfaces;
+
+    removeAssociation(sourcePath, DEFAULT_DBUS_SVC, *server, assocOwners,
+                      assocInterfaces);
+}
+
+// Verify call when owner is not in associated owners
+TEST_F(TestAssociations, OwnerNotInAssociations)
+{
+    AssociationInterfaces assocInterfaces;
+
+    auto assocOwners = createDefaultOwnerAssociation();
+
+    removeAssociation(DEFAULT_SOURCE_PATH, DEFAULT_DBUS_SVC, *server,
+                      assocOwners, assocInterfaces);
+}
+
+// Verify call when path is not in associated interfaces
+TEST_F(TestAssociations, PathNotInAssocInterfaces)
+{
+    AssociationInterfaces assocInterfaces;
+
+    auto assocOwners = createDefaultOwnerAssociation();
+
+    removeAssociation(DEFAULT_SOURCE_PATH, DEFAULT_DBUS_SVC, *server,
+                      assocOwners, assocInterfaces);
+
+    EXPECT_TRUE(assocOwners.empty());
+}
+
+// Verify call when path is in associated interfaces
+TEST_F(TestAssociations, PathIsInAssociatedInterfaces)
+{
+    // Build up these objects so that an associated interface will match
+    // with the associated owner being removed
+    auto assocOwners = createDefaultOwnerAssociation();
+    auto assocInterfaces = createDefaultInterfaceAssociation(server);
+
+    removeAssociation(DEFAULT_SOURCE_PATH, DEFAULT_DBUS_SVC, *server,
+                      assocOwners, assocInterfaces);
+
+    // Verify owner association was deleted
+    EXPECT_TRUE(assocOwners.empty());
+
+    // Verify endpoint was deleted from interface association
+    auto intfEndpoints =
+        std::get<endpointsPos>(assocInterfaces[DEFAULT_FWD_PATH]);
+    EXPECT_EQ(intfEndpoints.size(), 0);
+    intfEndpoints = std::get<endpointsPos>(assocInterfaces[DEFAULT_REV_PATH]);
+    EXPECT_EQ(intfEndpoints.size(), 0);
+}
+
+// Verify call when path is in associated interfaces, with extra endpoints
+TEST_F(TestAssociations, PathIsInAssociatedInterfacesExtraEndpoints)
+{
+    // Build up these objects so that an associated interface will match
+    // with the associated owner being removed
+    auto assocOwners = createDefaultOwnerAssociation();
+    auto assocInterfaces = createDefaultInterfaceAssociation(server);
+
+    // Add another endpoint to the assoc interfaces
+    addEndpointToInterfaceAssociation(assocInterfaces);
+
+    removeAssociation(DEFAULT_SOURCE_PATH, DEFAULT_DBUS_SVC, *server,
+                      assocOwners, assocInterfaces);
+
+    // Verify owner association was deleted
+    EXPECT_TRUE(assocOwners.empty());
+
+    // Verify all endpoints are deleted since source path was deleted
+    auto intfEndpoints =
+        std::get<endpointsPos>(assocInterfaces[DEFAULT_FWD_PATH]);
+    EXPECT_EQ(intfEndpoints.size(), 0);
+    intfEndpoints = std::get<endpointsPos>(assocInterfaces[DEFAULT_REV_PATH]);
+    EXPECT_EQ(intfEndpoints.size(), 0);
+}