topology: add initial module

To use this module, add information about boards and their exposes
records and then request a map of what resulting associations should be
exposed on D-Bus.

Tested: Unit tests pass

Signed-off-by: Benjamin Fair <benjaminfair@google.com>
Change-Id: I42cd79407ab476599f5010c11cc146a569694b24
diff --git a/test/test_topology.cpp b/test/test_topology.cpp
new file mode 100644
index 0000000..76ab54a
--- /dev/null
+++ b/test/test_topology.cpp
@@ -0,0 +1,187 @@
+#include "topology.hpp"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using ::testing::UnorderedElementsAre;
+
+const std::string subchassisPath =
+    "/xyz/openbmc_project/inventory/system/chassis/Subchassis";
+const std::string superchassisPath =
+    "/xyz/openbmc_project/inventory/system/chassis/Superchassis";
+
+const Association subchassisAssoc =
+    std::make_tuple("contained_by", "containing", superchassisPath);
+
+const nlohmann::json subchassisExposesItem = nlohmann::json::parse(R"(
+    {
+        "ConnectsToType": "BackplanePort",
+        "Name": "MyDownstreamPort",
+        "Type": "DownstreamPort"
+    }
+)");
+
+const nlohmann::json superchassisExposesItem = nlohmann::json::parse(R"(
+    {
+        "Name": "MyBackplanePort",
+        "Type": "BackplanePort"
+    }
+)");
+
+const nlohmann::json otherExposesItem = nlohmann::json::parse(R"(
+    {
+        "Name": "MyExposes",
+        "Type": "OtherType"
+    }
+)");
+
+TEST(Topology, Empty)
+{
+    Topology topo;
+
+    auto assocs = topo.getAssocs();
+
+    EXPECT_EQ(assocs.size(), 0);
+}
+
+TEST(Topology, EmptyExposes)
+{
+    Topology topo;
+
+    topo.addBoard(subchassisPath, "Chassis", nlohmann::json());
+    topo.addBoard(superchassisPath, "Chassis", nlohmann::json());
+
+    auto assocs = topo.getAssocs();
+
+    EXPECT_EQ(assocs.size(), 0);
+}
+
+TEST(Topology, MissingConnectsTo)
+{
+    const nlohmann::json subchassisMissingConnectsTo = nlohmann::json::parse(R"(
+        {
+            "Name": "MyDownstreamPort",
+            "Type": "DownstreamPort"
+        }
+    )");
+
+    Topology topo;
+
+    topo.addBoard(subchassisPath, "Chassis", subchassisMissingConnectsTo);
+    topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+
+    auto assocs = topo.getAssocs();
+
+    EXPECT_EQ(assocs.size(), 0);
+}
+
+TEST(Topology, OtherExposes)
+{
+    Topology topo;
+
+    topo.addBoard(subchassisPath, "Chassis", otherExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", otherExposesItem);
+
+    auto assocs = topo.getAssocs();
+
+    EXPECT_EQ(assocs.size(), 0);
+}
+
+TEST(Topology, NoMatchSubchassis)
+{
+    Topology topo;
+
+    topo.addBoard(subchassisPath, "Chassis", otherExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+
+    auto assocs = topo.getAssocs();
+
+    EXPECT_EQ(assocs.size(), 0);
+}
+
+TEST(Topology, NoMatchSuperchassis)
+{
+    Topology topo;
+
+    topo.addBoard(subchassisPath, "Chassis", subchassisExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", otherExposesItem);
+
+    auto assocs = topo.getAssocs();
+
+    EXPECT_EQ(assocs.size(), 0);
+}
+
+TEST(Topology, Basic)
+{
+    Topology topo;
+
+    topo.addBoard(subchassisPath, "Chassis", subchassisExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+
+    auto assocs = topo.getAssocs();
+
+    EXPECT_EQ(assocs.size(), 1);
+    EXPECT_EQ(assocs[subchassisPath].size(), 1);
+    EXPECT_EQ(assocs[subchassisPath][0], subchassisAssoc);
+}
+
+TEST(Topology, 2Subchassis)
+{
+    Topology topo;
+
+    topo.addBoard(subchassisPath, "Chassis", subchassisExposesItem);
+    topo.addBoard(subchassisPath + "2", "Chassis", subchassisExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+
+    auto assocs = topo.getAssocs();
+
+    EXPECT_EQ(assocs.size(), 2);
+    EXPECT_EQ(assocs[subchassisPath].size(), 1);
+    EXPECT_EQ(assocs[subchassisPath][0], subchassisAssoc);
+    EXPECT_EQ(assocs[subchassisPath + "2"].size(), 1);
+    EXPECT_EQ(assocs[subchassisPath + "2"][0], subchassisAssoc);
+}
+
+TEST(Topology, 2Superchassis)
+{
+    const Association subchassisAssoc2 =
+        std::make_tuple("contained_by", "containing", superchassisPath + "2");
+
+    Topology topo;
+
+    topo.addBoard(subchassisPath, "Chassis", subchassisExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+    topo.addBoard(superchassisPath + "2", "Chassis", superchassisExposesItem);
+
+    auto assocs = topo.getAssocs();
+
+    EXPECT_EQ(assocs.size(), 1);
+    EXPECT_EQ(assocs[subchassisPath].size(), 2);
+
+    EXPECT_THAT(assocs[subchassisPath],
+                UnorderedElementsAre(subchassisAssoc, subchassisAssoc2));
+}
+
+TEST(Topology, 2SuperchassisAnd2Subchassis)
+{
+    const Association subchassisAssoc2 =
+        std::make_tuple("contained_by", "containing", superchassisPath + "2");
+
+    Topology topo;
+
+    topo.addBoard(subchassisPath, "Chassis", subchassisExposesItem);
+    topo.addBoard(subchassisPath + "2", "Chassis", subchassisExposesItem);
+    topo.addBoard(superchassisPath, "Chassis", superchassisExposesItem);
+    topo.addBoard(superchassisPath + "2", "Chassis", superchassisExposesItem);
+
+    auto assocs = topo.getAssocs();
+
+    EXPECT_EQ(assocs.size(), 2);
+    EXPECT_EQ(assocs[subchassisPath].size(), 2);
+    EXPECT_EQ(assocs[subchassisPath + "2"].size(), 2);
+
+    EXPECT_THAT(assocs[subchassisPath],
+                UnorderedElementsAre(subchassisAssoc, subchassisAssoc2));
+    EXPECT_THAT(assocs[subchassisPath + "2"],
+                UnorderedElementsAre(subchassisAssoc, subchassisAssoc2));
+}