Sync PSU images on service startup

On service startup, it shall check the PSU images, find a latest
version, and update to the PSUs that are not with this version.

Tested: With dummy image and service, test on Witherspoon with 2
        different running PSU software:
* When startup without stored image, the serive tries to update an older
PSU but it does not have PSU image, so it's skipped with below journal
log:
    Automatically update PSU
    No image for the activation, skipped
* When startup with an older image stored in BMC, it behaves the same as
above.
* When startup with a new image stored in BMC, it updates the PSUs, with
below example journal log:
    Automatically update PSU
    Starting Update PSU
    /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0
    /var/lib/obmc/psu/2B1D...
    Started Update PSU
    /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0
    /var/lib/obmc/psu/2B1D.
    Starting Update PSU
    /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply1
    /var/lib/obmc/psu/2B1D...
    Started Update PSU
    /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply1
    /var/lib/obmc/psu/2B1D.

Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: I6d676c5a4441685fb2b5920455f439c00f6097af
diff --git a/test/test_activation.cpp b/test/test_activation.cpp
index a402758..97efe79 100644
--- a/test/test_activation.cpp
+++ b/test/test_activation.cpp
@@ -30,6 +30,7 @@
             .WillByDefault(Return(any(PropertyType(std::string("TestManu")))));
         ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(MODEL)))
             .WillByDefault(Return(any(PropertyType(std::string("TestModel")))));
+        ON_CALL(mockedUtils, isAssociated(_, _)).WillByDefault(Return(false));
     }
     ~TestActivation()
     {
@@ -64,7 +65,7 @@
     std::unique_ptr<Activation> activation;
     std::string versionId = "abcdefgh";
     std::string extVersion = "manufacturer=TestManu,model=TestModel";
-    std::string filePath = "";
+    std::string filePath = "/tmp/images/abcdefgh";
     std::string dBusPath = std::string(SOFTWARE_OBJPATH) + "/" + versionId;
     Status status = Status::Ready;
     AssociationList associations;
@@ -233,7 +234,7 @@
         .WillByDefault(Return(std::vector<std::string>({psu0})));
     activation->requestedActivation(RequestedStatus::Active);
 
-    EXPECT_EQ(Status::Failed, activation->activation());
+    EXPECT_EQ(Status::Ready, activation->activation());
 }
 
 TEST_F(TestActivation, doUpdateOnePSUManufactureNotCompatible)
@@ -247,7 +248,7 @@
         .WillByDefault(Return(std::vector<std::string>({psu0})));
     activation->requestedActivation(RequestedStatus::Active);
 
-    EXPECT_EQ(Status::Failed, activation->activation());
+    EXPECT_EQ(Status::Ready, activation->activation());
 }
 
 TEST_F(TestActivation, doUpdateOnePSUSelfManufactureIsEmpty)
@@ -315,3 +316,73 @@
     onUpdateDone();
     EXPECT_EQ(Status::Active, activation->activation());
 }
+
+TEST_F(TestActivation, doUpdateWhenNoFilePathInActiveState)
+{
+    filePath = "";
+    status = Status::Active; // Typically, a running PSU software is active
+                             // without file path
+    constexpr auto psu0 = "/com/example/inventory/psu0";
+    activation = std::make_unique<Activation>(
+        mockedBus, dBusPath, versionId, extVersion, status, associations,
+        &mockedAssociationInterface, filePath);
+
+    ON_CALL(mockedUtils, getPSUInventoryPath(_))
+        .WillByDefault(
+            Return(std::vector<std::string>({psu0}))); // One PSU inventory
+
+    // There shall be no DBus call to start update service
+    EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _,
+                                                          StrEq("StartUnit")))
+        .Times(0);
+
+    activation->requestedActivation(RequestedStatus::Active);
+    EXPECT_EQ(Status::Active, activation->activation());
+}
+
+TEST_F(TestActivation, doUpdateWhenNoFilePathInReadyState)
+{
+    filePath = "";
+    status = Status::Ready; // Usually a Ready activation should have file path,
+                            // but we are testing this case as well
+    constexpr auto psu0 = "/com/example/inventory/psu0";
+    activation = std::make_unique<Activation>(
+        mockedBus, dBusPath, versionId, extVersion, status, associations,
+        &mockedAssociationInterface, filePath);
+
+    ON_CALL(mockedUtils, getPSUInventoryPath(_))
+        .WillByDefault(
+            Return(std::vector<std::string>({psu0}))); // One PSU inventory
+
+    // There shall be no DBus call to start update service
+    EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _,
+                                                          StrEq("StartUnit")))
+        .Times(0);
+
+    activation->requestedActivation(RequestedStatus::Active);
+    EXPECT_EQ(Status::Ready, activation->activation());
+}
+
+TEST_F(TestActivation, doUpdateWhenPSUIsAssociated)
+{
+    constexpr auto psu0 = "/com/example/inventory/psu0";
+    status = Status::Active; // Typically, a running PSU software is associated
+    activation = std::make_unique<Activation>(
+        mockedBus, dBusPath, versionId, extVersion, status, associations,
+        &mockedAssociationInterface, filePath);
+
+    ON_CALL(mockedUtils, getPSUInventoryPath(_))
+        .WillByDefault(
+            Return(std::vector<std::string>({psu0}))); // One PSU inventory
+
+    // When PSU is already associated, there shall be no DBus call to start
+    // update service
+    EXPECT_CALL(mockedUtils, isAssociated(StrEq(psu0), _))
+        .WillOnce(Return(true));
+    EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _,
+                                                          StrEq("StartUnit")))
+        .Times(0);
+
+    activation->requestedActivation(RequestedStatus::Active);
+    EXPECT_EQ(Status::Active, activation->activation());
+}