regulators: Add phase_fault_detection to parser

Enhance the JSON configuration file parser to support the new
phase_fault_detection object.

Add gtest test cases to exercise new code.

Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
Change-Id: I5b987054ef35c8b27d7d984e5e9ec9b4488bf944
diff --git a/phosphor-regulators/src/config_file_parser.cpp b/phosphor-regulators/src/config_file_parser.cpp
index 6527b5f..430d490 100644
--- a/phosphor-regulators/src/config_file_parser.cpp
+++ b/phosphor-regulators/src/config_file_parser.cpp
@@ -759,6 +759,38 @@
     return std::make_unique<OrAction>(std::move(actions));
 }
 
+std::unique_ptr<PhaseFaultDetection>
+    parsePhaseFaultDetection(const json& element)
+{
+    verifyIsObject(element);
+    unsigned int propertyCount{0};
+
+    // Optional comments property; value not stored
+    if (element.contains("comments"))
+    {
+        ++propertyCount;
+    }
+
+    // Optional device_id property
+    std::string deviceID{};
+    auto deviceIDIt = element.find("device_id");
+    if (deviceIDIt != element.end())
+    {
+        deviceID = parseString(*deviceIDIt);
+        ++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<PhaseFaultDetection>(std::move(actions), deviceID);
+}
+
 PhaseFaultType parsePhaseFaultType(const json& element)
 {
     std::string value = parseString(element);
diff --git a/phosphor-regulators/src/config_file_parser.hpp b/phosphor-regulators/src/config_file_parser.hpp
index 63c8bf8..05c5b60 100644
--- a/phosphor-regulators/src/config_file_parser.hpp
+++ b/phosphor-regulators/src/config_file_parser.hpp
@@ -35,6 +35,7 @@
 #include "not_action.hpp"
 #include "or_action.hpp"
 #include "phase_fault.hpp"
+#include "phase_fault_detection.hpp"
 #include "pmbus_read_sensor_action.hpp"
 #include "pmbus_write_vout_command_action.hpp"
 #include "presence_detection.hpp"
@@ -256,7 +257,7 @@
     parseCompareVPD(const nlohmann::json& element);
 
 /**
- * Parses a JSON element containing a configuration.
+ * Parses a JSON element containing a configuration object.
  *
  * Returns the corresponding C++ Configuration object.
  *
@@ -554,6 +555,19 @@
 std::unique_ptr<OrAction> parseOr(const nlohmann::json& element);
 
 /**
+ * Parses a JSON element containing a phase_fault_detection object.
+ *
+ * Returns the corresponding C++ PhaseFaultDetection object.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return PhaseFaultDetection object
+ */
+std::unique_ptr<PhaseFaultDetection>
+    parsePhaseFaultDetection(const nlohmann::json& element);
+
+/**
  * Parses a JSON element containing a PhaseFaultType expressed as a string.
  *
  * Returns the corresponding PhaseFaultType enum value.
@@ -592,7 +606,7 @@
     parsePMBusWriteVoutCommand(const nlohmann::json& element);
 
 /**
- * Parses a JSON element containing a presence detection operation.
+ * Parses a JSON element containing a presence_detection object.
  *
  * Returns the corresponding C++ PresenceDetection object.
  *
@@ -713,7 +727,7 @@
     parseSensorDataFormat(const nlohmann::json& element);
 
 /**
- * Parses a JSON element containing a sensor monitoring operation.
+ * Parses a JSON element containing a sensor_monitoring object.
  *
  * Returns the corresponding C++ SensorMonitoring object.
  *
diff --git a/phosphor-regulators/test/config_file_parser_tests.cpp b/phosphor-regulators/test/config_file_parser_tests.cpp
index b48f8b8..79e2cf4 100644
--- a/phosphor-regulators/test/config_file_parser_tests.cpp
+++ b/phosphor-regulators/test/config_file_parser_tests.cpp
@@ -34,6 +34,7 @@
 #include "not_action.hpp"
 #include "or_action.hpp"
 #include "phase_fault.hpp"
+#include "phase_fault_detection.hpp"
 #include "pmbus_read_sensor_action.hpp"
 #include "pmbus_utils.hpp"
 #include "pmbus_write_vout_command_action.hpp"
@@ -3489,6 +3490,156 @@
     }
 }
 
+TEST(ConfigFileParserTests, ParsePhaseFaultDetection)
+{
+    // Test where works: actions specified: optional properties not specified
+    {
+        const json element = R"(
+            {
+              "actions": [
+                { "run_rule": "detect_phase_fault_rule" }
+              ]
+            }
+        )"_json;
+        std::unique_ptr<PhaseFaultDetection> phaseFaultDetection =
+            parsePhaseFaultDetection(element);
+        EXPECT_EQ(phaseFaultDetection->getActions().size(), 1);
+        EXPECT_EQ(phaseFaultDetection->getDeviceID(), "");
+    }
+
+    // Test where works: rule_id specified: optional properties specified
+    {
+        const json element = R"(
+            {
+              "comments": [ "Detect phase fault using I/O expander" ],
+              "device_id": "io_expander",
+              "rule_id": "detect_phase_fault_rule"
+            }
+        )"_json;
+        std::unique_ptr<PhaseFaultDetection> phaseFaultDetection =
+            parsePhaseFaultDetection(element);
+        EXPECT_EQ(phaseFaultDetection->getActions().size(), 1);
+        EXPECT_EQ(phaseFaultDetection->getDeviceID(), "io_expander");
+    }
+
+    // Test where fails: Element is not an object
+    try
+    {
+        const json element = R"( [ "foo", "bar" ] )"_json;
+        parsePhaseFaultDetection(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: device_id value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "device_id": 1,
+              "rule_id": "detect_phase_fault_rule"
+            }
+        )"_json;
+        parsePhaseFaultDetection(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: rule_id value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "rule_id": 1
+            }
+        )"_json;
+        parsePhaseFaultDetection(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: actions object is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "actions": 1
+            }
+        )"_json;
+        parsePhaseFaultDetection(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: Required actions or rule_id property not specified
+    try
+    {
+        const json element = R"(
+            {
+              "device_id": "io_expander"
+            }
+        )"_json;
+        parsePhaseFaultDetection(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": "detect_phase_fault_rule",
+              "actions": [
+                { "run_rule": "detect_phase_fault_rule" }
+              ]
+            }
+        )"_json;
+        parsePhaseFaultDetection(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: Invalid property specified
+    try
+    {
+        const json element = R"(
+            {
+              "foo": "bar",
+              "actions": [
+                { "run_rule": "detect_phase_fault_rule" }
+              ]
+            }
+        )"_json;
+        parsePhaseFaultDetection(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, ParsePhaseFaultType)
 {
     // Test where works: n