Test: Add unit test for PSU plug out and in

Split the function into two, one for handling the sdbus::message, the
other for handling the changed properties, so that it's easier to write
unit test cases.

Added the test cases:
* On a system with a PSU present, remove the PSU;
* On a system without PSU, plug the PSU in;
* On a system with a PSU, remove the PSU and add it back, while the
  propertiesChanged callback is invoked with both Present and Version
  properties.
* On a system with two PSUs with same version, remove them one-by-one,
  and add back one-by-one, while PSU1 has a different version.

Tested: Verify ItemUpdater correctly handles the above cases:
        * Remove the activation and version object if PSU is removed;
        * Create activation and version object if PSU is added;
        * When there are two PSUs with same version, removing one only
          update the associations, removing the other shall result in
          the objects to be removed;
          Adding one back will create the objects, and adding the other
          one with different version will create new objects.

Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: I14c7ae9f03ec91bb1c85bb5a18d69f20dc1efd53
diff --git a/test/test_item_updater.cpp b/test/test_item_updater.cpp
index 2b9af43..b362803 100644
--- a/test/test_item_updater.cpp
+++ b/test/test_item_updater.cpp
@@ -13,11 +13,13 @@
 using ::testing::StrEq;
 
 using std::experimental::any;
-using PropertyType = utils::UtilsInterface::PropertyType;
 
 class TestItemUpdater : public ::testing::Test
 {
   public:
+    using Properties = ItemUpdater::Properties;
+    using PropertyType = utils::UtilsInterface::PropertyType;
+
     TestItemUpdater() :
         mockedUtils(
             reinterpret_cast<const utils::MockedUtils&>(utils::getUtils()))
@@ -39,6 +41,12 @@
         return std::string(dBusPath) + "/" + versionId;
     }
 
+    void onPsuInventoryChanged(const std::string& psuPath,
+                               const Properties& properties)
+    {
+        itemUpdater->onPsuInventoryChanged(psuPath, properties);
+    }
+
     static constexpr auto dBusPath = SOFTWARE_OBJPATH;
     sdbusplus::SdBusMock sdbusMock;
     sdbusplus::bus::bus mockedBus = sdbusplus::get_mocked_new(&sdbusMock);
@@ -206,3 +214,203 @@
     EXPECT_EQ(1u, assocs1.size());
     EXPECT_EQ(psu1, std::get<2>(assocs1[0]));
 }
