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