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