+
+TEST_F(TestItemUpdater, OnOnePSURemoved)
+{
+    constexpr auto psuPath = "/com/example/inventory/psu0";
+    constexpr auto service = "com.example.Software.Psu";
+    constexpr auto version = "version0";
+    std::string objPath = getObjPath(version);
+    EXPECT_CALL(mockedUtils, getPSUInventoryPath(_))
+        .WillOnce(Return(std::vector<std::string>({psuPath})));
+    EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
+        .WillOnce(Return(service));
+    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
+                                             _, StrEq(VERSION)))
+        .WillOnce(Return(any(PropertyType(std::string(version)))));
+    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
+                                             _, StrEq(PRESENT)))
+        .WillOnce(Return(any(PropertyType(true)))); // present
+
+    // The item updater itself
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(dBusPath)))
+        .Times(1);
+
+    // activation and version object will be added
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath)))
+        .Times(2);
+    itemUpdater = std::make_unique<ItemUpdater>(mockedBus, dBusPath);
+
+    // the activation and version object will be removed
+    Properties p{{PRESENT, PropertyType(false)}};
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(objPath)))
+        .Times(2);
+    onPsuInventoryChanged(psuPath, p);
+
+    // on exit, item updater is removed
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(dBusPath)))
+        .Times(1);
+}
+
+TEST_F(TestItemUpdater, OnOnePSUAdded)
+{
+    constexpr auto psuPath = "/com/example/inventory/psu0";
+    constexpr auto service = "com.example.Software.Psu";
+    constexpr auto version = "version0";
+    std::string objPath = getObjPath(version);
+    EXPECT_CALL(mockedUtils, getPSUInventoryPath(_))
+        .WillOnce(Return(std::vector<std::string>({psuPath})));
+    EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
+        .WillOnce(Return(service));
+    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
+                                             _, StrEq(VERSION)))
+        .WillOnce(Return(any(PropertyType(std::string(version)))));
+    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
+                                             _, StrEq(PRESENT)))
+        .WillOnce(Return(any(PropertyType(false)))); // not present
+
+    // The item updater itself
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(dBusPath)))
+        .Times(1);
+
+    // No activation/version objects are created
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath)))
+        .Times(0);
+    itemUpdater = std::make_unique<ItemUpdater>(mockedBus, dBusPath);
+
+    // The PSU is present and version is added in a single call
+    Properties propAdded{{PRESENT, PropertyType(true)},
+                         {VERSION, PropertyType(std::string(version))}};
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath)))
+        .Times(2);
+    onPsuInventoryChanged(psuPath, propAdded);
+}
+
+TEST_F(TestItemUpdater, OnOnePSURemovedAndAdded)
+{
+    constexpr auto psuPath = "/com/example/inventory/psu0";
+    constexpr auto service = "com.example.Software.Psu";
+    constexpr auto version = "version0";
+    std::string objPath = getObjPath(version);
+    EXPECT_CALL(mockedUtils, getPSUInventoryPath(_))
+        .WillOnce(Return(std::vector<std::string>({psuPath})));
+    EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
+        .WillOnce(Return(service));
+    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
+                                             _, StrEq(VERSION)))
+        .WillOnce(Return(any(PropertyType(std::string(version)))));
+    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
+                                             _, StrEq(PRESENT)))
+        .WillOnce(Return(any(PropertyType(true)))); // present
+
+    // The item updater itself
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(dBusPath)))
+        .Times(1);
+
+    // activation and version object will be added
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath)))
+        .Times(2);
+    itemUpdater = std::make_unique<ItemUpdater>(mockedBus, dBusPath);
+
+    // the activation and version object will be removed
+    Properties propRemoved{{PRESENT, PropertyType(false)}};
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(objPath)))
+        .Times(2);
+    onPsuInventoryChanged(psuPath, propRemoved);
+
+    Properties propAdded{{PRESENT, PropertyType(true)}};
+    Properties propVersion{{VERSION, PropertyType(std::string(version))}};
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath)))
+        .Times(2);
+    onPsuInventoryChanged(psuPath, propAdded);
+    onPsuInventoryChanged(psuPath, propVersion);
+
+    // on exit, objects are removed
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(objPath)))
+        .Times(2);
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(dBusPath)))
+        .Times(1);
+}
+
+TEST_F(TestItemUpdater,
+       TwoPSUsWithSameVersionRemovedAndAddedWithDifferntVersion)
+{
+    constexpr auto psu0 = "/com/example/inventory/psu0";
+    constexpr auto psu1 = "/com/example/inventory/psu1";
+    constexpr auto service = "com.example.Software.Psu";
+    auto version0 = std::string("version0");
+    auto version1 = std::string("version0");
+    auto objPath0 = getObjPath(version0);
+    auto objPath1 = getObjPath(version1);
+
+    EXPECT_CALL(mockedUtils, getPSUInventoryPath(_))
+        .WillOnce(Return(std::vector<std::string>({psu0, psu1})));
+    EXPECT_CALL(mockedUtils, getService(_, StrEq(psu0), _))
+        .WillOnce(Return(service));
+    EXPECT_CALL(mockedUtils, getService(_, StrEq(psu1), _))
+        .WillOnce(Return(service));
+    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu0), _,
+                                             StrEq(VERSION)))
+        .WillOnce(Return(any(PropertyType(version0))));
+    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu0), _,
+                                             StrEq(PRESENT)))
+        .WillOnce(Return(any(PropertyType(true)))); // present
+    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu1), _,
+                                             StrEq(VERSION)))
+        .WillOnce(Return(any(PropertyType(version1))));
+    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu1), _,
+                                             StrEq(PRESENT)))
+        .WillOnce(Return(any(PropertyType(true)))); // present
+
+    // The item updater itself
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(dBusPath)))
+        .Times(1);
+
+    // activation and version object will be added
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath0)))
+        .Times(2);
+    itemUpdater = std::make_unique<ItemUpdater>(mockedBus, dBusPath);
+
+    // Verify there is only one activation and it has two associations
+    const auto& activations = GetActivations();
+    EXPECT_EQ(1u, activations.size());
+    const auto& activation = activations.find(version0)->second;
+    auto assocs = activation->associations();
+    EXPECT_EQ(2u, assocs.size());
+    EXPECT_EQ(psu0, std::get<2>(assocs[0]));
+    EXPECT_EQ(psu1, std::get<2>(assocs[1]));
+
+    // PSU0 is removed, only associations shall be updated
+    Properties propRemoved{{PRESENT, PropertyType(false)}};
+    onPsuInventoryChanged(psu0, propRemoved);
+    assocs = activation->associations();
+    EXPECT_EQ(1u, assocs.size());
+    EXPECT_EQ(psu1, std::get<2>(assocs[0]));
+
+    // PSU1 is removed, the activation and version object shall be removed
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(objPath0)))
+        .Times(2);
+    onPsuInventoryChanged(psu1, propRemoved);
+
+    // Add PSU0 and PSU1 back, but PSU1 with a different version
+    version1 = "version1";
+    objPath1 = getObjPath(version1);
+    Properties propAdded0{{PRESENT, PropertyType(true)},
+                          {VERSION, PropertyType(std::string(version0))}};
+    Properties propAdded1{{PRESENT, PropertyType(true)},
+                          {VERSION, PropertyType(std::string(version1))}};
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath0)))
+        .Times(2);
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath1)))
+        .Times(2);
+    onPsuInventoryChanged(psu0, propAdded0);
+    onPsuInventoryChanged(psu1, propAdded1);
+
+    // on exit, objects are removed
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(objPath0)))
+        .Times(2);
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(objPath1)))
+        .Times(2);
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(dBusPath)))
+        .Times(1);
+}