regulators: Implements support for presence_detection

Enhance the configuration file parser to support the
presence_detection element.

Signed-off-by: Bob King <Bob_King@wistron.com>
Change-Id: I40ed2e849b6baaebdfd1899000fc389429d46543
diff --git a/phosphor-regulators/src/config_file_parser.cpp b/phosphor-regulators/src/config_file_parser.cpp
index 9615d31..14bf942 100644
--- a/phosphor-regulators/src/config_file_parser.cpp
+++ b/phosphor-regulators/src/config_file_parser.cpp
@@ -299,14 +299,13 @@
     ++propertyCount;
 
     // Optional presence_detection property
-    // TODO: Not implemented yet
     std::unique_ptr<PresenceDetection> presenceDetection{};
-    // auto presenceDetectionIt = element.find("presence_detection");
-    // if (presenceDetectionIt != element.end())
-    // {
-    //     presenceDetection = parsePresenceDetection(*presenceDetectionIt);
-    //     ++propertyCount;
-    // }
+    auto presenceDetectionIt = element.find("presence_detection");
+    if (presenceDetectionIt != element.end())
+    {
+        presenceDetection = parsePresenceDetection(*presenceDetectionIt);
+        ++propertyCount;
+    }
 
     // Optional configuration property
     std::unique_ptr<Configuration> configuration{};
@@ -679,6 +678,28 @@
                                                          exponent, isVerified);
 }
 
+std::unique_ptr<PresenceDetection> parsePresenceDetection(const json& element)
+{
+    verifyIsObject(element);
+    unsigned int propertyCount{0};
+
+    // Optional comments property; value not stored
+    if (element.contains("comments"))
+    {
+        ++propertyCount;
+    }
+
+    // Required rule_id or actions property
+    std::vector<std::unique_ptr<Action>> actions{};
+    actions = parseRuleIDOrActionsProperty(element);
+    ++propertyCount;
+
+    // Verify no invalid properties exist
+    verifyPropertyCount(element, propertyCount);
+
+    return std::make_unique<PresenceDetection>(std::move(actions));
+}
+
 std::unique_ptr<Rail> parseRail(const json& element)
 {
     verifyIsObject(element);
diff --git a/phosphor-regulators/src/config_file_parser.hpp b/phosphor-regulators/src/config_file_parser.hpp
index e04f81f..771c699 100644
--- a/phosphor-regulators/src/config_file_parser.hpp
+++ b/phosphor-regulators/src/config_file_parser.hpp
@@ -492,6 +492,19 @@
     parsePMBusWriteVoutCommand(const nlohmann::json& element);
 
 /**
+ * Parses a JSON element containing a presence detection operation.
+ *
+ * Returns the corresponding C++ PresenceDetection object.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return PresenceDetection object
+ */
+std::unique_ptr<PresenceDetection>
+    parsePresenceDetection(const nlohmann::json& element);
+
+/**
  * Parses a JSON element containing a rail.
  *
  * Returns the corresponding C++ Rail object.
diff --git a/phosphor-regulators/test/config_file_parser_tests.cpp b/phosphor-regulators/test/config_file_parser_tests.cpp
index 5223e54..42c1e58 100644
--- a/phosphor-regulators/test/config_file_parser_tests.cpp
+++ b/phosphor-regulators/test/config_file_parser_tests.cpp
@@ -1088,7 +1088,6 @@
 
     // Test where works: All properties specified
     {
-        // TODO : add presence_detection property
         const json element = R"(
             {
               "id": "vdd_regulator",
@@ -1103,6 +1102,10 @@
               {
                   "rule_id": "configure_ir35221_rule"
               },
+              "presence_detection":
+              {
+                  "rule_id": "is_foobar_backplane_installed_rule"
+              },
               "rails":
               [
                 {
@@ -1116,7 +1119,7 @@
         EXPECT_EQ(device->isRegulator(), true);
         EXPECT_EQ(device->getFRU(), "/system/chassis/motherboard/regulator2");
         EXPECT_NE(&(device->getI2CInterface()), nullptr);
-        // EXPECT_NE(device->getPresenceDetection(), nullptr);
+        EXPECT_NE(device->getPresenceDetection(), nullptr);
         EXPECT_NE(device->getConfiguration(), nullptr);
         EXPECT_EQ(device->getRails().size(), 1);
     }
@@ -2955,6 +2958,136 @@
     }
 }
 
+TEST(ConfigFileParserTests, ParsePresenceDetection)
+{
+    // Test where works: actions property specified
+    {
+        const json element = R"(
+            {
+              "actions": [
+                { "run_rule": "read_sensors_rule" }
+              ]
+            }
+        )"_json;
+        std::unique_ptr<PresenceDetection> presenceDetection =
+            parsePresenceDetection(element);
+        EXPECT_EQ(presenceDetection->getActions().size(), 1);
+    }
+
+    // Test where works: rule_id property specified
+    {
+        const json element = R"(
+            {
+              "comments": [ "comments property" ],
+              "rule_id": "set_voltage_rule"
+            }
+        )"_json;
+        std::unique_ptr<PresenceDetection> presenceDetection =
+            parsePresenceDetection(element);
+        EXPECT_EQ(presenceDetection->getActions().size(), 1);
+    }
+
+    // Test where fails: actions object is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "actions": 1
+            }
+        )"_json;
+        parsePresenceDetection(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an array");
+    }
+
+    // Test where fails: rule_id value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "rule_id": 1
+            }
+        )"_json;
+        parsePresenceDetection(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a string");
+    }
+
+    // Test where fails: Required actions or rule_id property not specified
+    try
+    {
+        const json element = R"(
+            {
+              "comments": [ "comments property" ]
+            }
+        )"_json;
+        parsePresenceDetection(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
+                               "either rule_id or actions");
+    }
+
+    // Test where fails: Required actions or rule_id property both specified
+    try
+    {
+        const json element = R"(
+            {
+              "rule_id": "set_voltage_rule",
+              "actions": [
+                { "run_rule": "read_sensors_rule" }
+              ]
+            }
+        )"_json;
+        parsePresenceDetection(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
+                               "either rule_id or actions");
+    }
+
+    // Test where fails: Element is not an object
+    try
+    {
+        const json element = R"( [ "foo", "bar" ] )"_json;
+        parsePresenceDetection(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an object");
+    }
+
+    // Test where fails: Invalid property specified
+    try
+    {
+        const json element = R"(
+            {
+              "foo": "bar",
+              "actions": [
+                { "run_rule": "read_sensors_rule" }
+              ]
+            }
+        )"_json;
+        parsePresenceDetection(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element contains an invalid property");
+    }
+}
+
 TEST(ConfigFileParserTests, ParseRail)
 {
     // Test where works: Only required properties specified