1 /**
2  * Copyright © 2020 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "action.hpp"
17 #include "and_action.hpp"
18 #include "chassis.hpp"
19 #include "compare_presence_action.hpp"
20 #include "compare_vpd_action.hpp"
21 #include "config_file_parser.hpp"
22 #include "config_file_parser_error.hpp"
23 #include "configuration.hpp"
24 #include "device.hpp"
25 #include "i2c_capture_bytes_action.hpp"
26 #include "i2c_compare_bit_action.hpp"
27 #include "i2c_compare_byte_action.hpp"
28 #include "i2c_compare_bytes_action.hpp"
29 #include "i2c_interface.hpp"
30 #include "i2c_write_bit_action.hpp"
31 #include "i2c_write_byte_action.hpp"
32 #include "i2c_write_bytes_action.hpp"
33 #include "log_phase_fault_action.hpp"
34 #include "not_action.hpp"
35 #include "or_action.hpp"
36 #include "phase_fault.hpp"
37 #include "phase_fault_detection.hpp"
38 #include "pmbus_read_sensor_action.hpp"
39 #include "pmbus_utils.hpp"
40 #include "pmbus_write_vout_command_action.hpp"
41 #include "presence_detection.hpp"
42 #include "rail.hpp"
43 #include "rule.hpp"
44 #include "run_rule_action.hpp"
45 #include "sensor_monitoring.hpp"
46 #include "sensors.hpp"
47 #include "set_device_action.hpp"
48 #include "temporary_file.hpp"
49 
50 #include <sys/stat.h> // for chmod()
51 
52 #include <nlohmann/json.hpp>
53 
54 #include <cstdint>
55 #include <cstring>
56 #include <exception>
57 #include <filesystem>
58 #include <fstream>
59 #include <memory>
60 #include <optional>
61 #include <stdexcept>
62 #include <string>
63 #include <tuple>
64 #include <vector>
65 
66 #include <gtest/gtest.h>
67 
68 using namespace phosphor::power::regulators;
69 using namespace phosphor::power::regulators::config_file_parser;
70 using namespace phosphor::power::regulators::config_file_parser::internal;
71 using json = nlohmann::json;
72 
73 void writeConfigFile(const std::filesystem::path& pathName,
74                      const std::string& contents)
75 {
76     std::ofstream file{pathName};
77     file << contents;
78 }
79 
80 void writeConfigFile(const std::filesystem::path& pathName,
81                      const json& contents)
82 {
83     std::ofstream file{pathName};
84     file << contents;
85 }
86 
87 TEST(ConfigFileParserTests, Parse)
88 {
89     // Test where works
90     {
91         const json configFileContents = R"(
92             {
93               "rules": [
94                 {
95                   "id": "set_voltage_rule1",
96                   "actions": [
97                     { "pmbus_write_vout_command": { "volts": 1.03, "format": "linear" } }
98                   ]
99                 },
100                 {
101                   "id": "set_voltage_rule2",
102                   "actions": [
103                     { "pmbus_write_vout_command": { "volts": 1.33, "format": "linear" } }
104                   ]
105                 }
106               ],
107               "chassis": [
108                 { "number": 1, "inventory_path": "system/chassis1" },
109                 { "number": 2, "inventory_path": "system/chassis2" },
110                 { "number": 3, "inventory_path": "system/chassis3" }
111               ]
112             }
113         )"_json;
114 
115         TemporaryFile configFile;
116         std::filesystem::path pathName{configFile.getPath()};
117         writeConfigFile(pathName, configFileContents);
118 
119         std::vector<std::unique_ptr<Rule>> rules{};
120         std::vector<std::unique_ptr<Chassis>> chassis{};
121         std::tie(rules, chassis) = parse(pathName);
122 
123         EXPECT_EQ(rules.size(), 2);
124         EXPECT_EQ(rules[0]->getID(), "set_voltage_rule1");
125         EXPECT_EQ(rules[1]->getID(), "set_voltage_rule2");
126 
127         EXPECT_EQ(chassis.size(), 3);
128         EXPECT_EQ(chassis[0]->getNumber(), 1);
129         EXPECT_EQ(chassis[0]->getInventoryPath(),
130                   "/xyz/openbmc_project/inventory/system/chassis1");
131         EXPECT_EQ(chassis[1]->getNumber(), 2);
132         EXPECT_EQ(chassis[1]->getInventoryPath(),
133                   "/xyz/openbmc_project/inventory/system/chassis2");
134         EXPECT_EQ(chassis[2]->getNumber(), 3);
135         EXPECT_EQ(chassis[2]->getInventoryPath(),
136                   "/xyz/openbmc_project/inventory/system/chassis3");
137     }
138 
139     // Test where fails: File does not exist
140     try
141     {
142         std::filesystem::path pathName{"/tmp/non_existent_file"};
143         parse(pathName);
144         ADD_FAILURE() << "Should not have reached this line.";
145     }
146     catch (const ConfigFileParserError& e)
147     {
148         // Expected exception; what() message will vary
149     }
150 
151     // Test where fails: File is not readable
152     try
153     {
154         const json configFileContents = R"(
155             {
156               "chassis": [
157                 { "number": 1, "inventory_path": "system/chassis1" }
158               ]
159             }
160         )"_json;
161 
162         TemporaryFile configFile;
163         std::filesystem::path pathName{configFile.getPath()};
164         writeConfigFile(pathName, configFileContents);
165 
166         chmod(pathName.c_str(), 0222);
167 
168         parse(pathName);
169         ADD_FAILURE() << "Should not have reached this line.";
170     }
171     catch (const ConfigFileParserError& e)
172     {
173         // Expected exception; what() message will vary
174     }
175 
176     // Test where fails: File is not valid JSON
177     try
178     {
179         const std::string configFileContents = "] foo [";
180 
181         TemporaryFile configFile;
182         std::filesystem::path pathName{configFile.getPath()};
183         writeConfigFile(pathName, configFileContents);
184 
185         parse(pathName);
186         ADD_FAILURE() << "Should not have reached this line.";
187     }
188     catch (const ConfigFileParserError& e)
189     {
190         // Expected exception; what() message will vary
191     }
192 
193     // Test where fails: Error when parsing JSON elements
194     try
195     {
196         const json configFileContents = R"( { "foo": "bar" } )"_json;
197 
198         TemporaryFile configFile;
199         std::filesystem::path pathName{configFile.getPath()};
200         writeConfigFile(pathName, configFileContents);
201 
202         parse(pathName);
203         ADD_FAILURE() << "Should not have reached this line.";
204     }
205     catch (const ConfigFileParserError& e)
206     {
207         // Expected exception; what() message will vary
208     }
209 }
210 
211 TEST(ConfigFileParserTests, GetRequiredProperty)
212 {
213     // Test where property exists
214     {
215         const json element = R"( { "format": "linear" } )"_json;
216         const json& propertyElement = getRequiredProperty(element, "format");
217         EXPECT_EQ(propertyElement.get<std::string>(), "linear");
218     }
219 
220     // Test where property does not exist
221     try
222     {
223         const json element = R"( { "volts": 1.03 } )"_json;
224         getRequiredProperty(element, "format");
225         ADD_FAILURE() << "Should not have reached this line.";
226     }
227     catch (const std::invalid_argument& e)
228     {
229         EXPECT_STREQ(e.what(), "Required property missing: format");
230     }
231 }
232 
233 TEST(ConfigFileParserTests, ParseAction)
234 {
235     // Test where works: comments property specified
236     {
237         const json element = R"(
238             {
239               "comments": [ "Set output voltage." ],
240               "pmbus_write_vout_command": {
241                 "format": "linear"
242               }
243             }
244         )"_json;
245         std::unique_ptr<Action> action = parseAction(element);
246         EXPECT_NE(action.get(), nullptr);
247     }
248 
249     // Test where works: comments property not specified
250     {
251         const json element = R"(
252             {
253               "pmbus_write_vout_command": {
254                 "format": "linear"
255               }
256             }
257         )"_json;
258         std::unique_ptr<Action> action = parseAction(element);
259         EXPECT_NE(action.get(), nullptr);
260     }
261 
262     // Test where works: and action type specified
263     {
264         const json element = R"(
265             {
266               "and": [
267                 { "i2c_compare_byte": { "register": "0xA0", "value": "0x00" } },
268                 { "i2c_compare_byte": { "register": "0xA1", "value": "0x00" } }
269               ]
270             }
271         )"_json;
272         std::unique_ptr<Action> action = parseAction(element);
273         EXPECT_NE(action.get(), nullptr);
274     }
275 
276     // Test where works: compare_presence action type specified
277     {
278         const json element = R"(
279             {
280               "compare_presence":
281               {
282                 "fru": "system/chassis/motherboard/cpu3",
283                 "value": true
284               }
285             }
286         )"_json;
287         std::unique_ptr<Action> action = parseAction(element);
288         EXPECT_NE(action.get(), nullptr);
289     }
290 
291     // Test where works: compare_vpd action type specified
292     {
293         const json element = R"(
294             {
295               "compare_vpd":
296               {
297                 "fru": "system/chassis/disk_backplane",
298                 "keyword": "CCIN",
299                 "value": "2D35"
300               }
301             }
302         )"_json;
303         std::unique_ptr<Action> action = parseAction(element);
304         EXPECT_NE(action.get(), nullptr);
305     }
306 
307     // Test where works: i2c_capture_bytes action type specified
308     {
309         const json element = R"(
310             {
311               "i2c_capture_bytes": {
312                 "register": "0xA0",
313                 "count": 2
314               }
315             }
316         )"_json;
317         std::unique_ptr<Action> action = parseAction(element);
318         EXPECT_NE(action.get(), nullptr);
319     }
320 
321     // Test where works: i2c_compare_bit action type specified
322     {
323         const json element = R"(
324             {
325               "i2c_compare_bit": {
326                 "register": "0xA0",
327                 "position": 3,
328                 "value": 0
329               }
330             }
331         )"_json;
332         std::unique_ptr<Action> action = parseAction(element);
333         EXPECT_NE(action.get(), nullptr);
334     }
335 
336     // Test where works: i2c_compare_byte action type specified
337     {
338         const json element = R"(
339             {
340               "i2c_compare_byte": {
341                 "register": "0x0A",
342                 "value": "0xCC"
343               }
344             }
345         )"_json;
346         std::unique_ptr<Action> action = parseAction(element);
347         EXPECT_NE(action.get(), nullptr);
348     }
349 
350     // Test where works: i2c_compare_bytes action type specified
351     {
352         const json element = R"(
353             {
354               "i2c_compare_bytes": {
355                 "register": "0x0A",
356                 "values": [ "0xCC", "0xFF" ]
357               }
358             }
359         )"_json;
360         std::unique_ptr<Action> action = parseAction(element);
361         EXPECT_NE(action.get(), nullptr);
362     }
363 
364     // Test where works: i2c_write_bit action type specified
365     {
366         const json element = R"(
367             {
368               "i2c_write_bit": {
369                 "register": "0xA0",
370                 "position": 3,
371                 "value": 0
372               }
373             }
374         )"_json;
375         std::unique_ptr<Action> action = parseAction(element);
376         EXPECT_NE(action.get(), nullptr);
377     }
378 
379     // Test where works: i2c_write_byte action type specified
380     {
381         const json element = R"(
382             {
383               "i2c_write_byte": {
384                 "register": "0x0A",
385                 "value": "0xCC"
386               }
387             }
388         )"_json;
389         std::unique_ptr<Action> action = parseAction(element);
390         EXPECT_NE(action.get(), nullptr);
391     }
392 
393     // Test where works: i2c_write_bytes action type specified
394     {
395         const json element = R"(
396             {
397               "i2c_write_bytes": {
398                 "register": "0x0A",
399                 "values": [ "0xCC", "0xFF" ]
400               }
401             }
402         )"_json;
403         std::unique_ptr<Action> action = parseAction(element);
404         EXPECT_NE(action.get(), nullptr);
405     }
406 
407     // Test where works: if action type specified
408     {
409         const json element = R"(
410             {
411               "if":
412               {
413                 "condition": { "run_rule": "is_downlevel_regulator" },
414                 "then": [ { "run_rule": "configure_downlevel_regulator" } ],
415                 "else": [ { "run_rule": "configure_standard_regulator" } ]
416               }
417             }
418         )"_json;
419         std::unique_ptr<Action> action = parseAction(element);
420         EXPECT_NE(action.get(), nullptr);
421     }
422 
423     // Test where works: log_phase_fault action type specified
424     {
425         const json element = R"(
426             {
427               "log_phase_fault": {
428                 "type": "n+1"
429               }
430             }
431         )"_json;
432         std::unique_ptr<Action> action = parseAction(element);
433         EXPECT_NE(action.get(), nullptr);
434     }
435 
436     // Test where works: not action type specified
437     {
438         const json element = R"(
439             {
440               "not":
441               { "i2c_compare_byte": { "register": "0xA0", "value": "0xFF" } }
442             }
443         )"_json;
444         std::unique_ptr<Action> action = parseAction(element);
445         EXPECT_NE(action.get(), nullptr);
446     }
447 
448     // Test where works: or action type specified
449     {
450         const json element = R"(
451             {
452               "or": [
453                 { "i2c_compare_byte": { "register": "0xA0", "value": "0x00" } },
454                 { "i2c_compare_byte": { "register": "0xA1", "value": "0x00" } }
455               ]
456             }
457         )"_json;
458         std::unique_ptr<Action> action = parseAction(element);
459         EXPECT_NE(action.get(), nullptr);
460     }
461 
462     // Test where works: pmbus_read_sensor action type specified
463     {
464         const json element = R"(
465             {
466               "pmbus_read_sensor": {
467                 "type": "iout",
468                 "command": "0x8C",
469                 "format": "linear_11"
470               }
471             }
472         )"_json;
473         std::unique_ptr<Action> action = parseAction(element);
474         EXPECT_NE(action.get(), nullptr);
475     }
476 
477     // Test where works: pmbus_write_vout_command action type specified
478     {
479         const json element = R"(
480             {
481               "pmbus_write_vout_command": {
482                 "format": "linear"
483               }
484             }
485         )"_json;
486         std::unique_ptr<Action> action = parseAction(element);
487         EXPECT_NE(action.get(), nullptr);
488     }
489 
490     // Test where works: run_rule action type specified
491     {
492         const json element = R"(
493             {
494               "run_rule": "set_voltage_rule"
495             }
496         )"_json;
497         std::unique_ptr<Action> action = parseAction(element);
498         EXPECT_NE(action.get(), nullptr);
499     }
500 
501     // Test where works: set_device action type specified
502     {
503         const json element = R"(
504             {
505               "set_device": "io_expander2"
506             }
507         )"_json;
508         std::unique_ptr<Action> action = parseAction(element);
509         EXPECT_NE(action.get(), nullptr);
510     }
511 
512     // Test where fails: Element is not an object
513     try
514     {
515         const json element = R"( [ "0xFF", "0x01" ] )"_json;
516         parseAction(element);
517         ADD_FAILURE() << "Should not have reached this line.";
518     }
519     catch (const std::invalid_argument& e)
520     {
521         EXPECT_STREQ(e.what(), "Element is not an object");
522     }
523 
524     // Test where fails: No action type specified
525     try
526     {
527         const json element = R"(
528             {
529               "comments": [ "Set output voltage." ]
530             }
531         )"_json;
532         parseAction(element);
533         ADD_FAILURE() << "Should not have reached this line.";
534     }
535     catch (const std::invalid_argument& e)
536     {
537         EXPECT_STREQ(e.what(), "Required action type property missing");
538     }
539 
540     // Test where fails: Multiple action types specified
541     try
542     {
543         const json element = R"(
544             {
545               "pmbus_write_vout_command": { "format": "linear" },
546               "run_rule": "set_voltage_rule"
547             }
548         )"_json;
549         parseAction(element);
550         ADD_FAILURE() << "Should not have reached this line.";
551     }
552     catch (const std::invalid_argument& e)
553     {
554         EXPECT_STREQ(e.what(), "Element contains an invalid property");
555     }
556 
557     // Test where fails: Invalid property specified
558     try
559     {
560         const json element = R"(
561             {
562               "remarks": [ "Set output voltage." ],
563               "pmbus_write_vout_command": {
564                 "format": "linear"
565               }
566             }
567         )"_json;
568         parseAction(element);
569         ADD_FAILURE() << "Should not have reached this line.";
570     }
571     catch (const std::invalid_argument& e)
572     {
573         EXPECT_STREQ(e.what(), "Element contains an invalid property");
574     }
575 }
576 
577 TEST(ConfigFileParserTests, ParseActionArray)
578 {
579     // Test where works
580     {
581         const json element = R"(
582             [
583               { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } },
584               { "pmbus_write_vout_command": { "volts": 1.03, "format": "linear" } }
585             ]
586         )"_json;
587         std::vector<std::unique_ptr<Action>> actions =
588             parseActionArray(element);
589         EXPECT_EQ(actions.size(), 2);
590     }
591 
592     // Test where fails: Element is not an array
593     try
594     {
595         const json element = R"(
596             {
597               "foo": "bar"
598             }
599         )"_json;
600         parseActionArray(element);
601         ADD_FAILURE() << "Should not have reached this line.";
602     }
603     catch (const std::invalid_argument& e)
604     {
605         EXPECT_STREQ(e.what(), "Element is not an array");
606     }
607 }
608 
609 TEST(ConfigFileParserTests, ParseAnd)
610 {
611     // Test where works: Element is an array with 2 actions
612     {
613         const json element = R"(
614             [
615               { "i2c_compare_byte": { "register": "0xA0", "value": "0x00" } },
616               { "i2c_compare_byte": { "register": "0xA1", "value": "0x00" } }
617             ]
618         )"_json;
619         std::unique_ptr<AndAction> action = parseAnd(element);
620         EXPECT_EQ(action->getActions().size(), 2);
621     }
622 
623     // Test where fails: Element is an array with 1 action
624     try
625     {
626         const json element = R"(
627             [
628               { "i2c_compare_byte": { "register": "0xA0", "value": "0x00" } }
629             ]
630         )"_json;
631         parseAnd(element);
632         ADD_FAILURE() << "Should not have reached this line.";
633     }
634     catch (const std::invalid_argument& e)
635     {
636         EXPECT_STREQ(e.what(), "Array must contain two or more actions");
637     }
638 
639     // Test where fails: Element is not an array
640     try
641     {
642         const json element = R"(
643             {
644               "foo": "bar"
645             }
646         )"_json;
647         parseAnd(element);
648         ADD_FAILURE() << "Should not have reached this line.";
649     }
650     catch (const std::invalid_argument& e)
651     {
652         EXPECT_STREQ(e.what(), "Element is not an array");
653     }
654 }
655 
656 TEST(ConfigFileParserTests, ParseBitPosition)
657 {
658     // Test where works: 0
659     {
660         const json element = R"( 0 )"_json;
661         uint8_t value = parseBitPosition(element);
662         EXPECT_EQ(value, 0);
663     }
664 
665     // Test where works: 7
666     {
667         const json element = R"( 7 )"_json;
668         uint8_t value = parseBitPosition(element);
669         EXPECT_EQ(value, 7);
670     }
671 
672     // Test where fails: Element is not an integer
673     try
674     {
675         const json element = R"( 1.03 )"_json;
676         parseBitPosition(element);
677         ADD_FAILURE() << "Should not have reached this line.";
678     }
679     catch (const std::invalid_argument& e)
680     {
681         EXPECT_STREQ(e.what(), "Element is not an integer");
682     }
683 
684     // Test where fails: Value < 0
685     try
686     {
687         const json element = R"( -1 )"_json;
688         parseBitPosition(element);
689         ADD_FAILURE() << "Should not have reached this line.";
690     }
691     catch (const std::invalid_argument& e)
692     {
693         EXPECT_STREQ(e.what(), "Element is not a bit position");
694     }
695 
696     // Test where fails: Value > 7
697     try
698     {
699         const json element = R"( 8 )"_json;
700         parseBitPosition(element);
701         ADD_FAILURE() << "Should not have reached this line.";
702     }
703     catch (const std::invalid_argument& e)
704     {
705         EXPECT_STREQ(e.what(), "Element is not a bit position");
706     }
707 }
708 
709 TEST(ConfigFileParserTests, ParseBitValue)
710 {
711     // Test where works: 0
712     {
713         const json element = R"( 0 )"_json;
714         uint8_t value = parseBitValue(element);
715         EXPECT_EQ(value, 0);
716     }
717 
718     // Test where works: 1
719     {
720         const json element = R"( 1 )"_json;
721         uint8_t value = parseBitValue(element);
722         EXPECT_EQ(value, 1);
723     }
724 
725     // Test where fails: Element is not an integer
726     try
727     {
728         const json element = R"( 0.5 )"_json;
729         parseBitValue(element);
730         ADD_FAILURE() << "Should not have reached this line.";
731     }
732     catch (const std::invalid_argument& e)
733     {
734         EXPECT_STREQ(e.what(), "Element is not an integer");
735     }
736 
737     // Test where fails: Value < 0
738     try
739     {
740         const json element = R"( -1 )"_json;
741         parseBitValue(element);
742         ADD_FAILURE() << "Should not have reached this line.";
743     }
744     catch (const std::invalid_argument& e)
745     {
746         EXPECT_STREQ(e.what(), "Element is not a bit value");
747     }
748 
749     // Test where fails: Value > 1
750     try
751     {
752         const json element = R"( 2 )"_json;
753         parseBitValue(element);
754         ADD_FAILURE() << "Should not have reached this line.";
755     }
756     catch (const std::invalid_argument& e)
757     {
758         EXPECT_STREQ(e.what(), "Element is not a bit value");
759     }
760 }
761 
762 TEST(ConfigFileParserTests, ParseBoolean)
763 {
764     // Test where works: true
765     {
766         const json element = R"( true )"_json;
767         bool value = parseBoolean(element);
768         EXPECT_EQ(value, true);
769     }
770 
771     // Test where works: false
772     {
773         const json element = R"( false )"_json;
774         bool value = parseBoolean(element);
775         EXPECT_EQ(value, false);
776     }
777 
778     // Test where fails: Element is not a boolean
779     try
780     {
781         const json element = R"( 1 )"_json;
782         parseBoolean(element);
783         ADD_FAILURE() << "Should not have reached this line.";
784     }
785     catch (const std::invalid_argument& e)
786     {
787         EXPECT_STREQ(e.what(), "Element is not a boolean");
788     }
789 }
790 
791 TEST(ConfigFileParserTests, ParseChassis)
792 {
793     // Test where works: Only required properties specified
794     {
795         const json element = R"(
796             {
797               "number": 1,
798               "inventory_path": "system/chassis1"
799             }
800         )"_json;
801         std::unique_ptr<Chassis> chassis = parseChassis(element);
802         EXPECT_EQ(chassis->getNumber(), 1);
803         EXPECT_EQ(chassis->getInventoryPath(),
804                   "/xyz/openbmc_project/inventory/system/chassis1");
805         EXPECT_EQ(chassis->getDevices().size(), 0);
806     }
807 
808     // Test where works: All properties specified
809     {
810         const json element = R"(
811             {
812               "comments": [ "comments property" ],
813               "number": 2,
814               "inventory_path": "system/chassis2",
815               "devices": [
816                 {
817                   "id": "vdd_regulator",
818                   "is_regulator": true,
819                   "fru": "system/chassis/motherboard/regulator2",
820                   "i2c_interface":
821                   {
822                       "bus": 1,
823                       "address": "0x70"
824                   }
825                 }
826               ]
827             }
828         )"_json;
829         std::unique_ptr<Chassis> chassis = parseChassis(element);
830         EXPECT_EQ(chassis->getNumber(), 2);
831         EXPECT_EQ(chassis->getInventoryPath(),
832                   "/xyz/openbmc_project/inventory/system/chassis2");
833         EXPECT_EQ(chassis->getDevices().size(), 1);
834         EXPECT_EQ(chassis->getDevices()[0]->getID(), "vdd_regulator");
835     }
836 
837     // Test where fails: number value is invalid
838     try
839     {
840         const json element = R"(
841             {
842               "number": 0.5,
843               "inventory_path": "system/chassis"
844             }
845         )"_json;
846         parseChassis(element);
847         ADD_FAILURE() << "Should not have reached this line.";
848     }
849     catch (const std::invalid_argument& e)
850     {
851         EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
852     }
853 
854     // Test where fails: inventory_path is invalid: Not a string
855     try
856     {
857         const json element = R"(
858             {
859               "number": 2,
860               "inventory_path": true
861             }
862         )"_json;
863         parseChassis(element);
864         ADD_FAILURE() << "Should not have reached this line.";
865     }
866     catch (const std::invalid_argument& e)
867     {
868         EXPECT_STREQ(e.what(), "Element is not a string");
869     }
870 
871     // Test where fails: inventory_path is invalid: Empty string
872     try
873     {
874         const json element = R"(
875             {
876               "number": 2,
877               "inventory_path": ""
878             }
879         )"_json;
880         parseChassis(element);
881         ADD_FAILURE() << "Should not have reached this line.";
882     }
883     catch (const std::invalid_argument& e)
884     {
885         EXPECT_STREQ(e.what(), "Element contains an empty string");
886     }
887 
888     // Test where fails: Invalid property specified
889     try
890     {
891         const json element = R"(
892             {
893               "number": 1,
894               "inventory_path": "system/chassis",
895               "foo": 2
896             }
897         )"_json;
898         parseChassis(element);
899         ADD_FAILURE() << "Should not have reached this line.";
900     }
901     catch (const std::invalid_argument& e)
902     {
903         EXPECT_STREQ(e.what(), "Element contains an invalid property");
904     }
905 
906     // Test where fails: Required number property not specified
907     try
908     {
909         const json element = R"(
910             {
911               "inventory_path": "system/chassis"
912             }
913         )"_json;
914         parseChassis(element);
915         ADD_FAILURE() << "Should not have reached this line.";
916     }
917     catch (const std::invalid_argument& e)
918     {
919         EXPECT_STREQ(e.what(), "Required property missing: number");
920     }
921 
922     // Test where fails: Required inventory_path property not specified
923     try
924     {
925         const json element = R"(
926             {
927               "number": 1
928             }
929         )"_json;
930         parseChassis(element);
931         ADD_FAILURE() << "Should not have reached this line.";
932     }
933     catch (const std::invalid_argument& e)
934     {
935         EXPECT_STREQ(e.what(), "Required property missing: inventory_path");
936     }
937 
938     // Test where fails: Element is not an object
939     try
940     {
941         const json element = R"( [ "0xFF", "0x01" ] )"_json;
942         parseChassis(element);
943         ADD_FAILURE() << "Should not have reached this line.";
944     }
945     catch (const std::invalid_argument& e)
946     {
947         EXPECT_STREQ(e.what(), "Element is not an object");
948     }
949 
950     // Test where fails: number value is < 1
951     try
952     {
953         const json element = R"(
954             {
955               "number": 0,
956               "inventory_path": "system/chassis"
957             }
958         )"_json;
959         parseChassis(element);
960         ADD_FAILURE() << "Should not have reached this line.";
961     }
962     catch (const std::invalid_argument& e)
963     {
964         EXPECT_STREQ(e.what(), "Invalid chassis number: Must be > 0");
965     }
966 
967     // Test where fails: devices value is invalid
968     try
969     {
970         const json element = R"(
971             {
972               "number": 1,
973               "inventory_path": "system/chassis",
974               "devices": 2
975             }
976         )"_json;
977         parseChassis(element);
978         ADD_FAILURE() << "Should not have reached this line.";
979     }
980     catch (const std::invalid_argument& e)
981     {
982         EXPECT_STREQ(e.what(), "Element is not an array");
983     }
984 }
985 
986 TEST(ConfigFileParserTests, ParseChassisArray)
987 {
988     // Test where works
989     {
990         const json element = R"(
991             [
992               { "number": 1, "inventory_path": "system/chassis1" },
993               { "number": 2, "inventory_path": "system/chassis2" }
994             ]
995         )"_json;
996         std::vector<std::unique_ptr<Chassis>> chassis =
997             parseChassisArray(element);
998         EXPECT_EQ(chassis.size(), 2);
999         EXPECT_EQ(chassis[0]->getNumber(), 1);
1000         EXPECT_EQ(chassis[0]->getInventoryPath(),
1001                   "/xyz/openbmc_project/inventory/system/chassis1");
1002         EXPECT_EQ(chassis[1]->getNumber(), 2);
1003         EXPECT_EQ(chassis[1]->getInventoryPath(),
1004                   "/xyz/openbmc_project/inventory/system/chassis2");
1005     }
1006 
1007     // Test where fails: Element is not an array
1008     try
1009     {
1010         const json element = R"(
1011             {
1012               "foo": "bar"
1013             }
1014         )"_json;
1015         parseChassisArray(element);
1016         ADD_FAILURE() << "Should not have reached this line.";
1017     }
1018     catch (const std::invalid_argument& e)
1019     {
1020         EXPECT_STREQ(e.what(), "Element is not an array");
1021     }
1022 }
1023 
1024 TEST(ConfigFileParserTests, ParseComparePresence)
1025 {
1026     // Test where works
1027     {
1028         const json element = R"(
1029             {
1030               "fru": "system/chassis/motherboard/cpu3",
1031               "value": true
1032             }
1033         )"_json;
1034         std::unique_ptr<ComparePresenceAction> action =
1035             parseComparePresence(element);
1036         EXPECT_EQ(
1037             action->getFRU(),
1038             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu3");
1039         EXPECT_EQ(action->getValue(), true);
1040     }
1041 
1042     // Test where fails: Element is not an object
1043     try
1044     {
1045         const json element = R"( [ "0xFF", "0x01" ] )"_json;
1046         parseComparePresence(element);
1047         ADD_FAILURE() << "Should not have reached this line.";
1048     }
1049     catch (const std::invalid_argument& e)
1050     {
1051         EXPECT_STREQ(e.what(), "Element is not an object");
1052     }
1053 
1054     // Test where fails: Invalid property specified
1055     try
1056     {
1057         const json element = R"(
1058             {
1059               "fru": "system/chassis/motherboard/cpu3",
1060               "value": true,
1061               "foo" : true
1062             }
1063         )"_json;
1064         parseComparePresence(element);
1065         ADD_FAILURE() << "Should not have reached this line.";
1066     }
1067     catch (const std::invalid_argument& e)
1068     {
1069         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1070     }
1071 
1072     // Test where fails: Required fru property not specified
1073     try
1074     {
1075         const json element = R"(
1076             {
1077               "value": true
1078             }
1079         )"_json;
1080         parseComparePresence(element);
1081         ADD_FAILURE() << "Should not have reached this line.";
1082     }
1083     catch (const std::invalid_argument& e)
1084     {
1085         EXPECT_STREQ(e.what(), "Required property missing: fru");
1086     }
1087 
1088     // Test where fails: Required value property not specified
1089     try
1090     {
1091         const json element = R"(
1092             {
1093               "fru": "system/chassis/motherboard/cpu3"
1094             }
1095         )"_json;
1096         parseComparePresence(element);
1097         ADD_FAILURE() << "Should not have reached this line.";
1098     }
1099     catch (const std::invalid_argument& e)
1100     {
1101         EXPECT_STREQ(e.what(), "Required property missing: value");
1102     }
1103 
1104     // Test where fails: fru value is invalid
1105     try
1106     {
1107         const json element = R"(
1108             {
1109               "fru": 1,
1110               "value": true
1111             }
1112         )"_json;
1113         parseComparePresence(element);
1114         ADD_FAILURE() << "Should not have reached this line.";
1115     }
1116     catch (const std::invalid_argument& e)
1117     {
1118         EXPECT_STREQ(e.what(), "Element is not a string");
1119     }
1120 
1121     // Test where fails: value value is invalid
1122     try
1123     {
1124         const json element = R"(
1125             {
1126               "fru": "system/chassis/motherboard/cpu3",
1127               "value": 1
1128             }
1129         )"_json;
1130         parseComparePresence(element);
1131         ADD_FAILURE() << "Should not have reached this line.";
1132     }
1133     catch (const std::invalid_argument& e)
1134     {
1135         EXPECT_STREQ(e.what(), "Element is not a boolean");
1136     }
1137 }
1138 
1139 TEST(ConfigFileParserTests, ParseCompareVPD)
1140 {
1141     // Test where works: value property: Not empty
1142     {
1143         const json element = R"(
1144             {
1145               "fru": "system/chassis/disk_backplane",
1146               "keyword": "CCIN",
1147               "value": "2D35"
1148             }
1149         )"_json;
1150         std::unique_ptr<CompareVPDAction> action = parseCompareVPD(element);
1151         EXPECT_EQ(
1152             action->getFRU(),
1153             "/xyz/openbmc_project/inventory/system/chassis/disk_backplane");
1154         EXPECT_EQ(action->getKeyword(), "CCIN");
1155         EXPECT_EQ(action->getValue(),
1156                   (std::vector<uint8_t>{0x32, 0x44, 0x33, 0x35}));
1157     }
1158 
1159     // Test where works: value property: Empty
1160     {
1161         const json element = R"(
1162             {
1163               "fru": "system/chassis/disk_backplane",
1164               "keyword": "CCIN",
1165               "value": ""
1166             }
1167         )"_json;
1168         std::unique_ptr<CompareVPDAction> action = parseCompareVPD(element);
1169         EXPECT_EQ(
1170             action->getFRU(),
1171             "/xyz/openbmc_project/inventory/system/chassis/disk_backplane");
1172         EXPECT_EQ(action->getKeyword(), "CCIN");
1173         EXPECT_EQ(action->getValue(), (std::vector<uint8_t>{}));
1174     }
1175 
1176     // Test where works: byte_values property: Not empty
1177     {
1178         const json element = R"(
1179             {
1180               "fru": "system/chassis/disk_backplane",
1181               "keyword": "CCIN",
1182               "byte_values": ["0x11", "0x22", "0x33"]
1183             }
1184         )"_json;
1185         std::unique_ptr<CompareVPDAction> action = parseCompareVPD(element);
1186         EXPECT_EQ(
1187             action->getFRU(),
1188             "/xyz/openbmc_project/inventory/system/chassis/disk_backplane");
1189         EXPECT_EQ(action->getKeyword(), "CCIN");
1190         EXPECT_EQ(action->getValue(), (std::vector<uint8_t>{0x11, 0x22, 0x33}));
1191     }
1192 
1193     // Test where works: byte_values property: Empty
1194     {
1195         const json element = R"(
1196             {
1197               "fru": "system/chassis/disk_backplane",
1198               "keyword": "CCIN",
1199               "byte_values": []
1200             }
1201         )"_json;
1202         std::unique_ptr<CompareVPDAction> action = parseCompareVPD(element);
1203         EXPECT_EQ(
1204             action->getFRU(),
1205             "/xyz/openbmc_project/inventory/system/chassis/disk_backplane");
1206         EXPECT_EQ(action->getKeyword(), "CCIN");
1207         EXPECT_EQ(action->getValue(), (std::vector<uint8_t>{}));
1208     }
1209 
1210     // Test where fails: Element is not an object
1211     try
1212     {
1213         const json element = R"( [ "0xFF", "0x01" ] )"_json;
1214         parseCompareVPD(element);
1215         ADD_FAILURE() << "Should not have reached this line.";
1216     }
1217     catch (const std::invalid_argument& e)
1218     {
1219         EXPECT_STREQ(e.what(), "Element is not an object");
1220     }
1221 
1222     // Test where fails: Invalid property specified
1223     try
1224     {
1225         const json element = R"(
1226             {
1227               "fru": "system/chassis/disk_backplane",
1228               "keyword": "CCIN",
1229               "value": "2D35",
1230               "foo" : true
1231             }
1232         )"_json;
1233         parseCompareVPD(element);
1234         ADD_FAILURE() << "Should not have reached this line.";
1235     }
1236     catch (const std::invalid_argument& e)
1237     {
1238         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1239     }
1240 
1241     // Test where fails: Required fru property not specified
1242     try
1243     {
1244         const json element = R"(
1245             {
1246               "keyword": "CCIN",
1247               "value": "2D35"
1248             }
1249         )"_json;
1250         parseCompareVPD(element);
1251         ADD_FAILURE() << "Should not have reached this line.";
1252     }
1253     catch (const std::invalid_argument& e)
1254     {
1255         EXPECT_STREQ(e.what(), "Required property missing: fru");
1256     }
1257 
1258     // Test where fails: Required keyword property not specified
1259     try
1260     {
1261         const json element = R"(
1262             {
1263               "fru": "system/chassis/disk_backplane",
1264               "value": "2D35"
1265             }
1266         )"_json;
1267         parseCompareVPD(element);
1268         ADD_FAILURE() << "Should not have reached this line.";
1269     }
1270     catch (const std::invalid_argument& e)
1271     {
1272         EXPECT_STREQ(e.what(), "Required property missing: keyword");
1273     }
1274 
1275     // Test where fails: Required value property not specified
1276     try
1277     {
1278         const json element = R"(
1279             {
1280               "fru": "system/chassis/disk_backplane",
1281               "keyword": "CCIN"
1282             }
1283         )"_json;
1284         parseCompareVPD(element);
1285         ADD_FAILURE() << "Should not have reached this line.";
1286     }
1287     catch (const std::invalid_argument& e)
1288     {
1289         EXPECT_STREQ(e.what(), "Invalid property: Must contain "
1290                                "either value or byte_values");
1291     }
1292 
1293     // Test where fails: both value and byte_values specified
1294     try
1295     {
1296         const json element = R"(
1297             {
1298               "fru": "system/chassis/disk_backplane",
1299               "keyword": "CCIN",
1300               "value": "2D35",
1301               "byte_values": [ "0x01", "0x02" ]
1302             }
1303         )"_json;
1304         parseCompareVPD(element);
1305         ADD_FAILURE() << "Should not have reached this line.";
1306     }
1307     catch (const std::invalid_argument& e)
1308     {
1309         EXPECT_STREQ(e.what(), "Invalid property: Must contain "
1310                                "either value or byte_values");
1311     }
1312 
1313     // Test where fails: fru value is invalid
1314     try
1315     {
1316         const json element = R"(
1317             {
1318               "fru": 1,
1319               "keyword": "CCIN",
1320               "value": "2D35"
1321             }
1322         )"_json;
1323         parseCompareVPD(element);
1324         ADD_FAILURE() << "Should not have reached this line.";
1325     }
1326     catch (const std::invalid_argument& e)
1327     {
1328         EXPECT_STREQ(e.what(), "Element is not a string");
1329     }
1330 
1331     // Test where fails: keyword value is invalid
1332     try
1333     {
1334         const json element = R"(
1335             {
1336               "fru": "system/chassis/disk_backplane",
1337               "keyword": 1,
1338               "value": "2D35"
1339             }
1340         )"_json;
1341         parseCompareVPD(element);
1342         ADD_FAILURE() << "Should not have reached this line.";
1343     }
1344     catch (const std::invalid_argument& e)
1345     {
1346         EXPECT_STREQ(e.what(), "Element is not a string");
1347     }
1348 
1349     // Test where fails: value value is invalid
1350     try
1351     {
1352         const json element = R"(
1353             {
1354               "fru": "system/chassis/disk_backplane",
1355               "keyword": "CCIN",
1356               "value": 1
1357             }
1358         )"_json;
1359         parseCompareVPD(element);
1360         ADD_FAILURE() << "Should not have reached this line.";
1361     }
1362     catch (const std::invalid_argument& e)
1363     {
1364         EXPECT_STREQ(e.what(), "Element is not a string");
1365     }
1366 
1367     // Test where fails: byte_values is wrong format
1368     try
1369     {
1370         const json element = R"(
1371             {
1372               "fru": "system/chassis/disk_backplane",
1373               "keyword": "CCIN",
1374               "byte_values": [1, 2, 3]
1375             }
1376         )"_json;
1377         parseCompareVPD(element);
1378         ADD_FAILURE() << "Should not have reached this line.";
1379     }
1380     catch (const std::invalid_argument& e)
1381     {
1382         EXPECT_STREQ(e.what(), "Element is not a string");
1383     }
1384 }
1385 
1386 TEST(ConfigFileParserTests, ParseConfiguration)
1387 {
1388     // Test where works: actions required property specified
1389     {
1390         const json element = R"(
1391             {
1392               "actions": [
1393                 {
1394                   "pmbus_write_vout_command": {
1395                     "format": "linear"
1396                   }
1397                 }
1398               ]
1399             }
1400         )"_json;
1401         std::unique_ptr<Configuration> configuration =
1402             parseConfiguration(element);
1403         EXPECT_EQ(configuration->getActions().size(), 1);
1404         EXPECT_EQ(configuration->getVolts().has_value(), false);
1405     }
1406 
1407     // Test where works: volts and actions properties specified
1408     {
1409         const json element = R"(
1410             {
1411               "comments": [ "comments property" ],
1412               "volts": 1.03,
1413               "actions": [
1414                 { "pmbus_write_vout_command": { "format": "linear" } },
1415                 { "run_rule": "set_voltage_rule" }
1416               ]
1417             }
1418         )"_json;
1419         std::unique_ptr<Configuration> configuration =
1420             parseConfiguration(element);
1421         EXPECT_EQ(configuration->getVolts().has_value(), true);
1422         EXPECT_EQ(configuration->getVolts().value(), 1.03);
1423         EXPECT_EQ(configuration->getActions().size(), 2);
1424     }
1425 
1426     // Test where works: volts and rule_id properties specified
1427     {
1428         const json element = R"(
1429             {
1430               "volts": 1.05,
1431               "rule_id": "set_voltage_rule"
1432             }
1433         )"_json;
1434         std::unique_ptr<Configuration> configuration =
1435             parseConfiguration(element);
1436         EXPECT_EQ(configuration->getVolts().has_value(), true);
1437         EXPECT_EQ(configuration->getVolts().value(), 1.05);
1438         EXPECT_EQ(configuration->getActions().size(), 1);
1439     }
1440 
1441     // Test where fails: volts value is invalid
1442     try
1443     {
1444         const json element = R"(
1445             {
1446               "volts": "foo",
1447               "actions": [
1448                 {
1449                   "pmbus_write_vout_command": {
1450                     "format": "linear"
1451                   }
1452                 }
1453               ]
1454             }
1455         )"_json;
1456         parseConfiguration(element);
1457         ADD_FAILURE() << "Should not have reached this line.";
1458     }
1459     catch (const std::invalid_argument& e)
1460     {
1461         EXPECT_STREQ(e.what(), "Element is not a number");
1462     }
1463 
1464     // Test where fails: actions object is invalid
1465     try
1466     {
1467         const json element = R"(
1468             {
1469               "volts": 1.03,
1470               "actions": 1
1471             }
1472         )"_json;
1473         parseConfiguration(element);
1474         ADD_FAILURE() << "Should not have reached this line.";
1475     }
1476     catch (const std::invalid_argument& e)
1477     {
1478         EXPECT_STREQ(e.what(), "Element is not an array");
1479     }
1480 
1481     // Test where fails: rule_id value is invalid
1482     try
1483     {
1484         const json element = R"(
1485             {
1486               "volts": 1.05,
1487               "rule_id": 1
1488             }
1489         )"_json;
1490         parseConfiguration(element);
1491         ADD_FAILURE() << "Should not have reached this line.";
1492     }
1493     catch (const std::invalid_argument& e)
1494     {
1495         EXPECT_STREQ(e.what(), "Element is not a string");
1496     }
1497 
1498     // Test where fails: Required actions or rule_id property not specified
1499     try
1500     {
1501         const json element = R"(
1502             {
1503               "volts": 1.03
1504             }
1505         )"_json;
1506         parseConfiguration(element);
1507         ADD_FAILURE() << "Should not have reached this line.";
1508     }
1509     catch (const std::invalid_argument& e)
1510     {
1511         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
1512                                "either rule_id or actions");
1513     }
1514 
1515     // Test where fails: Required actions or rule_id property both specified
1516     try
1517     {
1518         const json element = R"(
1519             {
1520               "volts": 1.03,
1521               "rule_id": "set_voltage_rule",
1522               "actions": [
1523                 {
1524                   "pmbus_write_vout_command": {
1525                     "format": "linear"
1526                   }
1527                 }
1528               ]
1529             }
1530         )"_json;
1531         parseConfiguration(element);
1532         ADD_FAILURE() << "Should not have reached this line.";
1533     }
1534     catch (const std::invalid_argument& e)
1535     {
1536         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
1537                                "either rule_id or actions");
1538     }
1539 
1540     // Test where fails: Element is not an object
1541     try
1542     {
1543         const json element = R"( [ "0xFF", "0x01" ] )"_json;
1544         parseConfiguration(element);
1545         ADD_FAILURE() << "Should not have reached this line.";
1546     }
1547     catch (const std::invalid_argument& e)
1548     {
1549         EXPECT_STREQ(e.what(), "Element is not an object");
1550     }
1551 
1552     // Test where fails: Invalid property specified
1553     try
1554     {
1555         const json element = R"(
1556             {
1557               "volts": 1.03,
1558               "rule_id": "set_voltage_rule",
1559               "foo": 1
1560             }
1561         )"_json;
1562         parseConfiguration(element);
1563         ADD_FAILURE() << "Should not have reached this line.";
1564     }
1565     catch (const std::invalid_argument& e)
1566     {
1567         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1568     }
1569 }
1570 
1571 TEST(ConfigFileParserTests, ParseDevice)
1572 {
1573     // Test where works: Only required properties specified
1574     {
1575         const json element = R"(
1576             {
1577               "id": "vdd_regulator",
1578               "is_regulator": true,
1579               "fru": "system/chassis/motherboard/regulator2",
1580               "i2c_interface": { "bus": 1, "address": "0x70" }
1581             }
1582         )"_json;
1583         std::unique_ptr<Device> device = parseDevice(element);
1584         EXPECT_EQ(device->getID(), "vdd_regulator");
1585         EXPECT_EQ(device->isRegulator(), true);
1586         EXPECT_EQ(device->getFRU(), "/xyz/openbmc_project/inventory/system/"
1587                                     "chassis/motherboard/regulator2");
1588         EXPECT_NE(&(device->getI2CInterface()), nullptr);
1589         EXPECT_EQ(device->getPresenceDetection(), nullptr);
1590         EXPECT_EQ(device->getConfiguration(), nullptr);
1591         EXPECT_EQ(device->getPhaseFaultDetection(), nullptr);
1592         EXPECT_EQ(device->getRails().size(), 0);
1593     }
1594 
1595     // Test where works: All properties specified
1596     {
1597         const json element = R"(
1598             {
1599               "comments": [ "VDD Regulator" ],
1600               "id": "vdd_regulator",
1601               "is_regulator": true,
1602               "fru": "system/chassis/motherboard/regulator2",
1603               "i2c_interface":
1604               {
1605                   "bus": 1,
1606                   "address": "0x70"
1607               },
1608               "presence_detection":
1609               {
1610                   "rule_id": "is_foobar_backplane_installed_rule"
1611               },
1612               "configuration":
1613               {
1614                   "rule_id": "configure_ir35221_rule"
1615               },
1616               "phase_fault_detection":
1617               {
1618                   "rule_id": "detect_phase_fault_rule"
1619               },
1620               "rails":
1621               [
1622                 {
1623                   "id": "vdd"
1624                 }
1625               ]
1626             }
1627         )"_json;
1628         std::unique_ptr<Device> device = parseDevice(element);
1629         EXPECT_EQ(device->getID(), "vdd_regulator");
1630         EXPECT_EQ(device->isRegulator(), true);
1631         EXPECT_EQ(device->getFRU(), "/xyz/openbmc_project/inventory/system/"
1632                                     "chassis/motherboard/regulator2");
1633         EXPECT_NE(&(device->getI2CInterface()), nullptr);
1634         EXPECT_NE(device->getPresenceDetection(), nullptr);
1635         EXPECT_NE(device->getConfiguration(), nullptr);
1636         EXPECT_NE(device->getPhaseFaultDetection(), nullptr);
1637         EXPECT_EQ(device->getRails().size(), 1);
1638     }
1639 
1640     // Test where fails: phase_fault_detection property exists and is_regulator
1641     // is false
1642     try
1643     {
1644         const json element = R"(
1645             {
1646               "id": "vdd_regulator",
1647               "is_regulator": false,
1648               "fru": "system/chassis/motherboard/regulator2",
1649               "i2c_interface":
1650               {
1651                   "bus": 1,
1652                   "address": "0x70"
1653               },
1654               "phase_fault_detection":
1655               {
1656                   "rule_id": "detect_phase_fault_rule"
1657               }
1658             }
1659         )"_json;
1660         parseDevice(element);
1661         ADD_FAILURE() << "Should not have reached this line.";
1662     }
1663     catch (const std::invalid_argument& e)
1664     {
1665         EXPECT_STREQ(e.what(), "Invalid phase_fault_detection property when "
1666                                "is_regulator is false");
1667     }
1668 
1669     // Test where fails: rails property exists and is_regulator is false
1670     try
1671     {
1672         const json element = R"(
1673             {
1674               "id": "vdd_regulator",
1675               "is_regulator": false,
1676               "fru": "system/chassis/motherboard/regulator2",
1677               "i2c_interface":
1678               {
1679                   "bus": 1,
1680                   "address": "0x70"
1681               },
1682               "configuration":
1683               {
1684                   "rule_id": "configure_ir35221_rule"
1685               },
1686               "rails":
1687               [
1688                 {
1689                   "id": "vdd"
1690                 }
1691               ]
1692             }
1693         )"_json;
1694         parseDevice(element);
1695         ADD_FAILURE() << "Should not have reached this line.";
1696     }
1697     catch (const std::invalid_argument& e)
1698     {
1699         EXPECT_STREQ(e.what(),
1700                      "Invalid rails property when is_regulator is false");
1701     }
1702 
1703     // Test where fails: id value is invalid
1704     try
1705     {
1706         const json element = R"(
1707             {
1708               "id": 3,
1709               "is_regulator": true,
1710               "fru": "system/chassis/motherboard/regulator2",
1711               "i2c_interface":
1712               {
1713                   "bus": 1,
1714                   "address": "0x70"
1715               }
1716             }
1717         )"_json;
1718         parseDevice(element);
1719         ADD_FAILURE() << "Should not have reached this line.";
1720     }
1721     catch (const std::invalid_argument& e)
1722     {
1723         EXPECT_STREQ(e.what(), "Element is not a string");
1724     }
1725 
1726     // Test where fails: is_regulator value is invalid
1727     try
1728     {
1729         const json element = R"(
1730             {
1731               "id": "vdd_regulator",
1732               "is_regulator": 3,
1733               "fru": "system/chassis/motherboard/regulator2",
1734               "i2c_interface":
1735               {
1736                   "bus": 1,
1737                   "address": "0x70"
1738               }
1739             }
1740         )"_json;
1741         parseDevice(element);
1742         ADD_FAILURE() << "Should not have reached this line.";
1743     }
1744     catch (const std::invalid_argument& e)
1745     {
1746         EXPECT_STREQ(e.what(), "Element is not a boolean");
1747     }
1748 
1749     // Test where fails: fru value is invalid
1750     try
1751     {
1752         const json element = R"(
1753             {
1754               "id": "vdd_regulator",
1755               "is_regulator": true,
1756               "fru": 2,
1757               "i2c_interface":
1758               {
1759                   "bus": 1,
1760                   "address": "0x70"
1761               }
1762             }
1763         )"_json;
1764         parseDevice(element);
1765         ADD_FAILURE() << "Should not have reached this line.";
1766     }
1767     catch (const std::invalid_argument& e)
1768     {
1769         EXPECT_STREQ(e.what(), "Element is not a string");
1770     }
1771 
1772     // Test where fails: i2c_interface value is invalid
1773     try
1774     {
1775         const json element = R"(
1776             {
1777               "id": "vdd_regulator",
1778               "is_regulator": true,
1779               "fru": "system/chassis/motherboard/regulator2",
1780               "i2c_interface": 3
1781             }
1782         )"_json;
1783         parseDevice(element);
1784         ADD_FAILURE() << "Should not have reached this line.";
1785     }
1786     catch (const std::invalid_argument& e)
1787     {
1788         EXPECT_STREQ(e.what(), "Element is not an object");
1789     }
1790 
1791     // Test where fails: Required id property not specified
1792     try
1793     {
1794         const json element = R"(
1795             {
1796               "is_regulator": true,
1797               "fru": "system/chassis/motherboard/regulator2",
1798               "i2c_interface":
1799               {
1800                   "bus": 1,
1801                   "address": "0x70"
1802               }
1803             }
1804         )"_json;
1805         parseDevice(element);
1806         ADD_FAILURE() << "Should not have reached this line.";
1807     }
1808     catch (const std::invalid_argument& e)
1809     {
1810         EXPECT_STREQ(e.what(), "Required property missing: id");
1811     }
1812 
1813     // Test where fails: Required is_regulator property not specified
1814     try
1815     {
1816         const json element = R"(
1817             {
1818               "id": "vdd_regulator",
1819               "fru": "system/chassis/motherboard/regulator2",
1820               "i2c_interface":
1821               {
1822                   "bus": 1,
1823                   "address": "0x70"
1824               }
1825             }
1826         )"_json;
1827         parseDevice(element);
1828         ADD_FAILURE() << "Should not have reached this line.";
1829     }
1830     catch (const std::invalid_argument& e)
1831     {
1832         EXPECT_STREQ(e.what(), "Required property missing: is_regulator");
1833     }
1834 
1835     // Test where fails: Required fru property not specified
1836     try
1837     {
1838         const json element = R"(
1839             {
1840               "id": "vdd_regulator",
1841               "is_regulator": true,
1842               "i2c_interface":
1843               {
1844                   "bus": 1,
1845                   "address": "0x70"
1846               }
1847             }
1848         )"_json;
1849         parseDevice(element);
1850         ADD_FAILURE() << "Should not have reached this line.";
1851     }
1852     catch (const std::invalid_argument& e)
1853     {
1854         EXPECT_STREQ(e.what(), "Required property missing: fru");
1855     }
1856 
1857     // Test where fails: Required i2c_interface property not specified
1858     try
1859     {
1860         const json element = R"(
1861             {
1862               "id": "vdd_regulator",
1863               "is_regulator": true,
1864               "fru": "system/chassis/motherboard/regulator2"
1865             }
1866         )"_json;
1867         parseDevice(element);
1868         ADD_FAILURE() << "Should not have reached this line.";
1869     }
1870     catch (const std::invalid_argument& e)
1871     {
1872         EXPECT_STREQ(e.what(), "Required property missing: i2c_interface");
1873     }
1874 
1875     // Test where fails: Element is not an object
1876     try
1877     {
1878         const json element = R"( [ "0xFF", "0x01" ] )"_json;
1879         parseDevice(element);
1880         ADD_FAILURE() << "Should not have reached this line.";
1881     }
1882     catch (const std::invalid_argument& e)
1883     {
1884         EXPECT_STREQ(e.what(), "Element is not an object");
1885     }
1886 
1887     // Test where fails: Invalid property specified
1888     try
1889     {
1890         const json element = R"(
1891             {
1892               "id": "vdd_regulator",
1893               "is_regulator": true,
1894               "fru": "system/chassis/motherboard/regulator2",
1895               "i2c_interface": { "bus": 1, "address": "0x70" },
1896               "foo" : true
1897             }
1898         )"_json;
1899         parseDevice(element);
1900         ADD_FAILURE() << "Should not have reached this line.";
1901     }
1902     catch (const std::invalid_argument& e)
1903     {
1904         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1905     }
1906 }
1907 
1908 TEST(ConfigFileParserTests, ParseDeviceArray)
1909 {
1910     // Test where works
1911     {
1912         const json element = R"(
1913             [
1914               {
1915                 "id": "vdd_regulator",
1916                 "is_regulator": true,
1917                 "fru": "system/chassis/motherboard/regulator2",
1918                 "i2c_interface": { "bus": 1, "address": "0x70" }
1919               },
1920               {
1921                 "id": "vio_regulator",
1922                 "is_regulator": true,
1923                 "fru": "system/chassis/motherboard/regulator2",
1924                 "i2c_interface": { "bus": 1, "address": "0x71" }
1925               }
1926             ]
1927         )"_json;
1928         std::vector<std::unique_ptr<Device>> devices =
1929             parseDeviceArray(element);
1930         EXPECT_EQ(devices.size(), 2);
1931         EXPECT_EQ(devices[0]->getID(), "vdd_regulator");
1932         EXPECT_EQ(devices[1]->getID(), "vio_regulator");
1933     }
1934 
1935     // Test where fails: Element is not an array
1936     try
1937     {
1938         const json element = R"(
1939             {
1940               "foo": "bar"
1941             }
1942         )"_json;
1943         parseDeviceArray(element);
1944         ADD_FAILURE() << "Should not have reached this line.";
1945     }
1946     catch (const std::invalid_argument& e)
1947     {
1948         EXPECT_STREQ(e.what(), "Element is not an array");
1949     }
1950 }
1951 
1952 TEST(ConfigFileParserTests, ParseDouble)
1953 {
1954     // Test where works: floating point value
1955     {
1956         const json element = R"( 1.03 )"_json;
1957         double value = parseDouble(element);
1958         EXPECT_EQ(value, 1.03);
1959     }
1960 
1961     // Test where works: integer value
1962     {
1963         const json element = R"( 24 )"_json;
1964         double value = parseDouble(element);
1965         EXPECT_EQ(value, 24.0);
1966     }
1967 
1968     // Test where fails: Element is not a number
1969     try
1970     {
1971         const json element = R"( true )"_json;
1972         parseDouble(element);
1973         ADD_FAILURE() << "Should not have reached this line.";
1974     }
1975     catch (const std::invalid_argument& e)
1976     {
1977         EXPECT_STREQ(e.what(), "Element is not a number");
1978     }
1979 }
1980 
1981 TEST(ConfigFileParserTests, ParseHexByte)
1982 {
1983     // Test where works: "0xFF"
1984     {
1985         const json element = R"( "0xFF" )"_json;
1986         uint8_t value = parseHexByte(element);
1987         EXPECT_EQ(value, 0xFF);
1988     }
1989 
1990     // Test where works: "0xff"
1991     {
1992         const json element = R"( "0xff" )"_json;
1993         uint8_t value = parseHexByte(element);
1994         EXPECT_EQ(value, 0xff);
1995     }
1996 
1997     // Test where works: "0xf"
1998     {
1999         const json element = R"( "0xf" )"_json;
2000         uint8_t value = parseHexByte(element);
2001         EXPECT_EQ(value, 0xf);
2002     }
2003 
2004     // Test where fails: "0xfff"
2005     try
2006     {
2007         const json element = R"( "0xfff" )"_json;
2008         parseHexByte(element);
2009         ADD_FAILURE() << "Should not have reached this line.";
2010     }
2011     catch (const std::invalid_argument& e)
2012     {
2013         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2014     }
2015 
2016     // Test where fails: "0xAG"
2017     try
2018     {
2019         const json element = R"( "0xAG" )"_json;
2020         parseHexByte(element);
2021         ADD_FAILURE() << "Should not have reached this line.";
2022     }
2023     catch (const std::invalid_argument& e)
2024     {
2025         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2026     }
2027 
2028     // Test where fails: "ff"
2029     try
2030     {
2031         const json element = R"( "ff" )"_json;
2032         parseHexByte(element);
2033         ADD_FAILURE() << "Should not have reached this line.";
2034     }
2035     catch (const std::invalid_argument& e)
2036     {
2037         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2038     }
2039 
2040     // Test where fails: ""
2041     try
2042     {
2043         const json element = "";
2044         parseHexByte(element);
2045         ADD_FAILURE() << "Should not have reached this line.";
2046     }
2047     catch (const std::invalid_argument& e)
2048     {
2049         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2050     }
2051 
2052     // Test where fails: "f"
2053     try
2054     {
2055         const json element = R"( "f" )"_json;
2056         parseHexByte(element);
2057         ADD_FAILURE() << "Should not have reached this line.";
2058     }
2059     catch (const std::invalid_argument& e)
2060     {
2061         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2062     }
2063 
2064     // Test where fails: "0x"
2065     try
2066     {
2067         const json element = R"( "0x" )"_json;
2068         parseHexByte(element);
2069         ADD_FAILURE() << "Should not have reached this line.";
2070     }
2071     catch (const std::invalid_argument& e)
2072     {
2073         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2074     }
2075 
2076     // Test where fails: "0Xff"
2077     try
2078     {
2079         const json element = R"( "0XFF" )"_json;
2080         parseHexByte(element);
2081         ADD_FAILURE() << "Should not have reached this line.";
2082     }
2083     catch (const std::invalid_argument& e)
2084     {
2085         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2086     }
2087 }
2088 
2089 TEST(ConfigFileParserTests, ParseHexByteArray)
2090 {
2091     // Test where works
2092     {
2093         const json element = R"( [ "0xCC", "0xFF" ] )"_json;
2094         std::vector<uint8_t> hexBytes = parseHexByteArray(element);
2095         std::vector<uint8_t> expected = {0xcc, 0xff};
2096         EXPECT_EQ(hexBytes, expected);
2097     }
2098 
2099     // Test where fails: Element is not an array
2100     try
2101     {
2102         const json element = 0;
2103         parseHexByteArray(element);
2104         ADD_FAILURE() << "Should not have reached this line.";
2105     }
2106     catch (const std::invalid_argument& e)
2107     {
2108         EXPECT_STREQ(e.what(), "Element is not an array");
2109     }
2110 }
2111 
2112 TEST(ConfigFileParserTests, ParseI2CCaptureBytes)
2113 {
2114     // Test where works
2115     {
2116         const json element = R"(
2117             {
2118               "register": "0xA0",
2119               "count": 2
2120             }
2121         )"_json;
2122         std::unique_ptr<I2CCaptureBytesAction> action =
2123             parseI2CCaptureBytes(element);
2124         EXPECT_EQ(action->getRegister(), 0xA0);
2125         EXPECT_EQ(action->getCount(), 2);
2126     }
2127 
2128     // Test where fails: Element is not an object
2129     try
2130     {
2131         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2132         parseI2CCaptureBytes(element);
2133         ADD_FAILURE() << "Should not have reached this line.";
2134     }
2135     catch (const std::invalid_argument& e)
2136     {
2137         EXPECT_STREQ(e.what(), "Element is not an object");
2138     }
2139 
2140     // Test where fails: register value is invalid
2141     try
2142     {
2143         const json element = R"(
2144             {
2145               "register": "0x0Z",
2146               "count": 2
2147             }
2148         )"_json;
2149         parseI2CCaptureBytes(element);
2150         ADD_FAILURE() << "Should not have reached this line.";
2151     }
2152     catch (const std::invalid_argument& e)
2153     {
2154         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2155     }
2156 
2157     // Test where fails: count value is invalid
2158     try
2159     {
2160         const json element = R"(
2161             {
2162               "register": "0xA0",
2163               "count": 0
2164             }
2165         )"_json;
2166         parseI2CCaptureBytes(element);
2167         ADD_FAILURE() << "Should not have reached this line.";
2168     }
2169     catch (const std::invalid_argument& e)
2170     {
2171         EXPECT_STREQ(e.what(), "Invalid byte count: Must be > 0");
2172     }
2173 
2174     // Test where fails: Required register property not specified
2175     try
2176     {
2177         const json element = R"(
2178             {
2179               "count": 2
2180             }
2181         )"_json;
2182         parseI2CCaptureBytes(element);
2183         ADD_FAILURE() << "Should not have reached this line.";
2184     }
2185     catch (const std::invalid_argument& e)
2186     {
2187         EXPECT_STREQ(e.what(), "Required property missing: register");
2188     }
2189 
2190     // Test where fails: Required count property not specified
2191     try
2192     {
2193         const json element = R"(
2194             {
2195               "register": "0xA0"
2196             }
2197         )"_json;
2198         parseI2CCaptureBytes(element);
2199         ADD_FAILURE() << "Should not have reached this line.";
2200     }
2201     catch (const std::invalid_argument& e)
2202     {
2203         EXPECT_STREQ(e.what(), "Required property missing: count");
2204     }
2205 
2206     // Test where fails: Invalid property specified
2207     try
2208     {
2209         const json element = R"(
2210             {
2211               "register": "0xA0",
2212               "count": 2,
2213               "foo": 3
2214             }
2215         )"_json;
2216         parseI2CCaptureBytes(element);
2217         ADD_FAILURE() << "Should not have reached this line.";
2218     }
2219     catch (const std::invalid_argument& e)
2220     {
2221         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2222     }
2223 }
2224 
2225 TEST(ConfigFileParserTests, ParseI2CCompareBit)
2226 {
2227     // Test where works
2228     {
2229         const json element = R"(
2230             {
2231               "register": "0xA0",
2232               "position": 3,
2233               "value": 0
2234             }
2235         )"_json;
2236         std::unique_ptr<I2CCompareBitAction> action =
2237             parseI2CCompareBit(element);
2238         EXPECT_EQ(action->getRegister(), 0xA0);
2239         EXPECT_EQ(action->getPosition(), 3);
2240         EXPECT_EQ(action->getValue(), 0);
2241     }
2242 
2243     // Test where fails: Invalid property specified
2244     try
2245     {
2246         const json element = R"(
2247             {
2248               "register": "0xA0",
2249               "position": 3,
2250               "value": 0,
2251               "foo": 3
2252             }
2253         )"_json;
2254         parseI2CCompareBit(element);
2255         ADD_FAILURE() << "Should not have reached this line.";
2256     }
2257     catch (const std::invalid_argument& e)
2258     {
2259         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2260     }
2261 
2262     // Test where fails: Element is not an object
2263     try
2264     {
2265         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2266         parseI2CCompareBit(element);
2267         ADD_FAILURE() << "Should not have reached this line.";
2268     }
2269     catch (const std::invalid_argument& e)
2270     {
2271         EXPECT_STREQ(e.what(), "Element is not an object");
2272     }
2273 
2274     // Test where fails: register value is invalid
2275     try
2276     {
2277         const json element = R"(
2278             {
2279               "register": "0xAG",
2280               "position": 3,
2281               "value": 0
2282             }
2283         )"_json;
2284         parseI2CCompareBit(element);
2285         ADD_FAILURE() << "Should not have reached this line.";
2286     }
2287     catch (const std::invalid_argument& e)
2288     {
2289         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2290     }
2291 
2292     // Test where fails: position value is invalid
2293     try
2294     {
2295         const json element = R"(
2296                 {
2297                   "register": "0xA0",
2298                   "position": 8,
2299                   "value": 0
2300                 }
2301             )"_json;
2302         parseI2CCompareBit(element);
2303         ADD_FAILURE() << "Should not have reached this line.";
2304     }
2305     catch (const std::invalid_argument& e)
2306     {
2307         EXPECT_STREQ(e.what(), "Element is not a bit position");
2308     }
2309 
2310     // Test where fails: value value is invalid
2311     try
2312     {
2313         const json element = R"(
2314                 {
2315                   "register": "0xA0",
2316                   "position": 3,
2317                   "value": 2
2318                 }
2319             )"_json;
2320         parseI2CCompareBit(element);
2321         ADD_FAILURE() << "Should not have reached this line.";
2322     }
2323     catch (const std::invalid_argument& e)
2324     {
2325         EXPECT_STREQ(e.what(), "Element is not a bit value");
2326     }
2327 
2328     // Test where fails: Required register property not specified
2329     try
2330     {
2331         const json element = R"(
2332             {
2333               "position": 3,
2334               "value": 0
2335             }
2336         )"_json;
2337         parseI2CCompareBit(element);
2338         ADD_FAILURE() << "Should not have reached this line.";
2339     }
2340     catch (const std::invalid_argument& e)
2341     {
2342         EXPECT_STREQ(e.what(), "Required property missing: register");
2343     }
2344 
2345     // Test where fails: Required position property not specified
2346     try
2347     {
2348         const json element = R"(
2349             {
2350               "register": "0xA0",
2351               "value": 0
2352             }
2353         )"_json;
2354         parseI2CCompareBit(element);
2355         ADD_FAILURE() << "Should not have reached this line.";
2356     }
2357     catch (const std::invalid_argument& e)
2358     {
2359         EXPECT_STREQ(e.what(), "Required property missing: position");
2360     }
2361 
2362     // Test where fails: Required value property not specified
2363     try
2364     {
2365         const json element = R"(
2366             {
2367               "register": "0xA0",
2368               "position": 3
2369             }
2370         )"_json;
2371         parseI2CCompareBit(element);
2372         ADD_FAILURE() << "Should not have reached this line.";
2373     }
2374     catch (const std::invalid_argument& e)
2375     {
2376         EXPECT_STREQ(e.what(), "Required property missing: value");
2377     }
2378 }
2379 
2380 TEST(ConfigFileParserTests, ParseI2CCompareByte)
2381 {
2382     // Test where works: Only required properties specified
2383     {
2384         const json element = R"(
2385             {
2386               "register": "0x0A",
2387               "value": "0xCC"
2388             }
2389         )"_json;
2390         std::unique_ptr<I2CCompareByteAction> action =
2391             parseI2CCompareByte(element);
2392         EXPECT_EQ(action->getRegister(), 0x0A);
2393         EXPECT_EQ(action->getValue(), 0xCC);
2394         EXPECT_EQ(action->getMask(), 0xFF);
2395     }
2396 
2397     // Test where works: All properties specified
2398     {
2399         const json element = R"(
2400             {
2401               "register": "0x0A",
2402               "value": "0xCC",
2403               "mask": "0xF7"
2404             }
2405         )"_json;
2406         std::unique_ptr<I2CCompareByteAction> action =
2407             parseI2CCompareByte(element);
2408         EXPECT_EQ(action->getRegister(), 0x0A);
2409         EXPECT_EQ(action->getValue(), 0xCC);
2410         EXPECT_EQ(action->getMask(), 0xF7);
2411     }
2412 
2413     // Test where fails: Element is not an object
2414     try
2415     {
2416         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2417         parseI2CCompareByte(element);
2418         ADD_FAILURE() << "Should not have reached this line.";
2419     }
2420     catch (const std::invalid_argument& e)
2421     {
2422         EXPECT_STREQ(e.what(), "Element is not an object");
2423     }
2424 
2425     // Test where fails: Invalid property specified
2426     try
2427     {
2428         const json element = R"(
2429             {
2430               "register": "0x0A",
2431               "value": "0xCC",
2432               "mask": "0xF7",
2433               "foo": 1
2434             }
2435         )"_json;
2436         parseI2CCompareByte(element);
2437         ADD_FAILURE() << "Should not have reached this line.";
2438     }
2439     catch (const std::invalid_argument& e)
2440     {
2441         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2442     }
2443 
2444     // Test where fails: register value is invalid
2445     try
2446     {
2447         const json element = R"(
2448             {
2449               "register": "0x0Z",
2450               "value": "0xCC",
2451               "mask": "0xF7"
2452             }
2453         )"_json;
2454         parseI2CCompareByte(element);
2455         ADD_FAILURE() << "Should not have reached this line.";
2456     }
2457     catch (const std::invalid_argument& e)
2458     {
2459         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2460     }
2461 
2462     // Test where fails: value value is invalid
2463     try
2464     {
2465         const json element = R"(
2466             {
2467               "register": "0x0A",
2468               "value": "0xCCC",
2469               "mask": "0xF7"
2470             }
2471         )"_json;
2472         parseI2CCompareByte(element);
2473         ADD_FAILURE() << "Should not have reached this line.";
2474     }
2475     catch (const std::invalid_argument& e)
2476     {
2477         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2478     }
2479 
2480     // Test where fails: mask value is invalid
2481     try
2482     {
2483         const json element = R"(
2484             {
2485               "register": "0x0A",
2486               "value": "0xCC",
2487               "mask": "F7"
2488             }
2489         )"_json;
2490         parseI2CCompareByte(element);
2491         ADD_FAILURE() << "Should not have reached this line.";
2492     }
2493     catch (const std::invalid_argument& e)
2494     {
2495         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2496     }
2497 
2498     // Test where fails: Required register property not specified
2499     try
2500     {
2501         const json element = R"(
2502             {
2503               "value": "0xCC",
2504               "mask": "0xF7"
2505             }
2506         )"_json;
2507         parseI2CCompareByte(element);
2508         ADD_FAILURE() << "Should not have reached this line.";
2509     }
2510     catch (const std::invalid_argument& e)
2511     {
2512         EXPECT_STREQ(e.what(), "Required property missing: register");
2513     }
2514 
2515     // Test where fails: Required value property not specified
2516     try
2517     {
2518         const json element = R"(
2519             {
2520               "register": "0x0A",
2521               "mask": "0xF7"
2522             }
2523         )"_json;
2524         parseI2CCompareByte(element);
2525         ADD_FAILURE() << "Should not have reached this line.";
2526     }
2527     catch (const std::invalid_argument& e)
2528     {
2529         EXPECT_STREQ(e.what(), "Required property missing: value");
2530     }
2531 }
2532 
2533 TEST(ConfigFileParserTests, ParseI2CCompareBytes)
2534 {
2535     // Test where works: Only required properties specified
2536     {
2537         const json element = R"(
2538             {
2539               "register": "0x0A",
2540               "values": [ "0xCC", "0xFF" ]
2541             }
2542         )"_json;
2543         std::unique_ptr<I2CCompareBytesAction> action =
2544             parseI2CCompareBytes(element);
2545         EXPECT_EQ(action->getRegister(), 0x0A);
2546         EXPECT_EQ(action->getValues().size(), 2);
2547         EXPECT_EQ(action->getValues()[0], 0xCC);
2548         EXPECT_EQ(action->getValues()[1], 0xFF);
2549         EXPECT_EQ(action->getMasks().size(), 2);
2550         EXPECT_EQ(action->getMasks()[0], 0xFF);
2551         EXPECT_EQ(action->getMasks()[1], 0xFF);
2552     }
2553 
2554     // Test where works: All properties specified
2555     {
2556         const json element = R"(
2557             {
2558               "register": "0x0A",
2559               "values": [ "0xCC", "0xFF" ],
2560               "masks":  [ "0x7F", "0x77" ]
2561             }
2562         )"_json;
2563         std::unique_ptr<I2CCompareBytesAction> action =
2564             parseI2CCompareBytes(element);
2565         EXPECT_EQ(action->getRegister(), 0x0A);
2566         EXPECT_EQ(action->getValues().size(), 2);
2567         EXPECT_EQ(action->getValues()[0], 0xCC);
2568         EXPECT_EQ(action->getValues()[1], 0xFF);
2569         EXPECT_EQ(action->getMasks().size(), 2);
2570         EXPECT_EQ(action->getMasks()[0], 0x7F);
2571         EXPECT_EQ(action->getMasks()[1], 0x77);
2572     }
2573 
2574     // Test where fails: Element is not an object
2575     try
2576     {
2577         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2578         parseI2CCompareBytes(element);
2579         ADD_FAILURE() << "Should not have reached this line.";
2580     }
2581     catch (const std::invalid_argument& e)
2582     {
2583         EXPECT_STREQ(e.what(), "Element is not an object");
2584     }
2585 
2586     // Test where fails: Invalid property specified
2587     try
2588     {
2589         const json element = R"(
2590             {
2591               "register": "0x0A",
2592               "values": [ "0xCC", "0xFF" ],
2593               "masks":  [ "0x7F", "0x7F" ],
2594               "foo": 1
2595             }
2596         )"_json;
2597         parseI2CCompareBytes(element);
2598         ADD_FAILURE() << "Should not have reached this line.";
2599     }
2600     catch (const std::invalid_argument& e)
2601     {
2602         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2603     }
2604 
2605     // Test where fails: register value is invalid
2606     try
2607     {
2608         const json element = R"(
2609             {
2610               "register": "0x0Z",
2611               "values": [ "0xCC", "0xFF" ],
2612               "masks":  [ "0x7F", "0x7F" ]
2613             }
2614         )"_json;
2615         parseI2CCompareBytes(element);
2616         ADD_FAILURE() << "Should not have reached this line.";
2617     }
2618     catch (const std::invalid_argument& e)
2619     {
2620         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2621     }
2622 
2623     // Test where fails: values value is invalid
2624     try
2625     {
2626         const json element = R"(
2627             {
2628               "register": "0x0A",
2629               "values": [ "0xCCC", "0xFF" ],
2630               "masks":  [ "0x7F", "0x7F" ]
2631             }
2632         )"_json;
2633         parseI2CCompareBytes(element);
2634         ADD_FAILURE() << "Should not have reached this line.";
2635     }
2636     catch (const std::invalid_argument& e)
2637     {
2638         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2639     }
2640 
2641     // Test where fails: masks value is invalid
2642     try
2643     {
2644         const json element = R"(
2645             {
2646               "register": "0x0A",
2647               "values": [ "0xCC", "0xFF" ],
2648               "masks":  [ "F", "0x7F" ]
2649             }
2650         )"_json;
2651         parseI2CCompareBytes(element);
2652         ADD_FAILURE() << "Should not have reached this line.";
2653     }
2654     catch (const std::invalid_argument& e)
2655     {
2656         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2657     }
2658 
2659     // Test where fails: number of elements in masks is invalid
2660     try
2661     {
2662         const json element = R"(
2663             {
2664               "register": "0x0A",
2665               "values": [ "0xCC", "0xFF" ],
2666               "masks":  [ "0x7F" ]
2667             }
2668         )"_json;
2669         parseI2CCompareBytes(element);
2670         ADD_FAILURE() << "Should not have reached this line.";
2671     }
2672     catch (const std::invalid_argument& e)
2673     {
2674         EXPECT_STREQ(e.what(), "Invalid number of elements in masks");
2675     }
2676 
2677     // Test where fails: Required register property not specified
2678     try
2679     {
2680         const json element = R"(
2681             {
2682               "values": [ "0xCC", "0xFF" ]
2683             }
2684         )"_json;
2685         parseI2CCompareBytes(element);
2686         ADD_FAILURE() << "Should not have reached this line.";
2687     }
2688     catch (const std::invalid_argument& e)
2689     {
2690         EXPECT_STREQ(e.what(), "Required property missing: register");
2691     }
2692 
2693     // Test where fails: Required values property not specified
2694     try
2695     {
2696         const json element = R"(
2697             {
2698               "register": "0x0A"
2699             }
2700         )"_json;
2701         parseI2CCompareBytes(element);
2702         ADD_FAILURE() << "Should not have reached this line.";
2703     }
2704     catch (const std::invalid_argument& e)
2705     {
2706         EXPECT_STREQ(e.what(), "Required property missing: values");
2707     }
2708 }
2709 
2710 TEST(ConfigFileParserTests, ParseI2CWriteBit)
2711 {
2712     // Test where works
2713     {
2714         const json element = R"(
2715             {
2716               "register": "0xA0",
2717               "position": 3,
2718               "value": 0
2719             }
2720         )"_json;
2721         std::unique_ptr<I2CWriteBitAction> action = parseI2CWriteBit(element);
2722         EXPECT_EQ(action->getRegister(), 0xA0);
2723         EXPECT_EQ(action->getPosition(), 3);
2724         EXPECT_EQ(action->getValue(), 0);
2725     }
2726 
2727     // Test where fails: Invalid property specified
2728     try
2729     {
2730         const json element = R"(
2731             {
2732               "register": "0xA0",
2733               "position": 3,
2734               "value": 0,
2735               "foo": 3
2736             }
2737         )"_json;
2738         parseI2CWriteBit(element);
2739         ADD_FAILURE() << "Should not have reached this line.";
2740     }
2741     catch (const std::invalid_argument& e)
2742     {
2743         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2744     }
2745 
2746     // Test where fails: Element is not an object
2747     try
2748     {
2749         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2750         parseI2CWriteBit(element);
2751         ADD_FAILURE() << "Should not have reached this line.";
2752     }
2753     catch (const std::invalid_argument& e)
2754     {
2755         EXPECT_STREQ(e.what(), "Element is not an object");
2756     }
2757 
2758     // Test where fails: register value is invalid
2759     try
2760     {
2761         const json element = R"(
2762             {
2763               "register": "0xAG",
2764               "position": 3,
2765               "value": 0
2766             }
2767         )"_json;
2768         parseI2CWriteBit(element);
2769         ADD_FAILURE() << "Should not have reached this line.";
2770     }
2771     catch (const std::invalid_argument& e)
2772     {
2773         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2774     }
2775 
2776     // Test where fails: position value is invalid
2777     try
2778     {
2779         const json element = R"(
2780                 {
2781                   "register": "0xA0",
2782                   "position": 8,
2783                   "value": 0
2784                 }
2785             )"_json;
2786         parseI2CWriteBit(element);
2787         ADD_FAILURE() << "Should not have reached this line.";
2788     }
2789     catch (const std::invalid_argument& e)
2790     {
2791         EXPECT_STREQ(e.what(), "Element is not a bit position");
2792     }
2793 
2794     // Test where fails: value value is invalid
2795     try
2796     {
2797         const json element = R"(
2798                 {
2799                   "register": "0xA0",
2800                   "position": 3,
2801                   "value": 2
2802                 }
2803             )"_json;
2804         parseI2CWriteBit(element);
2805         ADD_FAILURE() << "Should not have reached this line.";
2806     }
2807     catch (const std::invalid_argument& e)
2808     {
2809         EXPECT_STREQ(e.what(), "Element is not a bit value");
2810     }
2811 
2812     // Test where fails: Required register property not specified
2813     try
2814     {
2815         const json element = R"(
2816             {
2817               "position": 3,
2818               "value": 0
2819             }
2820         )"_json;
2821         parseI2CWriteBit(element);
2822         ADD_FAILURE() << "Should not have reached this line.";
2823     }
2824     catch (const std::invalid_argument& e)
2825     {
2826         EXPECT_STREQ(e.what(), "Required property missing: register");
2827     }
2828 
2829     // Test where fails: Required position property not specified
2830     try
2831     {
2832         const json element = R"(
2833             {
2834               "register": "0xA0",
2835               "value": 0
2836             }
2837         )"_json;
2838         parseI2CWriteBit(element);
2839         ADD_FAILURE() << "Should not have reached this line.";
2840     }
2841     catch (const std::invalid_argument& e)
2842     {
2843         EXPECT_STREQ(e.what(), "Required property missing: position");
2844     }
2845 
2846     // Test where fails: Required value property not specified
2847     try
2848     {
2849         const json element = R"(
2850             {
2851               "register": "0xA0",
2852               "position": 3
2853             }
2854         )"_json;
2855         parseI2CWriteBit(element);
2856         ADD_FAILURE() << "Should not have reached this line.";
2857     }
2858     catch (const std::invalid_argument& e)
2859     {
2860         EXPECT_STREQ(e.what(), "Required property missing: value");
2861     }
2862 }
2863 
2864 TEST(ConfigFileParserTests, ParseI2CWriteByte)
2865 {
2866     // Test where works: Only required properties specified
2867     {
2868         const json element = R"(
2869             {
2870               "register": "0x0A",
2871               "value": "0xCC"
2872             }
2873         )"_json;
2874         std::unique_ptr<I2CWriteByteAction> action = parseI2CWriteByte(element);
2875         EXPECT_EQ(action->getRegister(), 0x0A);
2876         EXPECT_EQ(action->getValue(), 0xCC);
2877         EXPECT_EQ(action->getMask(), 0xFF);
2878     }
2879 
2880     // Test where works: All properties specified
2881     {
2882         const json element = R"(
2883             {
2884               "register": "0x0A",
2885               "value": "0xCC",
2886               "mask": "0xF7"
2887             }
2888         )"_json;
2889         std::unique_ptr<I2CWriteByteAction> action = parseI2CWriteByte(element);
2890         EXPECT_EQ(action->getRegister(), 0x0A);
2891         EXPECT_EQ(action->getValue(), 0xCC);
2892         EXPECT_EQ(action->getMask(), 0xF7);
2893     }
2894 
2895     // Test where fails: Element is not an object
2896     try
2897     {
2898         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2899         parseI2CWriteByte(element);
2900         ADD_FAILURE() << "Should not have reached this line.";
2901     }
2902     catch (const std::invalid_argument& e)
2903     {
2904         EXPECT_STREQ(e.what(), "Element is not an object");
2905     }
2906 
2907     // Test where fails: Invalid property specified
2908     try
2909     {
2910         const json element = R"(
2911             {
2912               "register": "0x0A",
2913               "value": "0xCC",
2914               "mask": "0xF7",
2915               "foo": 1
2916             }
2917         )"_json;
2918         parseI2CWriteByte(element);
2919         ADD_FAILURE() << "Should not have reached this line.";
2920     }
2921     catch (const std::invalid_argument& e)
2922     {
2923         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2924     }
2925 
2926     // Test where fails: register value is invalid
2927     try
2928     {
2929         const json element = R"(
2930             {
2931               "register": "0x0Z",
2932               "value": "0xCC",
2933               "mask": "0xF7"
2934             }
2935         )"_json;
2936         parseI2CWriteByte(element);
2937         ADD_FAILURE() << "Should not have reached this line.";
2938     }
2939     catch (const std::invalid_argument& e)
2940     {
2941         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2942     }
2943 
2944     // Test where fails: value value is invalid
2945     try
2946     {
2947         const json element = R"(
2948             {
2949               "register": "0x0A",
2950               "value": "0xCCC",
2951               "mask": "0xF7"
2952             }
2953         )"_json;
2954         parseI2CWriteByte(element);
2955         ADD_FAILURE() << "Should not have reached this line.";
2956     }
2957     catch (const std::invalid_argument& e)
2958     {
2959         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2960     }
2961 
2962     // Test where fails: mask value is invalid
2963     try
2964     {
2965         const json element = R"(
2966             {
2967               "register": "0x0A",
2968               "value": "0xCC",
2969               "mask": "F7"
2970             }
2971         )"_json;
2972         parseI2CWriteByte(element);
2973         ADD_FAILURE() << "Should not have reached this line.";
2974     }
2975     catch (const std::invalid_argument& e)
2976     {
2977         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2978     }
2979 
2980     // Test where fails: Required register property not specified
2981     try
2982     {
2983         const json element = R"(
2984             {
2985               "value": "0xCC",
2986               "mask": "0xF7"
2987             }
2988         )"_json;
2989         parseI2CWriteByte(element);
2990         ADD_FAILURE() << "Should not have reached this line.";
2991     }
2992     catch (const std::invalid_argument& e)
2993     {
2994         EXPECT_STREQ(e.what(), "Required property missing: register");
2995     }
2996 
2997     // Test where fails: Required value property not specified
2998     try
2999     {
3000         const json element = R"(
3001             {
3002               "register": "0x0A",
3003               "mask": "0xF7"
3004             }
3005         )"_json;
3006         parseI2CWriteByte(element);
3007         ADD_FAILURE() << "Should not have reached this line.";
3008     }
3009     catch (const std::invalid_argument& e)
3010     {
3011         EXPECT_STREQ(e.what(), "Required property missing: value");
3012     }
3013 }
3014 
3015 TEST(ConfigFileParserTests, ParseI2CWriteBytes)
3016 {
3017     // Test where works: Only required properties specified
3018     {
3019         const json element = R"(
3020             {
3021               "register": "0x0A",
3022               "values": [ "0xCC", "0xFF" ]
3023             }
3024         )"_json;
3025         std::unique_ptr<I2CWriteBytesAction> action =
3026             parseI2CWriteBytes(element);
3027         EXPECT_EQ(action->getRegister(), 0x0A);
3028         EXPECT_EQ(action->getValues().size(), 2);
3029         EXPECT_EQ(action->getValues()[0], 0xCC);
3030         EXPECT_EQ(action->getValues()[1], 0xFF);
3031         EXPECT_EQ(action->getMasks().size(), 0);
3032     }
3033 
3034     // Test where works: All properties specified
3035     {
3036         const json element = R"(
3037             {
3038               "register": "0x0A",
3039               "values": [ "0xCC", "0xFF" ],
3040               "masks":  [ "0x7F", "0x77" ]
3041             }
3042         )"_json;
3043         std::unique_ptr<I2CWriteBytesAction> action =
3044             parseI2CWriteBytes(element);
3045         EXPECT_EQ(action->getRegister(), 0x0A);
3046         EXPECT_EQ(action->getValues().size(), 2);
3047         EXPECT_EQ(action->getValues()[0], 0xCC);
3048         EXPECT_EQ(action->getValues()[1], 0xFF);
3049         EXPECT_EQ(action->getMasks().size(), 2);
3050         EXPECT_EQ(action->getMasks()[0], 0x7F);
3051         EXPECT_EQ(action->getMasks()[1], 0x77);
3052     }
3053 
3054     // Test where fails: Element is not an object
3055     try
3056     {
3057         const json element = R"( [ "0xFF", "0x01" ] )"_json;
3058         parseI2CWriteBytes(element);
3059         ADD_FAILURE() << "Should not have reached this line.";
3060     }
3061     catch (const std::invalid_argument& e)
3062     {
3063         EXPECT_STREQ(e.what(), "Element is not an object");
3064     }
3065 
3066     // Test where fails: Invalid property specified
3067     try
3068     {
3069         const json element = R"(
3070             {
3071               "register": "0x0A",
3072               "values": [ "0xCC", "0xFF" ],
3073               "masks":  [ "0x7F", "0x7F" ],
3074               "foo": 1
3075             }
3076         )"_json;
3077         parseI2CWriteBytes(element);
3078         ADD_FAILURE() << "Should not have reached this line.";
3079     }
3080     catch (const std::invalid_argument& e)
3081     {
3082         EXPECT_STREQ(e.what(), "Element contains an invalid property");
3083     }
3084 
3085     // Test where fails: register value is invalid
3086     try
3087     {
3088         const json element = R"(
3089             {
3090               "register": "0x0Z",
3091               "values": [ "0xCC", "0xFF" ],
3092               "masks":  [ "0x7F", "0x7F" ]
3093             }
3094         )"_json;
3095         parseI2CWriteBytes(element);
3096         ADD_FAILURE() << "Should not have reached this line.";
3097     }
3098     catch (const std::invalid_argument& e)
3099     {
3100         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
3101     }
3102 
3103     // Test where fails: values value is invalid
3104     try
3105     {
3106         const json element = R"(
3107             {
3108               "register": "0x0A",
3109               "values": [ "0xCCC", "0xFF" ],
3110               "masks":  [ "0x7F", "0x7F" ]
3111             }
3112         )"_json;
3113         parseI2CWriteBytes(element);
3114         ADD_FAILURE() << "Should not have reached this line.";
3115     }
3116     catch (const std::invalid_argument& e)
3117     {
3118         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
3119     }
3120 
3121     // Test where fails: masks value is invalid
3122     try
3123     {
3124         const json element = R"(
3125             {
3126               "register": "0x0A",
3127               "values": [ "0xCC", "0xFF" ],
3128               "masks":  [ "F", "0x7F" ]
3129             }
3130         )"_json;
3131         parseI2CWriteBytes(element);
3132         ADD_FAILURE() << "Should not have reached this line.";
3133     }
3134     catch (const std::invalid_argument& e)
3135     {
3136         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
3137     }
3138 
3139     // Test where fails: number of elements in masks is invalid
3140     try
3141     {
3142         const json element = R"(
3143             {
3144               "register": "0x0A",
3145               "values": [ "0xCC", "0xFF" ],
3146               "masks":  [ "0x7F" ]
3147             }
3148         )"_json;
3149         parseI2CWriteBytes(element);
3150         ADD_FAILURE() << "Should not have reached this line.";
3151     }
3152     catch (const std::invalid_argument& e)
3153     {
3154         EXPECT_STREQ(e.what(), "Invalid number of elements in masks");
3155     }
3156 
3157     // Test where fails: Required register property not specified
3158     try
3159     {
3160         const json element = R"(
3161             {
3162               "values": [ "0xCC", "0xFF" ]
3163             }
3164         )"_json;
3165         parseI2CWriteBytes(element);
3166         ADD_FAILURE() << "Should not have reached this line.";
3167     }
3168     catch (const std::invalid_argument& e)
3169     {
3170         EXPECT_STREQ(e.what(), "Required property missing: register");
3171     }
3172 
3173     // Test where fails: Required values property not specified
3174     try
3175     {
3176         const json element = R"(
3177             {
3178               "register": "0x0A"
3179             }
3180         )"_json;
3181         parseI2CWriteBytes(element);
3182         ADD_FAILURE() << "Should not have reached this line.";
3183     }
3184     catch (const std::invalid_argument& e)
3185     {
3186         EXPECT_STREQ(e.what(), "Required property missing: values");
3187     }
3188 }
3189 
3190 TEST(ConfigFileParserTests, ParseIf)
3191 {
3192     // Test where works: Only required properties specified
3193     {
3194         const json element = R"(
3195             {
3196               "condition": { "run_rule": "is_downlevel_regulator" },
3197               "then": [ { "run_rule": "configure_downlevel_regulator" },
3198                         { "run_rule": "configure_standard_regulator" } ]
3199             }
3200         )"_json;
3201         std::unique_ptr<IfAction> action = parseIf(element);
3202         EXPECT_NE(action->getConditionAction().get(), nullptr);
3203         EXPECT_EQ(action->getThenActions().size(), 2);
3204         EXPECT_EQ(action->getElseActions().size(), 0);
3205     }
3206 
3207     // Test where works: All properties specified
3208     {
3209         const json element = R"(
3210             {
3211               "condition": { "run_rule": "is_downlevel_regulator" },
3212               "then": [ { "run_rule": "configure_downlevel_regulator" } ],
3213               "else": [ { "run_rule": "configure_standard_regulator" } ]
3214             }
3215         )"_json;
3216         std::unique_ptr<IfAction> action = parseIf(element);
3217         EXPECT_NE(action->getConditionAction().get(), nullptr);
3218         EXPECT_EQ(action->getThenActions().size(), 1);
3219         EXPECT_EQ(action->getElseActions().size(), 1);
3220     }
3221 
3222     // Test where fails: Required condition property not specified
3223     try
3224     {
3225         const json element = R"(
3226             {
3227               "then": [ { "run_rule": "configure_downlevel_regulator" } ],
3228               "else": [ { "run_rule": "configure_standard_regulator" } ]
3229             }
3230         )"_json;
3231         parseIf(element);
3232         ADD_FAILURE() << "Should not have reached this line.";
3233     }
3234     catch (const std::invalid_argument& e)
3235     {
3236         EXPECT_STREQ(e.what(), "Required property missing: condition");
3237     }
3238 
3239     // Test where fails: Required then property not specified
3240     try
3241     {
3242         const json element = R"(
3243             {
3244               "condition": { "run_rule": "is_downlevel_regulator" },
3245               "else": [ { "run_rule": "configure_standard_regulator" } ]
3246             }
3247         )"_json;
3248         parseIf(element);
3249         ADD_FAILURE() << "Should not have reached this line.";
3250     }
3251     catch (const std::invalid_argument& e)
3252     {
3253         EXPECT_STREQ(e.what(), "Required property missing: then");
3254     }
3255 
3256     // Test where fails: condition value is invalid
3257     try
3258     {
3259         const json element = R"(
3260             {
3261               "condition": 1,
3262               "then": [ { "run_rule": "configure_downlevel_regulator" } ],
3263               "else": [ { "run_rule": "configure_standard_regulator" } ]
3264             }
3265         )"_json;
3266         parseIf(element);
3267         ADD_FAILURE() << "Should not have reached this line.";
3268     }
3269     catch (const std::invalid_argument& e)
3270     {
3271         EXPECT_STREQ(e.what(), "Element is not an object");
3272     }
3273 
3274     // Test where fails: then value is invalid
3275     try
3276     {
3277         const json element = R"(
3278             {
3279               "condition": { "run_rule": "is_downlevel_regulator" },
3280               "then": "foo",
3281               "else": [ { "run_rule": "configure_standard_regulator" } ]
3282             }
3283         )"_json;
3284         parseIf(element);
3285         ADD_FAILURE() << "Should not have reached this line.";
3286     }
3287     catch (const std::invalid_argument& e)
3288     {
3289         EXPECT_STREQ(e.what(), "Element is not an array");
3290     }
3291 
3292     // Test where fails: else value is invalid
3293     try
3294     {
3295         const json element = R"(
3296             {
3297               "condition": { "run_rule": "is_downlevel_regulator" },
3298               "then": [ { "run_rule": "configure_downlevel_regulator" } ],
3299               "else": 1
3300             }
3301         )"_json;
3302         parseIf(element);
3303         ADD_FAILURE() << "Should not have reached this line.";
3304     }
3305     catch (const std::invalid_argument& e)
3306     {
3307         EXPECT_STREQ(e.what(), "Element is not an array");
3308     }
3309 
3310     // Test where fails: Invalid property specified
3311     try
3312     {
3313         const json element = R"(
3314             {
3315               "condition": { "run_rule": "is_downlevel_regulator" },
3316               "then": [ { "run_rule": "configure_downlevel_regulator" } ],
3317               "foo": "bar"
3318             }
3319         )"_json;
3320         parseIf(element);
3321         ADD_FAILURE() << "Should not have reached this line.";
3322     }
3323     catch (const std::invalid_argument& e)
3324     {
3325         EXPECT_STREQ(e.what(), "Element contains an invalid property");
3326     }
3327 
3328     // Test where fails: Element is not an object
3329     try
3330     {
3331         const json element = R"( [ "0xFF", "0x01" ] )"_json;
3332         parseIf(element);
3333         ADD_FAILURE() << "Should not have reached this line.";
3334     }
3335     catch (const std::invalid_argument& e)
3336     {
3337         EXPECT_STREQ(e.what(), "Element is not an object");
3338     }
3339 }
3340 
3341 TEST(ConfigFileParserTests, ParseInt8)
3342 {
3343     // Test where works: INT8_MIN
3344     {
3345         const json element = R"( -128 )"_json;
3346         int8_t value = parseInt8(element);
3347         EXPECT_EQ(value, -128);
3348     }
3349 
3350     // Test where works: INT8_MAX
3351     {
3352         const json element = R"( 127 )"_json;
3353         int8_t value = parseInt8(element);
3354         EXPECT_EQ(value, 127);
3355     }
3356 
3357     // Test where fails: Element is not an integer
3358     try
3359     {
3360         const json element = R"( 1.03 )"_json;
3361         parseInt8(element);
3362         ADD_FAILURE() << "Should not have reached this line.";
3363     }
3364     catch (const std::invalid_argument& e)
3365     {
3366         EXPECT_STREQ(e.what(), "Element is not an integer");
3367     }
3368 
3369     // Test where fails: Value < INT8_MIN
3370     try
3371     {
3372         const json element = R"( -129 )"_json;
3373         parseInt8(element);
3374         ADD_FAILURE() << "Should not have reached this line.";
3375     }
3376     catch (const std::invalid_argument& e)
3377     {
3378         EXPECT_STREQ(e.what(), "Element is not an 8-bit signed integer");
3379     }
3380 
3381     // Test where fails: Value > INT8_MAX
3382     try
3383     {
3384         const json element = R"( 128 )"_json;
3385         parseInt8(element);
3386         ADD_FAILURE() << "Should not have reached this line.";
3387     }
3388     catch (const std::invalid_argument& e)
3389     {
3390         EXPECT_STREQ(e.what(), "Element is not an 8-bit signed integer");
3391     }
3392 }
3393 
3394 TEST(ConfigFileParserTests, ParseInventoryPath)
3395 {
3396     // Test where works: Inventory path has a leading '/'
3397     {
3398         const json element = "/system/chassis/motherboard/cpu3";
3399         std::string value = parseInventoryPath(element);
3400         EXPECT_EQ(
3401             value,
3402             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu3");
3403     }
3404 
3405     // Test where works: Inventory path does not have a leading '/'
3406     {
3407         const json element = "system/chassis/motherboard/cpu1";
3408         std::string value = parseInventoryPath(element);
3409         EXPECT_EQ(
3410             value,
3411             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1");
3412     }
3413 
3414     // Test where fails: JSON element is not a string
3415     try
3416     {
3417         const json element = R"( { "foo": "bar" } )"_json;
3418         parseInventoryPath(element);
3419         ADD_FAILURE() << "Should not have reached this line.";
3420     }
3421     catch (const std::invalid_argument& e)
3422     {
3423         EXPECT_STREQ(e.what(), "Element is not a string");
3424     }
3425 
3426     // Test where fails: JSON element contains an empty string
3427     try
3428     {
3429         const json element = "";
3430         parseInventoryPath(element);
3431         ADD_FAILURE() << "Should not have reached this line.";
3432     }
3433     catch (const std::invalid_argument& e)
3434     {
3435         EXPECT_STREQ(e.what(), "Element contains an empty string");
3436     }
3437 }
3438 
3439 TEST(ConfigFileParserTests, ParseLogPhaseFault)
3440 {
3441     // Test where works
3442     {
3443         const json element = R"(
3444             {
3445               "type": "n+1"
3446             }
3447         )"_json;
3448         std::unique_ptr<LogPhaseFaultAction> action =
3449             parseLogPhaseFault(element);
3450         EXPECT_EQ(action->getType(), PhaseFaultType::n_plus_1);
3451     }
3452 
3453     // Test where fails: Element is not an object
3454     try
3455     {
3456         const json element = R"( [ "0xFF", "0x01" ] )"_json;
3457         parseLogPhaseFault(element);
3458         ADD_FAILURE() << "Should not have reached this line.";
3459     }
3460     catch (const std::invalid_argument& e)
3461     {
3462         EXPECT_STREQ(e.what(), "Element is not an object");
3463     }
3464 
3465     // Test where fails: Required type property not specified
3466     try
3467     {
3468         const json element = R"(
3469             {
3470             }
3471         )"_json;
3472         parseLogPhaseFault(element);
3473         ADD_FAILURE() << "Should not have reached this line.";
3474     }
3475     catch (const std::invalid_argument& e)
3476     {
3477         EXPECT_STREQ(e.what(), "Required property missing: type");
3478     }
3479 
3480     // Test where fails: type value is invalid
3481     try
3482     {
3483         const json element = R"(
3484             {
3485               "type": "n+2"
3486             }
3487         )"_json;
3488         parseLogPhaseFault(element);
3489         ADD_FAILURE() << "Should not have reached this line.";
3490     }
3491     catch (const std::invalid_argument& e)
3492     {
3493         EXPECT_STREQ(e.what(), "Element is not a phase fault type");
3494     }
3495 
3496     // Test where fails: Invalid property specified
3497     try
3498     {
3499         const json element = R"(
3500             {
3501               "type": "n+1",
3502               "foo": 1
3503             }
3504         )"_json;
3505         parseLogPhaseFault(element);
3506         ADD_FAILURE() << "Should not have reached this line.";
3507     }
3508     catch (const std::invalid_argument& e)
3509     {
3510         EXPECT_STREQ(e.what(), "Element contains an invalid property");
3511     }
3512 }
3513 
3514 TEST(ConfigFileParserTests, ParseNot)
3515 {
3516     // Test where works
3517     {
3518         const json element = R"(
3519             { "i2c_compare_byte": { "register": "0xA0", "value": "0x00" } }
3520         )"_json;
3521         std::unique_ptr<NotAction> action = parseNot(element);
3522         EXPECT_NE(action->getAction().get(), nullptr);
3523     }
3524 
3525     // Test where fails: Element is not an object
3526     try
3527     {
3528         const json element = R"( [ "0xFF", "0x01" ] )"_json;
3529         parseNot(element);
3530         ADD_FAILURE() << "Should not have reached this line.";
3531     }
3532     catch (const std::invalid_argument& e)
3533     {
3534         EXPECT_STREQ(e.what(), "Element is not an object");
3535     }
3536 }
3537 
3538 TEST(ConfigFileParserTests, ParseOr)
3539 {
3540     // Test where works: Element is an array with 2 actions
3541     {
3542         const json element = R"(
3543             [
3544               { "i2c_compare_byte": { "register": "0xA0", "value": "0x00" } },
3545               { "i2c_compare_byte": { "register": "0xA1", "value": "0x00" } }
3546             ]
3547         )"_json;
3548         std::unique_ptr<OrAction> action = parseOr(element);
3549         EXPECT_EQ(action->getActions().size(), 2);
3550     }
3551 
3552     // Test where fails: Element is an array with 1 action
3553     try
3554     {
3555         const json element = R"(
3556             [
3557               { "i2c_compare_byte": { "register": "0xA0", "value": "0x00" } }
3558             ]
3559         )"_json;
3560         parseOr(element);
3561         ADD_FAILURE() << "Should not have reached this line.";
3562     }
3563     catch (const std::invalid_argument& e)
3564     {
3565         EXPECT_STREQ(e.what(), "Array must contain two or more actions");
3566     }
3567 
3568     // Test where fails: Element is not an array
3569     try
3570     {
3571         const json element = R"(
3572             {
3573               "foo": "bar"
3574             }
3575         )"_json;
3576         parseOr(element);
3577         ADD_FAILURE() << "Should not have reached this line.";
3578     }
3579     catch (const std::invalid_argument& e)
3580     {
3581         EXPECT_STREQ(e.what(), "Element is not an array");
3582     }
3583 }
3584 
3585 TEST(ConfigFileParserTests, ParsePhaseFaultDetection)
3586 {
3587     // Test where works: actions specified: optional properties not specified
3588     {
3589         const json element = R"(
3590             {
3591               "actions": [
3592                 { "run_rule": "detect_phase_fault_rule" }
3593               ]
3594             }
3595         )"_json;
3596         std::unique_ptr<PhaseFaultDetection> phaseFaultDetection =
3597             parsePhaseFaultDetection(element);
3598         EXPECT_EQ(phaseFaultDetection->getActions().size(), 1);
3599         EXPECT_EQ(phaseFaultDetection->getDeviceID(), "");
3600     }
3601 
3602     // Test where works: rule_id specified: optional properties specified
3603     {
3604         const json element = R"(
3605             {
3606               "comments": [ "Detect phase fault using I/O expander" ],
3607               "device_id": "io_expander",
3608               "rule_id": "detect_phase_fault_rule"
3609             }
3610         )"_json;
3611         std::unique_ptr<PhaseFaultDetection> phaseFaultDetection =
3612             parsePhaseFaultDetection(element);
3613         EXPECT_EQ(phaseFaultDetection->getActions().size(), 1);
3614         EXPECT_EQ(phaseFaultDetection->getDeviceID(), "io_expander");
3615     }
3616 
3617     // Test where fails: Element is not an object
3618     try
3619     {
3620         const json element = R"( [ "foo", "bar" ] )"_json;
3621         parsePhaseFaultDetection(element);
3622         ADD_FAILURE() << "Should not have reached this line.";
3623     }
3624     catch (const std::invalid_argument& e)
3625     {
3626         EXPECT_STREQ(e.what(), "Element is not an object");
3627     }
3628 
3629     // Test where fails: device_id value is invalid
3630     try
3631     {
3632         const json element = R"(
3633             {
3634               "device_id": 1,
3635               "rule_id": "detect_phase_fault_rule"
3636             }
3637         )"_json;
3638         parsePhaseFaultDetection(element);
3639         ADD_FAILURE() << "Should not have reached this line.";
3640     }
3641     catch (const std::invalid_argument& e)
3642     {
3643         EXPECT_STREQ(e.what(), "Element is not a string");
3644     }
3645 
3646     // Test where fails: rule_id value is invalid
3647     try
3648     {
3649         const json element = R"(
3650             {
3651               "rule_id": 1
3652             }
3653         )"_json;
3654         parsePhaseFaultDetection(element);
3655         ADD_FAILURE() << "Should not have reached this line.";
3656     }
3657     catch (const std::invalid_argument& e)
3658     {
3659         EXPECT_STREQ(e.what(), "Element is not a string");
3660     }
3661 
3662     // Test where fails: actions object is invalid
3663     try
3664     {
3665         const json element = R"(
3666             {
3667               "actions": 1
3668             }
3669         )"_json;
3670         parsePhaseFaultDetection(element);
3671         ADD_FAILURE() << "Should not have reached this line.";
3672     }
3673     catch (const std::invalid_argument& e)
3674     {
3675         EXPECT_STREQ(e.what(), "Element is not an array");
3676     }
3677 
3678     // Test where fails: Required actions or rule_id property not specified
3679     try
3680     {
3681         const json element = R"(
3682             {
3683               "device_id": "io_expander"
3684             }
3685         )"_json;
3686         parsePhaseFaultDetection(element);
3687         ADD_FAILURE() << "Should not have reached this line.";
3688     }
3689     catch (const std::invalid_argument& e)
3690     {
3691         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
3692                                "either rule_id or actions");
3693     }
3694 
3695     // Test where fails: Required actions or rule_id property both specified
3696     try
3697     {
3698         const json element = R"(
3699             {
3700               "rule_id": "detect_phase_fault_rule",
3701               "actions": [
3702                 { "run_rule": "detect_phase_fault_rule" }
3703               ]
3704             }
3705         )"_json;
3706         parsePhaseFaultDetection(element);
3707         ADD_FAILURE() << "Should not have reached this line.";
3708     }
3709     catch (const std::invalid_argument& e)
3710     {
3711         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
3712                                "either rule_id or actions");
3713     }
3714 
3715     // Test where fails: Invalid property specified
3716     try
3717     {
3718         const json element = R"(
3719             {
3720               "foo": "bar",
3721               "actions": [
3722                 { "run_rule": "detect_phase_fault_rule" }
3723               ]
3724             }
3725         )"_json;
3726         parsePhaseFaultDetection(element);
3727         ADD_FAILURE() << "Should not have reached this line.";
3728     }
3729     catch (const std::invalid_argument& e)
3730     {
3731         EXPECT_STREQ(e.what(), "Element contains an invalid property");
3732     }
3733 }
3734 
3735 TEST(ConfigFileParserTests, ParsePhaseFaultType)
3736 {
3737     // Test where works: n
3738     {
3739         const json element = "n";
3740         PhaseFaultType type = parsePhaseFaultType(element);
3741         EXPECT_EQ(type, PhaseFaultType::n);
3742     }
3743 
3744     // Test where works: n+1
3745     {
3746         const json element = "n+1";
3747         PhaseFaultType type = parsePhaseFaultType(element);
3748         EXPECT_EQ(type, PhaseFaultType::n_plus_1);
3749     }
3750 
3751     // Test where fails: Element is not a phase fault type
3752     try
3753     {
3754         const json element = "n+2";
3755         parsePhaseFaultType(element);
3756         ADD_FAILURE() << "Should not have reached this line.";
3757     }
3758     catch (const std::invalid_argument& e)
3759     {
3760         EXPECT_STREQ(e.what(), "Element is not a phase fault type");
3761     }
3762 
3763     // Test where fails: Element is not a string
3764     try
3765     {
3766         const json element = R"( { "foo": "bar" } )"_json;
3767         parsePhaseFaultType(element);
3768         ADD_FAILURE() << "Should not have reached this line.";
3769     }
3770     catch (const std::invalid_argument& e)
3771     {
3772         EXPECT_STREQ(e.what(), "Element is not a string");
3773     }
3774 }
3775 
3776 TEST(ConfigFileParserTests, ParsePMBusReadSensor)
3777 {
3778     // Test where works: Only required properties specified
3779     {
3780         const json element = R"(
3781             {
3782               "type": "iout",
3783               "command": "0x8C",
3784               "format": "linear_11"
3785             }
3786         )"_json;
3787         std::unique_ptr<PMBusReadSensorAction> action =
3788             parsePMBusReadSensor(element);
3789         EXPECT_EQ(action->getType(), SensorType::iout);
3790         EXPECT_EQ(action->getCommand(), 0x8C);
3791         EXPECT_EQ(action->getFormat(),
3792                   pmbus_utils::SensorDataFormat::linear_11);
3793         EXPECT_EQ(action->getExponent().has_value(), false);
3794     }
3795 
3796     // Test where works: All properties specified
3797     {
3798         const json element = R"(
3799             {
3800               "type": "temperature",
3801               "command": "0x7A",
3802               "format": "linear_16",
3803               "exponent": -8
3804             }
3805         )"_json;
3806         std::unique_ptr<PMBusReadSensorAction> action =
3807             parsePMBusReadSensor(element);
3808         EXPECT_EQ(action->getType(), SensorType::temperature);
3809         EXPECT_EQ(action->getCommand(), 0x7A);
3810         EXPECT_EQ(action->getFormat(),
3811                   pmbus_utils::SensorDataFormat::linear_16);
3812         EXPECT_EQ(action->getExponent().has_value(), true);
3813         EXPECT_EQ(action->getExponent().value(), -8);
3814     }
3815 
3816     // Test where fails: Element is not an object
3817     try
3818     {
3819         const json element = R"( [ "0xFF", "0x01" ] )"_json;
3820         parsePMBusReadSensor(element);
3821         ADD_FAILURE() << "Should not have reached this line.";
3822     }
3823     catch (const std::invalid_argument& e)
3824     {
3825         EXPECT_STREQ(e.what(), "Element is not an object");
3826     }
3827 
3828     // Test where fails: Invalid property specified
3829     try
3830     {
3831         const json element = R"(
3832             {
3833               "type": "iout",
3834               "command": "0x8C",
3835               "format": "linear_11",
3836               "foo": 1
3837             }
3838         )"_json;
3839         parsePMBusReadSensor(element);
3840         ADD_FAILURE() << "Should not have reached this line.";
3841     }
3842     catch (const std::invalid_argument& e)
3843     {
3844         EXPECT_STREQ(e.what(), "Element contains an invalid property");
3845     }
3846 
3847     // Test where fails: Required type property not specified
3848     try
3849     {
3850         const json element = R"(
3851             {
3852               "command": "0x8C",
3853               "format": "linear_11"
3854             }
3855         )"_json;
3856         parsePMBusReadSensor(element);
3857         ADD_FAILURE() << "Should not have reached this line.";
3858     }
3859     catch (const std::invalid_argument& e)
3860     {
3861         EXPECT_STREQ(e.what(), "Required property missing: type");
3862     }
3863 
3864     // Test where fails: Required command property not specified
3865     try
3866     {
3867         const json element = R"(
3868             {
3869               "type": "iout",
3870               "format": "linear_11"
3871             }
3872         )"_json;
3873         parsePMBusReadSensor(element);
3874         ADD_FAILURE() << "Should not have reached this line.";
3875     }
3876     catch (const std::invalid_argument& e)
3877     {
3878         EXPECT_STREQ(e.what(), "Required property missing: command");
3879     }
3880 
3881     // Test where fails: Required format property not specified
3882     try
3883     {
3884         const json element = R"(
3885             {
3886               "type": "iout",
3887               "command": "0x8C"
3888             }
3889         )"_json;
3890         parsePMBusReadSensor(element);
3891         ADD_FAILURE() << "Should not have reached this line.";
3892     }
3893     catch (const std::invalid_argument& e)
3894     {
3895         EXPECT_STREQ(e.what(), "Required property missing: format");
3896     }
3897 
3898     // Test where fails: type value is invalid
3899     try
3900     {
3901         const json element = R"(
3902             {
3903               "type": 1,
3904               "command": "0x7A",
3905               "format": "linear_16"
3906             }
3907         )"_json;
3908         parsePMBusReadSensor(element);
3909         ADD_FAILURE() << "Should not have reached this line.";
3910     }
3911     catch (const std::invalid_argument& e)
3912     {
3913         EXPECT_STREQ(e.what(), "Element is not a string");
3914     }
3915 
3916     // Test where fails: command value is invalid
3917     try
3918     {
3919         const json element = R"(
3920             {
3921               "type": "temperature",
3922               "command": 0,
3923               "format": "linear_16"
3924             }
3925         )"_json;
3926         parsePMBusReadSensor(element);
3927         ADD_FAILURE() << "Should not have reached this line.";
3928     }
3929     catch (const std::invalid_argument& e)
3930     {
3931         EXPECT_STREQ(e.what(), "Element is not a string");
3932     }
3933 
3934     // Test where fails: format value is invalid
3935     try
3936     {
3937         const json element = R"(
3938             {
3939               "type": "temperature",
3940               "command": "0x7A",
3941               "format": 1
3942             }
3943         )"_json;
3944         parsePMBusReadSensor(element);
3945         ADD_FAILURE() << "Should not have reached this line.";
3946     }
3947     catch (const std::invalid_argument& e)
3948     {
3949         EXPECT_STREQ(e.what(), "Element is not a string");
3950     }
3951 
3952     // Test where fails: exponent value is invalid
3953     try
3954     {
3955         const json element = R"(
3956             {
3957               "type": "temperature",
3958               "command": "0x7A",
3959               "format": "linear_16",
3960               "exponent": 1.3
3961             }
3962         )"_json;
3963         parsePMBusReadSensor(element);
3964         ADD_FAILURE() << "Should not have reached this line.";
3965     }
3966     catch (const std::invalid_argument& e)
3967     {
3968         EXPECT_STREQ(e.what(), "Element is not an integer");
3969     }
3970 }
3971 
3972 TEST(ConfigFileParserTests, ParsePMBusWriteVoutCommand)
3973 {
3974     // Test where works: Only required properties specified
3975     {
3976         const json element = R"(
3977             {
3978               "format": "linear"
3979             }
3980         )"_json;
3981         std::unique_ptr<PMBusWriteVoutCommandAction> action =
3982             parsePMBusWriteVoutCommand(element);
3983         EXPECT_EQ(action->getVolts().has_value(), false);
3984         EXPECT_EQ(action->getFormat(), pmbus_utils::VoutDataFormat::linear);
3985         EXPECT_EQ(action->getExponent().has_value(), false);
3986         EXPECT_EQ(action->isVerified(), false);
3987     }
3988 
3989     // Test where works: All properties specified
3990     {
3991         const json element = R"(
3992             {
3993               "volts": 1.03,
3994               "format": "linear",
3995               "exponent": -8,
3996               "is_verified": true
3997             }
3998         )"_json;
3999         std::unique_ptr<PMBusWriteVoutCommandAction> action =
4000             parsePMBusWriteVoutCommand(element);
4001         EXPECT_EQ(action->getVolts().has_value(), true);
4002         EXPECT_EQ(action->getVolts().value(), 1.03);
4003         EXPECT_EQ(action->getFormat(), pmbus_utils::VoutDataFormat::linear);
4004         EXPECT_EQ(action->getExponent().has_value(), true);
4005         EXPECT_EQ(action->getExponent().value(), -8);
4006         EXPECT_EQ(action->isVerified(), true);
4007     }
4008 
4009     // Test where fails: Element is not an object
4010     try
4011     {
4012         const json element = R"( [ "0xFF", "0x01" ] )"_json;
4013         parsePMBusWriteVoutCommand(element);
4014         ADD_FAILURE() << "Should not have reached this line.";
4015     }
4016     catch (const std::invalid_argument& e)
4017     {
4018         EXPECT_STREQ(e.what(), "Element is not an object");
4019     }
4020 
4021     // Test where fails: volts value is invalid
4022     try
4023     {
4024         const json element = R"(
4025             {
4026               "volts": "foo",
4027               "format": "linear"
4028             }
4029         )"_json;
4030         parsePMBusWriteVoutCommand(element);
4031         ADD_FAILURE() << "Should not have reached this line.";
4032     }
4033     catch (const std::invalid_argument& e)
4034     {
4035         EXPECT_STREQ(e.what(), "Element is not a number");
4036     }
4037 
4038     // Test where fails: Required format property not specified
4039     try
4040     {
4041         const json element = R"(
4042             {
4043               "volts": 1.03,
4044               "is_verified": true
4045             }
4046         )"_json;
4047         parsePMBusWriteVoutCommand(element);
4048         ADD_FAILURE() << "Should not have reached this line.";
4049     }
4050     catch (const std::invalid_argument& e)
4051     {
4052         EXPECT_STREQ(e.what(), "Required property missing: format");
4053     }
4054 
4055     // Test where fails: format value is invalid
4056     try
4057     {
4058         const json element = R"(
4059             {
4060               "format": "linear_11"
4061             }
4062         )"_json;
4063         parsePMBusWriteVoutCommand(element);
4064         ADD_FAILURE() << "Should not have reached this line.";
4065     }
4066     catch (const std::invalid_argument& e)
4067     {
4068         EXPECT_STREQ(e.what(), "Invalid format value: linear_11");
4069     }
4070 
4071     // Test where fails: exponent value is invalid
4072     try
4073     {
4074         const json element = R"(
4075             {
4076               "format": "linear",
4077               "exponent": 1.3
4078             }
4079         )"_json;
4080         parsePMBusWriteVoutCommand(element);
4081         ADD_FAILURE() << "Should not have reached this line.";
4082     }
4083     catch (const std::invalid_argument& e)
4084     {
4085         EXPECT_STREQ(e.what(), "Element is not an integer");
4086     }
4087 
4088     // Test where fails: is_verified value is invalid
4089     try
4090     {
4091         const json element = R"(
4092             {
4093               "format": "linear",
4094               "is_verified": "true"
4095             }
4096         )"_json;
4097         parsePMBusWriteVoutCommand(element);
4098         ADD_FAILURE() << "Should not have reached this line.";
4099     }
4100     catch (const std::invalid_argument& e)
4101     {
4102         EXPECT_STREQ(e.what(), "Element is not a boolean");
4103     }
4104 
4105     // Test where fails: Invalid property specified
4106     try
4107     {
4108         const json element = R"(
4109             {
4110               "format": "linear",
4111               "foo": "bar"
4112             }
4113         )"_json;
4114         parsePMBusWriteVoutCommand(element);
4115         ADD_FAILURE() << "Should not have reached this line.";
4116     }
4117     catch (const std::invalid_argument& e)
4118     {
4119         EXPECT_STREQ(e.what(), "Element contains an invalid property");
4120     }
4121 }
4122 
4123 TEST(ConfigFileParserTests, ParsePresenceDetection)
4124 {
4125     // Test where works: actions property specified
4126     {
4127         const json element = R"(
4128             {
4129               "actions": [
4130                 { "run_rule": "read_sensors_rule" }
4131               ]
4132             }
4133         )"_json;
4134         std::unique_ptr<PresenceDetection> presenceDetection =
4135             parsePresenceDetection(element);
4136         EXPECT_EQ(presenceDetection->getActions().size(), 1);
4137     }
4138 
4139     // Test where works: rule_id property specified
4140     {
4141         const json element = R"(
4142             {
4143               "comments": [ "comments property" ],
4144               "rule_id": "set_voltage_rule"
4145             }
4146         )"_json;
4147         std::unique_ptr<PresenceDetection> presenceDetection =
4148             parsePresenceDetection(element);
4149         EXPECT_EQ(presenceDetection->getActions().size(), 1);
4150     }
4151 
4152     // Test where fails: actions object is invalid
4153     try
4154     {
4155         const json element = R"(
4156             {
4157               "actions": 1
4158             }
4159         )"_json;
4160         parsePresenceDetection(element);
4161         ADD_FAILURE() << "Should not have reached this line.";
4162     }
4163     catch (const std::invalid_argument& e)
4164     {
4165         EXPECT_STREQ(e.what(), "Element is not an array");
4166     }
4167 
4168     // Test where fails: rule_id value is invalid
4169     try
4170     {
4171         const json element = R"(
4172             {
4173               "rule_id": 1
4174             }
4175         )"_json;
4176         parsePresenceDetection(element);
4177         ADD_FAILURE() << "Should not have reached this line.";
4178     }
4179     catch (const std::invalid_argument& e)
4180     {
4181         EXPECT_STREQ(e.what(), "Element is not a string");
4182     }
4183 
4184     // Test where fails: Required actions or rule_id property not specified
4185     try
4186     {
4187         const json element = R"(
4188             {
4189               "comments": [ "comments property" ]
4190             }
4191         )"_json;
4192         parsePresenceDetection(element);
4193         ADD_FAILURE() << "Should not have reached this line.";
4194     }
4195     catch (const std::invalid_argument& e)
4196     {
4197         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
4198                                "either rule_id or actions");
4199     }
4200 
4201     // Test where fails: Required actions or rule_id property both specified
4202     try
4203     {
4204         const json element = R"(
4205             {
4206               "rule_id": "set_voltage_rule",
4207               "actions": [
4208                 { "run_rule": "read_sensors_rule" }
4209               ]
4210             }
4211         )"_json;
4212         parsePresenceDetection(element);
4213         ADD_FAILURE() << "Should not have reached this line.";
4214     }
4215     catch (const std::invalid_argument& e)
4216     {
4217         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
4218                                "either rule_id or actions");
4219     }
4220 
4221     // Test where fails: Element is not an object
4222     try
4223     {
4224         const json element = R"( [ "foo", "bar" ] )"_json;
4225         parsePresenceDetection(element);
4226         ADD_FAILURE() << "Should not have reached this line.";
4227     }
4228     catch (const std::invalid_argument& e)
4229     {
4230         EXPECT_STREQ(e.what(), "Element is not an object");
4231     }
4232 
4233     // Test where fails: Invalid property specified
4234     try
4235     {
4236         const json element = R"(
4237             {
4238               "foo": "bar",
4239               "actions": [
4240                 { "run_rule": "read_sensors_rule" }
4241               ]
4242             }
4243         )"_json;
4244         parsePresenceDetection(element);
4245         ADD_FAILURE() << "Should not have reached this line.";
4246     }
4247     catch (const std::invalid_argument& e)
4248     {
4249         EXPECT_STREQ(e.what(), "Element contains an invalid property");
4250     }
4251 }
4252 
4253 TEST(ConfigFileParserTests, ParseRail)
4254 {
4255     // Test where works: Only required properties specified
4256     {
4257         const json element = R"(
4258             {
4259               "id": "vdd"
4260             }
4261         )"_json;
4262         std::unique_ptr<Rail> rail = parseRail(element);
4263         EXPECT_EQ(rail->getID(), "vdd");
4264         EXPECT_EQ(rail->getConfiguration(), nullptr);
4265         EXPECT_EQ(rail->getSensorMonitoring(), nullptr);
4266     }
4267 
4268     // Test where works: All properties specified
4269     {
4270         const json element = R"(
4271             {
4272               "comments": [ "comments property" ],
4273               "id": "vdd",
4274               "configuration": {
4275                 "volts": 1.1,
4276                 "actions": [
4277                   {
4278                     "pmbus_write_vout_command": {
4279                       "format": "linear"
4280                     }
4281                   }
4282                 ]
4283               },
4284               "sensor_monitoring": {
4285                 "actions": [
4286                   { "run_rule": "read_sensors_rule" }
4287                 ]
4288               }
4289             }
4290         )"_json;
4291         std::unique_ptr<Rail> rail = parseRail(element);
4292         EXPECT_EQ(rail->getID(), "vdd");
4293         EXPECT_NE(rail->getConfiguration(), nullptr);
4294         EXPECT_NE(rail->getSensorMonitoring(), nullptr);
4295     }
4296 
4297     // Test where fails: id property not specified
4298     try
4299     {
4300         const json element = R"(
4301             {
4302               "configuration": {
4303                 "volts": 1.1,
4304                 "actions": [
4305                   {
4306                     "pmbus_write_vout_command": {
4307                       "format": "linear"
4308                     }
4309                   }
4310                 ]
4311               }
4312             }
4313         )"_json;
4314         parseRail(element);
4315         ADD_FAILURE() << "Should not have reached this line.";
4316     }
4317     catch (const std::invalid_argument& e)
4318     {
4319         EXPECT_STREQ(e.what(), "Required property missing: id");
4320     }
4321 
4322     // Test where fails: id property is invalid
4323     try
4324     {
4325         const json element = R"(
4326             {
4327               "id": "",
4328               "configuration": {
4329                 "volts": 1.1,
4330                 "actions": [
4331                   {
4332                     "pmbus_write_vout_command": {
4333                       "format": "linear"
4334                     }
4335                   }
4336                 ]
4337               }
4338             }
4339         )"_json;
4340         parseRail(element);
4341         ADD_FAILURE() << "Should not have reached this line.";
4342     }
4343     catch (const std::invalid_argument& e)
4344     {
4345         EXPECT_STREQ(e.what(), "Element contains an empty string");
4346     }
4347 
4348     // Test where fails: Element is not an object
4349     try
4350     {
4351         const json element = R"( [ "0xFF", "0x01" ] )"_json;
4352         parseRail(element);
4353         ADD_FAILURE() << "Should not have reached this line.";
4354     }
4355     catch (const std::invalid_argument& e)
4356     {
4357         EXPECT_STREQ(e.what(), "Element is not an object");
4358     }
4359 
4360     // Test where fails: configuration value is invalid
4361     try
4362     {
4363         const json element = R"(
4364             {
4365               "id": "vdd",
4366               "configuration": "config"
4367             }
4368         )"_json;
4369         parseRail(element);
4370         ADD_FAILURE() << "Should not have reached this line.";
4371     }
4372     catch (const std::invalid_argument& e)
4373     {
4374         EXPECT_STREQ(e.what(), "Element is not an object");
4375     }
4376 
4377     // Test where fails: sensor_monitoring value is invalid
4378     try
4379     {
4380         const json element = R"(
4381             {
4382               "comments": [ "comments property" ],
4383               "id": "vdd",
4384               "configuration": {
4385                 "volts": 1.1,
4386                 "actions": [
4387                   {
4388                     "pmbus_write_vout_command": {
4389                       "format": "linear"
4390                     }
4391                   }
4392                 ]
4393               },
4394               "sensor_monitoring": 1
4395             }
4396         )"_json;
4397         parseRail(element);
4398         ADD_FAILURE() << "Should not have reached this line.";
4399     }
4400     catch (const std::invalid_argument& e)
4401     {
4402         EXPECT_STREQ(e.what(), "Element is not an object");
4403     }
4404 
4405     // Test where fails: Invalid property specified
4406     try
4407     {
4408         const json element = R"(
4409             {
4410               "id": "vdd",
4411               "foo" : true
4412             }
4413         )"_json;
4414         parseRail(element);
4415         ADD_FAILURE() << "Should not have reached this line.";
4416     }
4417     catch (const std::invalid_argument& e)
4418     {
4419         EXPECT_STREQ(e.what(), "Element contains an invalid property");
4420     }
4421 }
4422 
4423 TEST(ConfigFileParserTests, ParseRailArray)
4424 {
4425     // Test where works
4426     {
4427         const json element = R"(
4428             [
4429               { "id": "vdd" },
4430               { "id": "vio" }
4431             ]
4432         )"_json;
4433         std::vector<std::unique_ptr<Rail>> rails = parseRailArray(element);
4434         EXPECT_EQ(rails.size(), 2);
4435         EXPECT_EQ(rails[0]->getID(), "vdd");
4436         EXPECT_EQ(rails[1]->getID(), "vio");
4437     }
4438 
4439     // Test where fails: Element is not an array
4440     try
4441     {
4442         const json element = R"(
4443             {
4444               "foo": "bar"
4445             }
4446         )"_json;
4447         parseRailArray(element);
4448         ADD_FAILURE() << "Should not have reached this line.";
4449     }
4450     catch (const std::invalid_argument& e)
4451     {
4452         EXPECT_STREQ(e.what(), "Element is not an array");
4453     }
4454 }
4455 
4456 TEST(ConfigFileParserTests, ParseRoot)
4457 {
4458     // Test where works: Only required properties specified
4459     {
4460         const json element = R"(
4461             {
4462               "chassis": [
4463                 { "number": 1, "inventory_path": "system/chassis" }
4464               ]
4465             }
4466         )"_json;
4467         std::vector<std::unique_ptr<Rule>> rules{};
4468         std::vector<std::unique_ptr<Chassis>> chassis{};
4469         std::tie(rules, chassis) = parseRoot(element);
4470         EXPECT_EQ(rules.size(), 0);
4471         EXPECT_EQ(chassis.size(), 1);
4472     }
4473 
4474     // Test where works: All properties specified
4475     {
4476         const json element = R"(
4477             {
4478               "comments": [ "Config file for a FooBar one-chassis system" ],
4479               "rules": [
4480                 {
4481                   "id": "set_voltage_rule",
4482                   "actions": [
4483                     { "pmbus_write_vout_command": { "format": "linear" } }
4484                   ]
4485                 }
4486               ],
4487               "chassis": [
4488                 { "number": 1, "inventory_path": "system/chassis1" },
4489                 { "number": 3, "inventory_path": "system/chassis3" }
4490               ]
4491             }
4492         )"_json;
4493         std::vector<std::unique_ptr<Rule>> rules{};
4494         std::vector<std::unique_ptr<Chassis>> chassis{};
4495         std::tie(rules, chassis) = parseRoot(element);
4496         EXPECT_EQ(rules.size(), 1);
4497         EXPECT_EQ(chassis.size(), 2);
4498     }
4499 
4500     // Test where fails: Element is not an object
4501     try
4502     {
4503         const json element = R"( [ "0xFF", "0x01" ] )"_json;
4504         parseRoot(element);
4505         ADD_FAILURE() << "Should not have reached this line.";
4506     }
4507     catch (const std::invalid_argument& e)
4508     {
4509         EXPECT_STREQ(e.what(), "Element is not an object");
4510     }
4511 
4512     // Test where fails: chassis property not specified
4513     try
4514     {
4515         const json element = R"(
4516             {
4517               "rules": [
4518                 {
4519                   "id": "set_voltage_rule",
4520                   "actions": [
4521                     { "pmbus_write_vout_command": { "format": "linear" } }
4522                   ]
4523                 }
4524               ]
4525             }
4526         )"_json;
4527         parseRoot(element);
4528         ADD_FAILURE() << "Should not have reached this line.";
4529     }
4530     catch (const std::invalid_argument& e)
4531     {
4532         EXPECT_STREQ(e.what(), "Required property missing: chassis");
4533     }
4534 
4535     // Test where fails: Invalid property specified
4536     try
4537     {
4538         const json element = R"(
4539             {
4540               "remarks": [ "Config file for a FooBar one-chassis system" ],
4541               "chassis": [
4542                 { "number": 1, "inventory_path": "system/chassis" }
4543               ]
4544             }
4545         )"_json;
4546         parseRoot(element);
4547         ADD_FAILURE() << "Should not have reached this line.";
4548     }
4549     catch (const std::invalid_argument& e)
4550     {
4551         EXPECT_STREQ(e.what(), "Element contains an invalid property");
4552     }
4553 }
4554 
4555 TEST(ConfigFileParserTests, ParseRule)
4556 {
4557     // Test where works: comments property specified
4558     {
4559         const json element = R"(
4560             {
4561               "comments": [ "Set voltage rule" ],
4562               "id": "set_voltage_rule",
4563               "actions": [
4564                 { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } },
4565                 { "pmbus_write_vout_command": { "volts": 1.03, "format": "linear" } }
4566               ]
4567             }
4568         )"_json;
4569         std::unique_ptr<Rule> rule = parseRule(element);
4570         EXPECT_EQ(rule->getID(), "set_voltage_rule");
4571         EXPECT_EQ(rule->getActions().size(), 2);
4572     }
4573 
4574     // Test where works: comments property not specified
4575     {
4576         const json element = R"(
4577             {
4578               "id": "set_voltage_rule",
4579               "actions": [
4580                 { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } },
4581                 { "pmbus_write_vout_command": { "volts": 1.03, "format": "linear" } },
4582                 { "pmbus_write_vout_command": { "volts": 1.05, "format": "linear" } }
4583               ]
4584             }
4585         )"_json;
4586         std::unique_ptr<Rule> rule = parseRule(element);
4587         EXPECT_EQ(rule->getID(), "set_voltage_rule");
4588         EXPECT_EQ(rule->getActions().size(), 3);
4589     }
4590 
4591     // Test where fails: Element is not an object
4592     try
4593     {
4594         const json element = R"( [ "0xFF", "0x01" ] )"_json;
4595         parseRule(element);
4596         ADD_FAILURE() << "Should not have reached this line.";
4597     }
4598     catch (const std::invalid_argument& e)
4599     {
4600         EXPECT_STREQ(e.what(), "Element is not an object");
4601     }
4602 
4603     // Test where fails: id property not specified
4604     try
4605     {
4606         const json element = R"(
4607             {
4608               "actions": [
4609                 { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
4610               ]
4611             }
4612         )"_json;
4613         parseRule(element);
4614         ADD_FAILURE() << "Should not have reached this line.";
4615     }
4616     catch (const std::invalid_argument& e)
4617     {
4618         EXPECT_STREQ(e.what(), "Required property missing: id");
4619     }
4620 
4621     // Test where fails: id property is invalid
4622     try
4623     {
4624         const json element = R"(
4625             {
4626               "id": "",
4627               "actions": [
4628                 { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
4629               ]
4630             }
4631         )"_json;
4632         parseRule(element);
4633         ADD_FAILURE() << "Should not have reached this line.";
4634     }
4635     catch (const std::invalid_argument& e)
4636     {
4637         EXPECT_STREQ(e.what(), "Element contains an empty string");
4638     }
4639 
4640     // Test where fails: actions property not specified
4641     try
4642     {
4643         const json element = R"(
4644             {
4645               "comments": [ "Set voltage rule" ],
4646               "id": "set_voltage_rule"
4647             }
4648         )"_json;
4649         parseRule(element);
4650         ADD_FAILURE() << "Should not have reached this line.";
4651     }
4652     catch (const std::invalid_argument& e)
4653     {
4654         EXPECT_STREQ(e.what(), "Required property missing: actions");
4655     }
4656 
4657     // Test where fails: actions property is invalid
4658     try
4659     {
4660         const json element = R"(
4661             {
4662               "id": "set_voltage_rule",
4663               "actions": true
4664             }
4665         )"_json;
4666         parseRule(element);
4667         ADD_FAILURE() << "Should not have reached this line.";
4668     }
4669     catch (const std::invalid_argument& e)
4670     {
4671         EXPECT_STREQ(e.what(), "Element is not an array");
4672     }
4673 
4674     // Test where fails: Invalid property specified
4675     try
4676     {
4677         const json element = R"(
4678             {
4679               "remarks": [ "Set voltage rule" ],
4680               "id": "set_voltage_rule",
4681               "actions": [
4682                 { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
4683               ]
4684             }
4685         )"_json;
4686         parseRule(element);
4687         ADD_FAILURE() << "Should not have reached this line.";
4688     }
4689     catch (const std::invalid_argument& e)
4690     {
4691         EXPECT_STREQ(e.what(), "Element contains an invalid property");
4692     }
4693 }
4694 
4695 TEST(ConfigFileParserTests, ParseRuleArray)
4696 {
4697     // Test where works
4698     {
4699         const json element = R"(
4700             [
4701               {
4702                 "id": "set_voltage_rule1",
4703                 "actions": [
4704                   { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
4705                 ]
4706               },
4707               {
4708                 "id": "set_voltage_rule2",
4709                 "actions": [
4710                   { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } },
4711                   { "pmbus_write_vout_command": { "volts": 1.11, "format": "linear" } }
4712                 ]
4713               }
4714             ]
4715         )"_json;
4716         std::vector<std::unique_ptr<Rule>> rules = parseRuleArray(element);
4717         EXPECT_EQ(rules.size(), 2);
4718         EXPECT_EQ(rules[0]->getID(), "set_voltage_rule1");
4719         EXPECT_EQ(rules[0]->getActions().size(), 1);
4720         EXPECT_EQ(rules[1]->getID(), "set_voltage_rule2");
4721         EXPECT_EQ(rules[1]->getActions().size(), 2);
4722     }
4723 
4724     // Test where fails: Element is not an array
4725     try
4726     {
4727         const json element = R"( { "id": "set_voltage_rule" } )"_json;
4728         parseRuleArray(element);
4729         ADD_FAILURE() << "Should not have reached this line.";
4730     }
4731     catch (const std::invalid_argument& e)
4732     {
4733         EXPECT_STREQ(e.what(), "Element is not an array");
4734     }
4735 }
4736 
4737 TEST(ConfigFileParserTests, ParseRuleIDOrActionsProperty)
4738 {
4739     // Test where works: actions specified
4740     {
4741         const json element = R"(
4742             {
4743               "actions": [
4744                 { "pmbus_write_vout_command": { "format": "linear" } },
4745                 { "run_rule": "set_voltage_rule" }
4746               ]
4747             }
4748         )"_json;
4749         std::vector<std::unique_ptr<Action>> actions =
4750             parseRuleIDOrActionsProperty(element);
4751         EXPECT_EQ(actions.size(), 2);
4752     }
4753 
4754     // Test where works: rule_id specified
4755     {
4756         const json element = R"(
4757             {
4758               "rule_id": "set_voltage_rule"
4759             }
4760         )"_json;
4761         std::vector<std::unique_ptr<Action>> actions =
4762             parseRuleIDOrActionsProperty(element);
4763         EXPECT_EQ(actions.size(), 1);
4764     }
4765 
4766     // Test where fails: Element is not an object
4767     try
4768     {
4769         const json element = R"( [ "foo", "bar" ] )"_json;
4770         parseRuleIDOrActionsProperty(element);
4771         ADD_FAILURE() << "Should not have reached this line.";
4772     }
4773     catch (const std::invalid_argument& e)
4774     {
4775         EXPECT_STREQ(e.what(), "Element is not an object");
4776     }
4777 
4778     // Test where fails: rule_id is invalid
4779     try
4780     {
4781         const json element = R"(
4782             { "rule_id": 1 }
4783         )"_json;
4784         parseRuleIDOrActionsProperty(element);
4785         ADD_FAILURE() << "Should not have reached this line.";
4786     }
4787     catch (const std::invalid_argument& e)
4788     {
4789         EXPECT_STREQ(e.what(), "Element is not a string");
4790     }
4791 
4792     // Test where fails: actions is invalid
4793     try
4794     {
4795         const json element = R"(
4796             { "actions": 1 }
4797         )"_json;
4798         parseRuleIDOrActionsProperty(element);
4799         ADD_FAILURE() << "Should not have reached this line.";
4800     }
4801     catch (const std::invalid_argument& e)
4802     {
4803         EXPECT_STREQ(e.what(), "Element is not an array");
4804     }
4805 
4806     // Test where fails: Neither rule_id nor actions specified
4807     try
4808     {
4809         const json element = R"(
4810             {
4811               "volts": 1.03
4812             }
4813         )"_json;
4814         parseRuleIDOrActionsProperty(element);
4815         ADD_FAILURE() << "Should not have reached this line.";
4816     }
4817     catch (const std::invalid_argument& e)
4818     {
4819         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
4820                                "either rule_id or actions");
4821     }
4822 
4823     // Test where fails: Both rule_id and actions specified
4824     try
4825     {
4826         const json element = R"(
4827             {
4828               "volts": 1.03,
4829               "rule_id": "set_voltage_rule",
4830               "actions": [
4831                 {
4832                   "pmbus_write_vout_command": {
4833                     "format": "linear"
4834                   }
4835                 }
4836               ]
4837             }
4838         )"_json;
4839         parseRuleIDOrActionsProperty(element);
4840         ADD_FAILURE() << "Should not have reached this line.";
4841     }
4842     catch (const std::invalid_argument& e)
4843     {
4844         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
4845                                "either rule_id or actions");
4846     }
4847 }
4848 
4849 TEST(ConfigFileParserTests, ParseRunRule)
4850 {
4851     // Test where works
4852     {
4853         const json element = "vdd_regulator";
4854         std::unique_ptr<RunRuleAction> action = parseRunRule(element);
4855         EXPECT_EQ(action->getRuleID(), "vdd_regulator");
4856     }
4857 
4858     // Test where fails: Element is not a string
4859     try
4860     {
4861         const json element = 1;
4862         parseRunRule(element);
4863         ADD_FAILURE() << "Should not have reached this line.";
4864     }
4865     catch (const std::invalid_argument& e)
4866     {
4867         EXPECT_STREQ(e.what(), "Element is not a string");
4868     }
4869 
4870     // Test where fails: Empty string
4871     try
4872     {
4873         const json element = "";
4874         parseRunRule(element);
4875         ADD_FAILURE() << "Should not have reached this line.";
4876     }
4877     catch (const std::invalid_argument& e)
4878     {
4879         EXPECT_STREQ(e.what(), "Element contains an empty string");
4880     }
4881 }
4882 
4883 TEST(ConfigFileParserTests, ParseSensorDataFormat)
4884 {
4885     // Test where works: linear_11
4886     {
4887         const json element = "linear_11";
4888         pmbus_utils::SensorDataFormat value = parseSensorDataFormat(element);
4889         pmbus_utils::SensorDataFormat format =
4890             pmbus_utils::SensorDataFormat::linear_11;
4891         EXPECT_EQ(value, format);
4892     }
4893 
4894     // Test where works: linear_16
4895     {
4896         const json element = "linear_16";
4897         pmbus_utils::SensorDataFormat value = parseSensorDataFormat(element);
4898         pmbus_utils::SensorDataFormat format =
4899             pmbus_utils::SensorDataFormat::linear_16;
4900         EXPECT_EQ(value, format);
4901     }
4902 
4903     // Test where fails: Element is not a sensor data format
4904     try
4905     {
4906         const json element = "foo";
4907         parseSensorDataFormat(element);
4908         ADD_FAILURE() << "Should not have reached this line.";
4909     }
4910     catch (const std::invalid_argument& e)
4911     {
4912         EXPECT_STREQ(e.what(), "Element is not a sensor data format");
4913     }
4914 
4915     // Test where fails: Element is not a string
4916     try
4917     {
4918         const json element = R"( { "foo": "bar" } )"_json;
4919         parseSensorDataFormat(element);
4920         ADD_FAILURE() << "Should not have reached this line.";
4921     }
4922     catch (const std::invalid_argument& e)
4923     {
4924         EXPECT_STREQ(e.what(), "Element is not a string");
4925     }
4926 }
4927 
4928 TEST(ConfigFileParserTests, ParseSensorMonitoring)
4929 {
4930     // Test where works: actions property specified
4931     {
4932         const json element = R"(
4933             {
4934               "actions": [
4935                 { "run_rule": "read_sensors_rule" }
4936               ]
4937             }
4938         )"_json;
4939         std::unique_ptr<SensorMonitoring> sensorMonitoring =
4940             parseSensorMonitoring(element);
4941         EXPECT_EQ(sensorMonitoring->getActions().size(), 1);
4942     }
4943 
4944     // Test where works: rule_id property specified
4945     {
4946         const json element = R"(
4947             {
4948               "comments": [ "comments property" ],
4949               "rule_id": "set_voltage_rule"
4950             }
4951         )"_json;
4952         std::unique_ptr<SensorMonitoring> sensorMonitoring =
4953             parseSensorMonitoring(element);
4954         EXPECT_EQ(sensorMonitoring->getActions().size(), 1);
4955     }
4956 
4957     // Test where fails: actions object is invalid
4958     try
4959     {
4960         const json element = R"(
4961             {
4962               "actions": 1
4963             }
4964         )"_json;
4965         parseSensorMonitoring(element);
4966         ADD_FAILURE() << "Should not have reached this line.";
4967     }
4968     catch (const std::invalid_argument& e)
4969     {
4970         EXPECT_STREQ(e.what(), "Element is not an array");
4971     }
4972 
4973     // Test where fails: rule_id value is invalid
4974     try
4975     {
4976         const json element = R"(
4977             {
4978               "rule_id": 1
4979             }
4980         )"_json;
4981         parseSensorMonitoring(element);
4982         ADD_FAILURE() << "Should not have reached this line.";
4983     }
4984     catch (const std::invalid_argument& e)
4985     {
4986         EXPECT_STREQ(e.what(), "Element is not a string");
4987     }
4988 
4989     // Test where fails: Required actions or rule_id property not specified
4990     try
4991     {
4992         const json element = R"(
4993             {
4994               "comments": [ "comments property" ]
4995             }
4996         )"_json;
4997         parseSensorMonitoring(element);
4998         ADD_FAILURE() << "Should not have reached this line.";
4999     }
5000     catch (const std::invalid_argument& e)
5001     {
5002         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
5003                                "either rule_id or actions");
5004     }
5005 
5006     // Test where fails: Required actions or rule_id property both specified
5007     try
5008     {
5009         const json element = R"(
5010             {
5011               "rule_id": "set_voltage_rule",
5012               "actions": [
5013                 { "run_rule": "read_sensors_rule" }
5014               ]
5015             }
5016         )"_json;
5017         parseSensorMonitoring(element);
5018         ADD_FAILURE() << "Should not have reached this line.";
5019     }
5020     catch (const std::invalid_argument& e)
5021     {
5022         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
5023                                "either rule_id or actions");
5024     }
5025 
5026     // Test where fails: Element is not an object
5027     try
5028     {
5029         const json element = R"( [ "foo", "bar" ] )"_json;
5030         parseSensorMonitoring(element);
5031         ADD_FAILURE() << "Should not have reached this line.";
5032     }
5033     catch (const std::invalid_argument& e)
5034     {
5035         EXPECT_STREQ(e.what(), "Element is not an object");
5036     }
5037 
5038     // Test where fails: Invalid property specified
5039     try
5040     {
5041         const json element = R"(
5042             {
5043               "foo": "bar",
5044               "actions": [
5045                 { "run_rule": "read_sensors_rule" }
5046               ]
5047             }
5048         )"_json;
5049         parseSensorMonitoring(element);
5050         ADD_FAILURE() << "Should not have reached this line.";
5051     }
5052     catch (const std::invalid_argument& e)
5053     {
5054         EXPECT_STREQ(e.what(), "Element contains an invalid property");
5055     }
5056 }
5057 
5058 TEST(ConfigFileParserTests, ParseSensorType)
5059 {
5060     // Test where works: iout
5061     {
5062         const json element = "iout";
5063         SensorType type = parseSensorType(element);
5064         EXPECT_EQ(type, SensorType::iout);
5065     }
5066 
5067     // Test where works: iout_peak
5068     {
5069         const json element = "iout_peak";
5070         SensorType type = parseSensorType(element);
5071         EXPECT_EQ(type, SensorType::iout_peak);
5072     }
5073 
5074     // Test where works: iout_valley
5075     {
5076         const json element = "iout_valley";
5077         SensorType type = parseSensorType(element);
5078         EXPECT_EQ(type, SensorType::iout_valley);
5079     }
5080 
5081     // Test where works: pout
5082     {
5083         const json element = "pout";
5084         SensorType type = parseSensorType(element);
5085         EXPECT_EQ(type, SensorType::pout);
5086     }
5087 
5088     // Test where works: temperature
5089     {
5090         const json element = "temperature";
5091         SensorType type = parseSensorType(element);
5092         EXPECT_EQ(type, SensorType::temperature);
5093     }
5094 
5095     // Test where works: temperature_peak
5096     {
5097         const json element = "temperature_peak";
5098         SensorType type = parseSensorType(element);
5099         EXPECT_EQ(type, SensorType::temperature_peak);
5100     }
5101 
5102     // Test where works: vout
5103     {
5104         const json element = "vout";
5105         SensorType type = parseSensorType(element);
5106         EXPECT_EQ(type, SensorType::vout);
5107     }
5108 
5109     // Test where works: vout_peak
5110     {
5111         const json element = "vout_peak";
5112         SensorType type = parseSensorType(element);
5113         EXPECT_EQ(type, SensorType::vout_peak);
5114     }
5115 
5116     // Test where works: vout_valley
5117     {
5118         const json element = "vout_valley";
5119         SensorType type = parseSensorType(element);
5120         EXPECT_EQ(type, SensorType::vout_valley);
5121     }
5122 
5123     // Test where fails: Element is not a sensor type
5124     try
5125     {
5126         const json element = "foo";
5127         parseSensorType(element);
5128         ADD_FAILURE() << "Should not have reached this line.";
5129     }
5130     catch (const std::invalid_argument& e)
5131     {
5132         EXPECT_STREQ(e.what(), "Element is not a sensor type");
5133     }
5134 
5135     // Test where fails: Element is not a string
5136     try
5137     {
5138         const json element = R"( { "foo": "bar" } )"_json;
5139         parseSensorType(element);
5140         ADD_FAILURE() << "Should not have reached this line.";
5141     }
5142     catch (const std::invalid_argument& e)
5143     {
5144         EXPECT_STREQ(e.what(), "Element is not a string");
5145     }
5146 }
5147 
5148 TEST(ConfigFileParserTests, ParseSetDevice)
5149 {
5150     // Test where works
5151     {
5152         const json element = "regulator1";
5153         std::unique_ptr<SetDeviceAction> action = parseSetDevice(element);
5154         EXPECT_EQ(action->getDeviceID(), "regulator1");
5155     }
5156 
5157     // Test where fails: Element is not a string
5158     try
5159     {
5160         const json element = 1;
5161         parseSetDevice(element);
5162         ADD_FAILURE() << "Should not have reached this line.";
5163     }
5164     catch (const std::invalid_argument& e)
5165     {
5166         EXPECT_STREQ(e.what(), "Element is not a string");
5167     }
5168 
5169     // Test where fails: Empty string
5170     try
5171     {
5172         const json element = "";
5173         parseSetDevice(element);
5174         ADD_FAILURE() << "Should not have reached this line.";
5175     }
5176     catch (const std::invalid_argument& e)
5177     {
5178         EXPECT_STREQ(e.what(), "Element contains an empty string");
5179     }
5180 }
5181 
5182 TEST(ConfigFileParserTests, ParseString)
5183 {
5184     // Test where works: Empty string
5185     {
5186         const json element = "";
5187         std::string value = parseString(element, true);
5188         EXPECT_EQ(value, "");
5189     }
5190 
5191     // Test where works: Non-empty string
5192     {
5193         const json element = "vdd_regulator";
5194         std::string value = parseString(element, false);
5195         EXPECT_EQ(value, "vdd_regulator");
5196     }
5197 
5198     // Test where fails: Element is not a string
5199     try
5200     {
5201         const json element = R"( { "foo": "bar" } )"_json;
5202         parseString(element);
5203         ADD_FAILURE() << "Should not have reached this line.";
5204     }
5205     catch (const std::invalid_argument& e)
5206     {
5207         EXPECT_STREQ(e.what(), "Element is not a string");
5208     }
5209 
5210     // Test where fails: Empty string
5211     try
5212     {
5213         const json element = "";
5214         parseString(element);
5215         ADD_FAILURE() << "Should not have reached this line.";
5216     }
5217     catch (const std::invalid_argument& e)
5218     {
5219         EXPECT_STREQ(e.what(), "Element contains an empty string");
5220     }
5221 }
5222 
5223 TEST(ConfigFileParserTests, ParseUint8)
5224 {
5225     // Test where works: 0
5226     {
5227         const json element = R"( 0 )"_json;
5228         uint8_t value = parseUint8(element);
5229         EXPECT_EQ(value, 0);
5230     }
5231 
5232     // Test where works: UINT8_MAX
5233     {
5234         const json element = R"( 255 )"_json;
5235         uint8_t value = parseUint8(element);
5236         EXPECT_EQ(value, 255);
5237     }
5238 
5239     // Test where fails: Element is not an integer
5240     try
5241     {
5242         const json element = R"( 1.03 )"_json;
5243         parseUint8(element);
5244         ADD_FAILURE() << "Should not have reached this line.";
5245     }
5246     catch (const std::invalid_argument& e)
5247     {
5248         EXPECT_STREQ(e.what(), "Element is not an integer");
5249     }
5250 
5251     // Test where fails: Value < 0
5252     try
5253     {
5254         const json element = R"( -1 )"_json;
5255         parseUint8(element);
5256         ADD_FAILURE() << "Should not have reached this line.";
5257     }
5258     catch (const std::invalid_argument& e)
5259     {
5260         EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
5261     }
5262 
5263     // Test where fails: Value > UINT8_MAX
5264     try
5265     {
5266         const json element = R"( 256 )"_json;
5267         parseUint8(element);
5268         ADD_FAILURE() << "Should not have reached this line.";
5269     }
5270     catch (const std::invalid_argument& e)
5271     {
5272         EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
5273     }
5274 }
5275 
5276 TEST(ConfigFileParserTests, ParseUnsignedInteger)
5277 {
5278     // Test where works: 1
5279     {
5280         const json element = R"( 1 )"_json;
5281         unsigned int value = parseUnsignedInteger(element);
5282         EXPECT_EQ(value, 1);
5283     }
5284 
5285     // Test where fails: Element is not an integer
5286     try
5287     {
5288         const json element = R"( 1.5 )"_json;
5289         parseUnsignedInteger(element);
5290         ADD_FAILURE() << "Should not have reached this line.";
5291     }
5292     catch (const std::invalid_argument& e)
5293     {
5294         EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
5295     }
5296 
5297     // Test where fails: Value < 0
5298     try
5299     {
5300         const json element = R"( -1 )"_json;
5301         parseUnsignedInteger(element);
5302         ADD_FAILURE() << "Should not have reached this line.";
5303     }
5304     catch (const std::invalid_argument& e)
5305     {
5306         EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
5307     }
5308 }
5309 
5310 TEST(ConfigFileParserTests, ParseVoutDataFormat)
5311 {
5312     // Test where works: linear
5313     {
5314         const json element = "linear";
5315         pmbus_utils::VoutDataFormat value = parseVoutDataFormat(element);
5316         pmbus_utils::VoutDataFormat format =
5317             pmbus_utils::VoutDataFormat::linear;
5318         EXPECT_EQ(value, format);
5319     }
5320 
5321     // Test where works: vid
5322     {
5323         const json element = "vid";
5324         pmbus_utils::VoutDataFormat value = parseVoutDataFormat(element);
5325         pmbus_utils::VoutDataFormat format = pmbus_utils::VoutDataFormat::vid;
5326         EXPECT_EQ(value, format);
5327     }
5328 
5329     // Test where works: direct
5330     {
5331         const json element = "direct";
5332         pmbus_utils::VoutDataFormat value = parseVoutDataFormat(element);
5333         pmbus_utils::VoutDataFormat format =
5334             pmbus_utils::VoutDataFormat::direct;
5335         EXPECT_EQ(value, format);
5336     }
5337 
5338     // Test where works: ieee
5339     {
5340         const json element = "ieee";
5341         pmbus_utils::VoutDataFormat value = parseVoutDataFormat(element);
5342         pmbus_utils::VoutDataFormat format = pmbus_utils::VoutDataFormat::ieee;
5343         EXPECT_EQ(value, format);
5344     }
5345 
5346     // Test where fails: Element is not a vout data format
5347     try
5348     {
5349         const json element = "foo";
5350         parseVoutDataFormat(element);
5351         ADD_FAILURE() << "Should not have reached this line.";
5352     }
5353     catch (const std::invalid_argument& e)
5354     {
5355         EXPECT_STREQ(e.what(), "Element is not a vout data format");
5356     }
5357 
5358     // Test where fails: Element is not a string
5359     try
5360     {
5361         const json element = R"( { "foo": "bar" } )"_json;
5362         parseVoutDataFormat(element);
5363         ADD_FAILURE() << "Should not have reached this line.";
5364     }
5365     catch (const std::invalid_argument& e)
5366     {
5367         EXPECT_STREQ(e.what(), "Element is not a string");
5368     }
5369 }
5370 
5371 TEST(ConfigFileParserTests, VerifyIsArray)
5372 {
5373     // Test where element is an array
5374     try
5375     {
5376         const json element = R"( [ "foo", "bar" ] )"_json;
5377         verifyIsArray(element);
5378     }
5379     catch (const std::exception& e)
5380     {
5381         ADD_FAILURE() << "Should not have caught exception.";
5382     }
5383 
5384     // Test where element is not an array
5385     try
5386     {
5387         const json element = R"( { "foo": "bar" } )"_json;
5388         verifyIsArray(element);
5389         ADD_FAILURE() << "Should not have reached this line.";
5390     }
5391     catch (const std::invalid_argument& e)
5392     {
5393         EXPECT_STREQ(e.what(), "Element is not an array");
5394     }
5395 }
5396 
5397 TEST(ConfigFileParserTests, VerifyIsObject)
5398 {
5399     // Test where element is an object
5400     try
5401     {
5402         const json element = R"( { "foo": "bar" } )"_json;
5403         verifyIsObject(element);
5404     }
5405     catch (const std::exception& e)
5406     {
5407         ADD_FAILURE() << "Should not have caught exception.";
5408     }
5409 
5410     // Test where element is not an object
5411     try
5412     {
5413         const json element = R"( [ "foo", "bar" ] )"_json;
5414         verifyIsObject(element);
5415         ADD_FAILURE() << "Should not have reached this line.";
5416     }
5417     catch (const std::invalid_argument& e)
5418     {
5419         EXPECT_STREQ(e.what(), "Element is not an object");
5420     }
5421 }
5422 
5423 TEST(ConfigFileParserTests, VerifyPropertyCount)
5424 {
5425     // Test where element has expected number of properties
5426     try
5427     {
5428         const json element = R"(
5429             {
5430               "comments": [ "Set voltage rule" ],
5431               "id": "set_voltage_rule"
5432             }
5433         )"_json;
5434         verifyPropertyCount(element, 2);
5435     }
5436     catch (const std::exception& e)
5437     {
5438         ADD_FAILURE() << "Should not have caught exception.";
5439     }
5440 
5441     // Test where element has unexpected number of properties
5442     try
5443     {
5444         const json element = R"(
5445             {
5446               "comments": [ "Set voltage rule" ],
5447               "id": "set_voltage_rule",
5448               "foo": 1.3
5449             }
5450         )"_json;
5451         verifyPropertyCount(element, 2);
5452         ADD_FAILURE() << "Should not have reached this line.";
5453     }
5454     catch (const std::invalid_argument& e)
5455     {
5456         EXPECT_STREQ(e.what(), "Element contains an invalid property");
5457     }
5458 }
5459