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(
944             action->getFRU(),
945             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu3");
946         EXPECT_EQ(action->getValue(), true);
947     }
948 
949     // Test where fails: Element is not an object
950     try
951     {
952         const json element = R"( [ "0xFF", "0x01" ] )"_json;
953         parseComparePresence(element);
954         ADD_FAILURE() << "Should not have reached this line.";
955     }
956     catch (const std::invalid_argument& e)
957     {
958         EXPECT_STREQ(e.what(), "Element is not an object");
959     }
960 
961     // Test where fails: Invalid property specified
962     try
963     {
964         const json element = R"(
965             {
966               "fru": "system/chassis/motherboard/cpu3",
967               "value": true,
968               "foo" : true
969             }
970         )"_json;
971         parseComparePresence(element);
972         ADD_FAILURE() << "Should not have reached this line.";
973     }
974     catch (const std::invalid_argument& e)
975     {
976         EXPECT_STREQ(e.what(), "Element contains an invalid property");
977     }
978 
979     // Test where fails: Required fru property not specified
980     try
981     {
982         const json element = R"(
983             {
984               "value": true
985             }
986         )"_json;
987         parseComparePresence(element);
988         ADD_FAILURE() << "Should not have reached this line.";
989     }
990     catch (const std::invalid_argument& e)
991     {
992         EXPECT_STREQ(e.what(), "Required property missing: fru");
993     }
994 
995     // Test where fails: Required value property not specified
996     try
997     {
998         const json element = R"(
999             {
1000               "fru": "system/chassis/motherboard/cpu3"
1001             }
1002         )"_json;
1003         parseComparePresence(element);
1004         ADD_FAILURE() << "Should not have reached this line.";
1005     }
1006     catch (const std::invalid_argument& e)
1007     {
1008         EXPECT_STREQ(e.what(), "Required property missing: value");
1009     }
1010 
1011     // Test where fails: fru value is invalid
1012     try
1013     {
1014         const json element = R"(
1015             {
1016               "fru": 1,
1017               "value": true
1018             }
1019         )"_json;
1020         parseComparePresence(element);
1021         ADD_FAILURE() << "Should not have reached this line.";
1022     }
1023     catch (const std::invalid_argument& e)
1024     {
1025         EXPECT_STREQ(e.what(), "Element is not a string");
1026     }
1027 
1028     // Test where fails: value value is invalid
1029     try
1030     {
1031         const json element = R"(
1032             {
1033               "fru": "system/chassis/motherboard/cpu3",
1034               "value": 1
1035             }
1036         )"_json;
1037         parseComparePresence(element);
1038         ADD_FAILURE() << "Should not have reached this line.";
1039     }
1040     catch (const std::invalid_argument& e)
1041     {
1042         EXPECT_STREQ(e.what(), "Element is not a boolean");
1043     }
1044 }
1045 
1046 TEST(ConfigFileParserTests, ParseCompareVPD)
1047 {
1048     // Test where works
1049     {
1050         const json element = R"(
1051             {
1052               "fru": "system/chassis/disk_backplane",
1053               "keyword": "CCIN",
1054               "value": "2D35"
1055             }
1056         )"_json;
1057         std::unique_ptr<CompareVPDAction> action = parseCompareVPD(element);
1058         EXPECT_EQ(
1059             action->getFRU(),
1060             "/xyz/openbmc_project/inventory/system/chassis/disk_backplane");
1061         EXPECT_EQ(action->getKeyword(), "CCIN");
1062         EXPECT_EQ(action->getValue(), "2D35");
1063     }
1064 
1065     // Test where fails: Element is not an object
1066     try
1067     {
1068         const json element = R"( [ "0xFF", "0x01" ] )"_json;
1069         parseCompareVPD(element);
1070         ADD_FAILURE() << "Should not have reached this line.";
1071     }
1072     catch (const std::invalid_argument& e)
1073     {
1074         EXPECT_STREQ(e.what(), "Element is not an object");
1075     }
1076 
1077     // Test where fails: Invalid property specified
1078     try
1079     {
1080         const json element = R"(
1081             {
1082               "fru": "system/chassis/disk_backplane",
1083               "keyword": "CCIN",
1084               "value": "2D35",
1085               "foo" : true
1086             }
1087         )"_json;
1088         parseCompareVPD(element);
1089         ADD_FAILURE() << "Should not have reached this line.";
1090     }
1091     catch (const std::invalid_argument& e)
1092     {
1093         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1094     }
1095 
1096     // Test where fails: Required fru property not specified
1097     try
1098     {
1099         const json element = R"(
1100             {
1101               "keyword": "CCIN",
1102               "value": "2D35"
1103             }
1104         )"_json;
1105         parseCompareVPD(element);
1106         ADD_FAILURE() << "Should not have reached this line.";
1107     }
1108     catch (const std::invalid_argument& e)
1109     {
1110         EXPECT_STREQ(e.what(), "Required property missing: fru");
1111     }
1112 
1113     // Test where fails: Required keyword property not specified
1114     try
1115     {
1116         const json element = R"(
1117             {
1118               "fru": "system/chassis/disk_backplane",
1119               "value": "2D35"
1120             }
1121         )"_json;
1122         parseCompareVPD(element);
1123         ADD_FAILURE() << "Should not have reached this line.";
1124     }
1125     catch (const std::invalid_argument& e)
1126     {
1127         EXPECT_STREQ(e.what(), "Required property missing: keyword");
1128     }
1129 
1130     // Test where fails: Required value property not specified
1131     try
1132     {
1133         const json element = R"(
1134             {
1135               "fru": "system/chassis/disk_backplane",
1136               "keyword": "CCIN"
1137             }
1138         )"_json;
1139         parseCompareVPD(element);
1140         ADD_FAILURE() << "Should not have reached this line.";
1141     }
1142     catch (const std::invalid_argument& e)
1143     {
1144         EXPECT_STREQ(e.what(), "Required property missing: value");
1145     }
1146 
1147     // Test where fails: fru value is invalid
1148     try
1149     {
1150         const json element = R"(
1151             {
1152               "fru": 1,
1153               "keyword": "CCIN",
1154               "value": "2D35"
1155             }
1156         )"_json;
1157         parseCompareVPD(element);
1158         ADD_FAILURE() << "Should not have reached this line.";
1159     }
1160     catch (const std::invalid_argument& e)
1161     {
1162         EXPECT_STREQ(e.what(), "Element is not a string");
1163     }
1164 
1165     // Test where fails: keyword value is invalid
1166     try
1167     {
1168         const json element = R"(
1169             {
1170               "fru": "system/chassis/disk_backplane",
1171               "keyword": 1,
1172               "value": "2D35"
1173             }
1174         )"_json;
1175         parseCompareVPD(element);
1176         ADD_FAILURE() << "Should not have reached this line.";
1177     }
1178     catch (const std::invalid_argument& e)
1179     {
1180         EXPECT_STREQ(e.what(), "Element is not a string");
1181     }
1182 
1183     // Test where fails: value value is invalid
1184     try
1185     {
1186         const json element = R"(
1187             {
1188               "fru": "system/chassis/disk_backplane",
1189               "keyword": "CCIN",
1190               "value": 1
1191             }
1192         )"_json;
1193         parseCompareVPD(element);
1194         ADD_FAILURE() << "Should not have reached this line.";
1195     }
1196     catch (const std::invalid_argument& e)
1197     {
1198         EXPECT_STREQ(e.what(), "Element is not a string");
1199     }
1200 }
1201 
1202 TEST(ConfigFileParserTests, ParseConfiguration)
1203 {
1204     // Test where works: actions required property specified
1205     {
1206         const json element = R"(
1207             {
1208               "actions": [
1209                 {
1210                   "pmbus_write_vout_command": {
1211                     "format": "linear"
1212                   }
1213                 }
1214               ]
1215             }
1216         )"_json;
1217         std::unique_ptr<Configuration> configuration =
1218             parseConfiguration(element);
1219         EXPECT_EQ(configuration->getActions().size(), 1);
1220         EXPECT_EQ(configuration->getVolts().has_value(), false);
1221     }
1222 
1223     // Test where works: volts and actions properties specified
1224     {
1225         const json element = R"(
1226             {
1227               "comments": [ "comments property" ],
1228               "volts": 1.03,
1229               "actions": [
1230                 { "pmbus_write_vout_command": { "format": "linear" } },
1231                 { "run_rule": "set_voltage_rule" }
1232               ]
1233             }
1234         )"_json;
1235         std::unique_ptr<Configuration> configuration =
1236             parseConfiguration(element);
1237         EXPECT_EQ(configuration->getVolts().has_value(), true);
1238         EXPECT_EQ(configuration->getVolts().value(), 1.03);
1239         EXPECT_EQ(configuration->getActions().size(), 2);
1240     }
1241 
1242     // Test where works: volts and rule_id properties specified
1243     {
1244         const json element = R"(
1245             {
1246               "volts": 1.05,
1247               "rule_id": "set_voltage_rule"
1248             }
1249         )"_json;
1250         std::unique_ptr<Configuration> configuration =
1251             parseConfiguration(element);
1252         EXPECT_EQ(configuration->getVolts().has_value(), true);
1253         EXPECT_EQ(configuration->getVolts().value(), 1.05);
1254         EXPECT_EQ(configuration->getActions().size(), 1);
1255     }
1256 
1257     // Test where fails: volts value is invalid
1258     try
1259     {
1260         const json element = R"(
1261             {
1262               "volts": "foo",
1263               "actions": [
1264                 {
1265                   "pmbus_write_vout_command": {
1266                     "format": "linear"
1267                   }
1268                 }
1269               ]
1270             }
1271         )"_json;
1272         parseConfiguration(element);
1273         ADD_FAILURE() << "Should not have reached this line.";
1274     }
1275     catch (const std::invalid_argument& e)
1276     {
1277         EXPECT_STREQ(e.what(), "Element is not a number");
1278     }
1279 
1280     // Test where fails: actions object is invalid
1281     try
1282     {
1283         const json element = R"(
1284             {
1285               "volts": 1.03,
1286               "actions": 1
1287             }
1288         )"_json;
1289         parseConfiguration(element);
1290         ADD_FAILURE() << "Should not have reached this line.";
1291     }
1292     catch (const std::invalid_argument& e)
1293     {
1294         EXPECT_STREQ(e.what(), "Element is not an array");
1295     }
1296 
1297     // Test where fails: rule_id value is invalid
1298     try
1299     {
1300         const json element = R"(
1301             {
1302               "volts": 1.05,
1303               "rule_id": 1
1304             }
1305         )"_json;
1306         parseConfiguration(element);
1307         ADD_FAILURE() << "Should not have reached this line.";
1308     }
1309     catch (const std::invalid_argument& e)
1310     {
1311         EXPECT_STREQ(e.what(), "Element is not a string");
1312     }
1313 
1314     // Test where fails: Required actions or rule_id property not specified
1315     try
1316     {
1317         const json element = R"(
1318             {
1319               "volts": 1.03
1320             }
1321         )"_json;
1322         parseConfiguration(element);
1323         ADD_FAILURE() << "Should not have reached this line.";
1324     }
1325     catch (const std::invalid_argument& e)
1326     {
1327         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
1328                                "either rule_id or actions");
1329     }
1330 
1331     // Test where fails: Required actions or rule_id property both specified
1332     try
1333     {
1334         const json element = R"(
1335             {
1336               "volts": 1.03,
1337               "rule_id": "set_voltage_rule",
1338               "actions": [
1339                 {
1340                   "pmbus_write_vout_command": {
1341                     "format": "linear"
1342                   }
1343                 }
1344               ]
1345             }
1346         )"_json;
1347         parseConfiguration(element);
1348         ADD_FAILURE() << "Should not have reached this line.";
1349     }
1350     catch (const std::invalid_argument& e)
1351     {
1352         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
1353                                "either rule_id or actions");
1354     }
1355 
1356     // Test where fails: Element is not an object
1357     try
1358     {
1359         const json element = R"( [ "0xFF", "0x01" ] )"_json;
1360         parseConfiguration(element);
1361         ADD_FAILURE() << "Should not have reached this line.";
1362     }
1363     catch (const std::invalid_argument& e)
1364     {
1365         EXPECT_STREQ(e.what(), "Element is not an object");
1366     }
1367 
1368     // Test where fails: Invalid property specified
1369     try
1370     {
1371         const json element = R"(
1372             {
1373               "volts": 1.03,
1374               "rule_id": "set_voltage_rule",
1375               "foo": 1
1376             }
1377         )"_json;
1378         parseConfiguration(element);
1379         ADD_FAILURE() << "Should not have reached this line.";
1380     }
1381     catch (const std::invalid_argument& e)
1382     {
1383         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1384     }
1385 }
1386 
1387 TEST(ConfigFileParserTests, ParseDevice)
1388 {
1389     // Test where works: Only required properties specified
1390     {
1391         const json element = R"(
1392             {
1393               "id": "vdd_regulator",
1394               "is_regulator": true,
1395               "fru": "system/chassis/motherboard/regulator2",
1396               "i2c_interface": { "bus": 1, "address": "0x70" }
1397             }
1398         )"_json;
1399         std::unique_ptr<Device> device = parseDevice(element);
1400         EXPECT_EQ(device->getID(), "vdd_regulator");
1401         EXPECT_EQ(device->isRegulator(), true);
1402         EXPECT_EQ(device->getFRU(), "/xyz/openbmc_project/inventory/system/"
1403                                     "chassis/motherboard/regulator2");
1404         EXPECT_NE(&(device->getI2CInterface()), nullptr);
1405         EXPECT_EQ(device->getPresenceDetection(), nullptr);
1406         EXPECT_EQ(device->getConfiguration(), nullptr);
1407         EXPECT_EQ(device->getRails().size(), 0);
1408     }
1409 
1410     // Test where works: All properties specified
1411     {
1412         const json element = R"(
1413             {
1414               "id": "vdd_regulator",
1415               "is_regulator": true,
1416               "fru": "system/chassis/motherboard/regulator2",
1417               "i2c_interface":
1418               {
1419                   "bus": 1,
1420                   "address": "0x70"
1421               },
1422               "configuration":
1423               {
1424                   "rule_id": "configure_ir35221_rule"
1425               },
1426               "presence_detection":
1427               {
1428                   "rule_id": "is_foobar_backplane_installed_rule"
1429               },
1430               "rails":
1431               [
1432                 {
1433                   "id": "vdd"
1434                 }
1435               ]
1436             }
1437         )"_json;
1438         std::unique_ptr<Device> device = parseDevice(element);
1439         EXPECT_EQ(device->getID(), "vdd_regulator");
1440         EXPECT_EQ(device->isRegulator(), true);
1441         EXPECT_EQ(device->getFRU(), "/xyz/openbmc_project/inventory/system/"
1442                                     "chassis/motherboard/regulator2");
1443         EXPECT_NE(&(device->getI2CInterface()), nullptr);
1444         EXPECT_NE(device->getPresenceDetection(), nullptr);
1445         EXPECT_NE(device->getConfiguration(), nullptr);
1446         EXPECT_EQ(device->getRails().size(), 1);
1447     }
1448 
1449     // Test where fails: rails property exists and is_regulator is false
1450     try
1451     {
1452         const json element = R"(
1453             {
1454               "id": "vdd_regulator",
1455               "is_regulator": false,
1456               "fru": "system/chassis/motherboard/regulator2",
1457               "i2c_interface":
1458               {
1459                   "bus": 1,
1460                   "address": "0x70"
1461               },
1462               "configuration":
1463               {
1464                   "rule_id": "configure_ir35221_rule"
1465               },
1466               "rails":
1467               [
1468                 {
1469                   "id": "vdd"
1470                 }
1471               ]
1472             }
1473         )"_json;
1474         parseDevice(element);
1475         ADD_FAILURE() << "Should not have reached this line.";
1476     }
1477     catch (const std::invalid_argument& e)
1478     {
1479         EXPECT_STREQ(e.what(),
1480                      "Invalid rails property when is_regulator is false");
1481     }
1482 
1483     // Test where fails: id value is invalid
1484     try
1485     {
1486         const json element = R"(
1487             {
1488               "id": 3,
1489               "is_regulator": true,
1490               "fru": "system/chassis/motherboard/regulator2",
1491               "i2c_interface":
1492               {
1493                   "bus": 1,
1494                   "address": "0x70"
1495               }
1496             }
1497         )"_json;
1498         parseDevice(element);
1499         ADD_FAILURE() << "Should not have reached this line.";
1500     }
1501     catch (const std::invalid_argument& e)
1502     {
1503         EXPECT_STREQ(e.what(), "Element is not a string");
1504     }
1505 
1506     // Test where fails: is_regulator value is invalid
1507     try
1508     {
1509         const json element = R"(
1510             {
1511               "id": "vdd_regulator",
1512               "is_regulator": 3,
1513               "fru": "system/chassis/motherboard/regulator2",
1514               "i2c_interface":
1515               {
1516                   "bus": 1,
1517                   "address": "0x70"
1518               }
1519             }
1520         )"_json;
1521         parseDevice(element);
1522         ADD_FAILURE() << "Should not have reached this line.";
1523     }
1524     catch (const std::invalid_argument& e)
1525     {
1526         EXPECT_STREQ(e.what(), "Element is not a boolean");
1527     }
1528 
1529     // Test where fails: fru value is invalid
1530     try
1531     {
1532         const json element = R"(
1533             {
1534               "id": "vdd_regulator",
1535               "is_regulator": true,
1536               "fru": 2,
1537               "i2c_interface":
1538               {
1539                   "bus": 1,
1540                   "address": "0x70"
1541               }
1542             }
1543         )"_json;
1544         parseDevice(element);
1545         ADD_FAILURE() << "Should not have reached this line.";
1546     }
1547     catch (const std::invalid_argument& e)
1548     {
1549         EXPECT_STREQ(e.what(), "Element is not a string");
1550     }
1551 
1552     // Test where fails: i2c_interface value is invalid
1553     try
1554     {
1555         const json element = R"(
1556             {
1557               "id": "vdd_regulator",
1558               "is_regulator": true,
1559               "fru": "system/chassis/motherboard/regulator2",
1560               "i2c_interface": 3
1561             }
1562         )"_json;
1563         parseDevice(element);
1564         ADD_FAILURE() << "Should not have reached this line.";
1565     }
1566     catch (const std::invalid_argument& e)
1567     {
1568         EXPECT_STREQ(e.what(), "Element is not an object");
1569     }
1570 
1571     // Test where fails: Required id property not specified
1572     try
1573     {
1574         const json element = R"(
1575             {
1576               "is_regulator": true,
1577               "fru": "system/chassis/motherboard/regulator2",
1578               "i2c_interface":
1579               {
1580                   "bus": 1,
1581                   "address": "0x70"
1582               }
1583             }
1584         )"_json;
1585         parseDevice(element);
1586         ADD_FAILURE() << "Should not have reached this line.";
1587     }
1588     catch (const std::invalid_argument& e)
1589     {
1590         EXPECT_STREQ(e.what(), "Required property missing: id");
1591     }
1592 
1593     // Test where fails: Required is_regulator property not specified
1594     try
1595     {
1596         const json element = R"(
1597             {
1598               "id": "vdd_regulator",
1599               "fru": "system/chassis/motherboard/regulator2",
1600               "i2c_interface":
1601               {
1602                   "bus": 1,
1603                   "address": "0x70"
1604               }
1605             }
1606         )"_json;
1607         parseDevice(element);
1608         ADD_FAILURE() << "Should not have reached this line.";
1609     }
1610     catch (const std::invalid_argument& e)
1611     {
1612         EXPECT_STREQ(e.what(), "Required property missing: is_regulator");
1613     }
1614 
1615     // Test where fails: Required fru property not specified
1616     try
1617     {
1618         const json element = R"(
1619             {
1620               "id": "vdd_regulator",
1621               "is_regulator": true,
1622               "i2c_interface":
1623               {
1624                   "bus": 1,
1625                   "address": "0x70"
1626               }
1627             }
1628         )"_json;
1629         parseDevice(element);
1630         ADD_FAILURE() << "Should not have reached this line.";
1631     }
1632     catch (const std::invalid_argument& e)
1633     {
1634         EXPECT_STREQ(e.what(), "Required property missing: fru");
1635     }
1636 
1637     // Test where fails: Required i2c_interface property not specified
1638     try
1639     {
1640         const json element = R"(
1641             {
1642               "id": "vdd_regulator",
1643               "is_regulator": true,
1644               "fru": "system/chassis/motherboard/regulator2"
1645             }
1646         )"_json;
1647         parseDevice(element);
1648         ADD_FAILURE() << "Should not have reached this line.";
1649     }
1650     catch (const std::invalid_argument& e)
1651     {
1652         EXPECT_STREQ(e.what(), "Required property missing: i2c_interface");
1653     }
1654 
1655     // Test where fails: Element is not an object
1656     try
1657     {
1658         const json element = R"( [ "0xFF", "0x01" ] )"_json;
1659         parseDevice(element);
1660         ADD_FAILURE() << "Should not have reached this line.";
1661     }
1662     catch (const std::invalid_argument& e)
1663     {
1664         EXPECT_STREQ(e.what(), "Element is not an object");
1665     }
1666 
1667     // Test where fails: Invalid property specified
1668     try
1669     {
1670         const json element = R"(
1671             {
1672               "id": "vdd_regulator",
1673               "is_regulator": true,
1674               "fru": "system/chassis/motherboard/regulator2",
1675               "i2c_interface": { "bus": 1, "address": "0x70" },
1676               "foo" : true
1677             }
1678         )"_json;
1679         parseDevice(element);
1680         ADD_FAILURE() << "Should not have reached this line.";
1681     }
1682     catch (const std::invalid_argument& e)
1683     {
1684         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1685     }
1686 }
1687 
1688 TEST(ConfigFileParserTests, ParseDeviceArray)
1689 {
1690     // Test where works
1691     {
1692         const json element = R"(
1693             [
1694               {
1695                 "id": "vdd_regulator",
1696                 "is_regulator": true,
1697                 "fru": "system/chassis/motherboard/regulator2",
1698                 "i2c_interface": { "bus": 1, "address": "0x70" }
1699               },
1700               {
1701                 "id": "vio_regulator",
1702                 "is_regulator": true,
1703                 "fru": "system/chassis/motherboard/regulator2",
1704                 "i2c_interface": { "bus": 1, "address": "0x71" }
1705               }
1706             ]
1707         )"_json;
1708         std::vector<std::unique_ptr<Device>> devices =
1709             parseDeviceArray(element);
1710         EXPECT_EQ(devices.size(), 2);
1711         EXPECT_EQ(devices[0]->getID(), "vdd_regulator");
1712         EXPECT_EQ(devices[1]->getID(), "vio_regulator");
1713     }
1714 
1715     // Test where fails: Element is not an array
1716     try
1717     {
1718         const json element = R"(
1719             {
1720               "foo": "bar"
1721             }
1722         )"_json;
1723         parseDeviceArray(element);
1724         ADD_FAILURE() << "Should not have reached this line.";
1725     }
1726     catch (const std::invalid_argument& e)
1727     {
1728         EXPECT_STREQ(e.what(), "Element is not an array");
1729     }
1730 }
1731 
1732 TEST(ConfigFileParserTests, ParseDouble)
1733 {
1734     // Test where works: floating point value
1735     {
1736         const json element = R"( 1.03 )"_json;
1737         double value = parseDouble(element);
1738         EXPECT_EQ(value, 1.03);
1739     }
1740 
1741     // Test where works: integer value
1742     {
1743         const json element = R"( 24 )"_json;
1744         double value = parseDouble(element);
1745         EXPECT_EQ(value, 24.0);
1746     }
1747 
1748     // Test where fails: Element is not a number
1749     try
1750     {
1751         const json element = R"( true )"_json;
1752         parseDouble(element);
1753         ADD_FAILURE() << "Should not have reached this line.";
1754     }
1755     catch (const std::invalid_argument& e)
1756     {
1757         EXPECT_STREQ(e.what(), "Element is not a number");
1758     }
1759 }
1760 
1761 TEST(ConfigFileParserTests, ParseHexByte)
1762 {
1763     // Test where works: "0xFF"
1764     {
1765         const json element = R"( "0xFF" )"_json;
1766         uint8_t value = parseHexByte(element);
1767         EXPECT_EQ(value, 0xFF);
1768     }
1769 
1770     // Test where works: "0xff"
1771     {
1772         const json element = R"( "0xff" )"_json;
1773         uint8_t value = parseHexByte(element);
1774         EXPECT_EQ(value, 0xff);
1775     }
1776 
1777     // Test where works: "0xf"
1778     {
1779         const json element = R"( "0xf" )"_json;
1780         uint8_t value = parseHexByte(element);
1781         EXPECT_EQ(value, 0xf);
1782     }
1783 
1784     // Test where fails: "0xfff"
1785     try
1786     {
1787         const json element = R"( "0xfff" )"_json;
1788         parseHexByte(element);
1789         ADD_FAILURE() << "Should not have reached this line.";
1790     }
1791     catch (const std::invalid_argument& e)
1792     {
1793         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1794     }
1795 
1796     // Test where fails: "0xAG"
1797     try
1798     {
1799         const json element = R"( "0xAG" )"_json;
1800         parseHexByte(element);
1801         ADD_FAILURE() << "Should not have reached this line.";
1802     }
1803     catch (const std::invalid_argument& e)
1804     {
1805         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1806     }
1807 
1808     // Test where fails: "ff"
1809     try
1810     {
1811         const json element = R"( "ff" )"_json;
1812         parseHexByte(element);
1813         ADD_FAILURE() << "Should not have reached this line.";
1814     }
1815     catch (const std::invalid_argument& e)
1816     {
1817         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1818     }
1819 
1820     // Test where fails: ""
1821     try
1822     {
1823         const json element = "";
1824         parseHexByte(element);
1825         ADD_FAILURE() << "Should not have reached this line.";
1826     }
1827     catch (const std::invalid_argument& e)
1828     {
1829         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1830     }
1831 
1832     // Test where fails: "f"
1833     try
1834     {
1835         const json element = R"( "f" )"_json;
1836         parseHexByte(element);
1837         ADD_FAILURE() << "Should not have reached this line.";
1838     }
1839     catch (const std::invalid_argument& e)
1840     {
1841         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1842     }
1843 
1844     // Test where fails: "0x"
1845     try
1846     {
1847         const json element = R"( "0x" )"_json;
1848         parseHexByte(element);
1849         ADD_FAILURE() << "Should not have reached this line.";
1850     }
1851     catch (const std::invalid_argument& e)
1852     {
1853         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1854     }
1855 
1856     // Test where fails: "0Xff"
1857     try
1858     {
1859         const json element = R"( "0XFF" )"_json;
1860         parseHexByte(element);
1861         ADD_FAILURE() << "Should not have reached this line.";
1862     }
1863     catch (const std::invalid_argument& e)
1864     {
1865         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1866     }
1867 }
1868 
1869 TEST(ConfigFileParserTests, ParseHexByteArray)
1870 {
1871     // Test where works
1872     {
1873         const json element = R"( [ "0xCC", "0xFF" ] )"_json;
1874         std::vector<uint8_t> hexBytes = parseHexByteArray(element);
1875         std::vector<uint8_t> expected = {0xcc, 0xff};
1876         EXPECT_EQ(hexBytes, expected);
1877     }
1878 
1879     // Test where fails: Element is not an array
1880     try
1881     {
1882         const json element = 0;
1883         parseHexByteArray(element);
1884         ADD_FAILURE() << "Should not have reached this line.";
1885     }
1886     catch (const std::invalid_argument& e)
1887     {
1888         EXPECT_STREQ(e.what(), "Element is not an array");
1889     }
1890 }
1891 
1892 TEST(ConfigFileParserTests, ParseI2CCompareBit)
1893 {
1894     // Test where works
1895     {
1896         const json element = R"(
1897             {
1898               "register": "0xA0",
1899               "position": 3,
1900               "value": 0
1901             }
1902         )"_json;
1903         std::unique_ptr<I2CCompareBitAction> action =
1904             parseI2CCompareBit(element);
1905         EXPECT_EQ(action->getRegister(), 0xA0);
1906         EXPECT_EQ(action->getPosition(), 3);
1907         EXPECT_EQ(action->getValue(), 0);
1908     }
1909 
1910     // Test where fails: Invalid property specified
1911     try
1912     {
1913         const json element = R"(
1914             {
1915               "register": "0xA0",
1916               "position": 3,
1917               "value": 0,
1918               "foo": 3
1919             }
1920         )"_json;
1921         parseI2CCompareBit(element);
1922         ADD_FAILURE() << "Should not have reached this line.";
1923     }
1924     catch (const std::invalid_argument& e)
1925     {
1926         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1927     }
1928 
1929     // Test where fails: Element is not an object
1930     try
1931     {
1932         const json element = R"( [ "0xFF", "0x01" ] )"_json;
1933         parseI2CCompareBit(element);
1934         ADD_FAILURE() << "Should not have reached this line.";
1935     }
1936     catch (const std::invalid_argument& e)
1937     {
1938         EXPECT_STREQ(e.what(), "Element is not an object");
1939     }
1940 
1941     // Test where fails: register value is invalid
1942     try
1943     {
1944         const json element = R"(
1945             {
1946               "register": "0xAG",
1947               "position": 3,
1948               "value": 0
1949             }
1950         )"_json;
1951         parseI2CCompareBit(element);
1952         ADD_FAILURE() << "Should not have reached this line.";
1953     }
1954     catch (const std::invalid_argument& e)
1955     {
1956         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1957     }
1958 
1959     // Test where fails: position value is invalid
1960     try
1961     {
1962         const json element = R"(
1963                 {
1964                   "register": "0xA0",
1965                   "position": 8,
1966                   "value": 0
1967                 }
1968             )"_json;
1969         parseI2CCompareBit(element);
1970         ADD_FAILURE() << "Should not have reached this line.";
1971     }
1972     catch (const std::invalid_argument& e)
1973     {
1974         EXPECT_STREQ(e.what(), "Element is not a bit position");
1975     }
1976 
1977     // Test where fails: value value is invalid
1978     try
1979     {
1980         const json element = R"(
1981                 {
1982                   "register": "0xA0",
1983                   "position": 3,
1984                   "value": 2
1985                 }
1986             )"_json;
1987         parseI2CCompareBit(element);
1988         ADD_FAILURE() << "Should not have reached this line.";
1989     }
1990     catch (const std::invalid_argument& e)
1991     {
1992         EXPECT_STREQ(e.what(), "Element is not a bit value");
1993     }
1994 
1995     // Test where fails: Required register property not specified
1996     try
1997     {
1998         const json element = R"(
1999             {
2000               "position": 3,
2001               "value": 0
2002             }
2003         )"_json;
2004         parseI2CCompareBit(element);
2005         ADD_FAILURE() << "Should not have reached this line.";
2006     }
2007     catch (const std::invalid_argument& e)
2008     {
2009         EXPECT_STREQ(e.what(), "Required property missing: register");
2010     }
2011 
2012     // Test where fails: Required position property not specified
2013     try
2014     {
2015         const json element = R"(
2016             {
2017               "register": "0xA0",
2018               "value": 0
2019             }
2020         )"_json;
2021         parseI2CCompareBit(element);
2022         ADD_FAILURE() << "Should not have reached this line.";
2023     }
2024     catch (const std::invalid_argument& e)
2025     {
2026         EXPECT_STREQ(e.what(), "Required property missing: position");
2027     }
2028 
2029     // Test where fails: Required value property not specified
2030     try
2031     {
2032         const json element = R"(
2033             {
2034               "register": "0xA0",
2035               "position": 3
2036             }
2037         )"_json;
2038         parseI2CCompareBit(element);
2039         ADD_FAILURE() << "Should not have reached this line.";
2040     }
2041     catch (const std::invalid_argument& e)
2042     {
2043         EXPECT_STREQ(e.what(), "Required property missing: value");
2044     }
2045 }
2046 
2047 TEST(ConfigFileParserTests, ParseI2CCompareByte)
2048 {
2049     // Test where works: Only required properties specified
2050     {
2051         const json element = R"(
2052             {
2053               "register": "0x0A",
2054               "value": "0xCC"
2055             }
2056         )"_json;
2057         std::unique_ptr<I2CCompareByteAction> action =
2058             parseI2CCompareByte(element);
2059         EXPECT_EQ(action->getRegister(), 0x0A);
2060         EXPECT_EQ(action->getValue(), 0xCC);
2061         EXPECT_EQ(action->getMask(), 0xFF);
2062     }
2063 
2064     // Test where works: All properties specified
2065     {
2066         const json element = R"(
2067             {
2068               "register": "0x0A",
2069               "value": "0xCC",
2070               "mask": "0xF7"
2071             }
2072         )"_json;
2073         std::unique_ptr<I2CCompareByteAction> action =
2074             parseI2CCompareByte(element);
2075         EXPECT_EQ(action->getRegister(), 0x0A);
2076         EXPECT_EQ(action->getValue(), 0xCC);
2077         EXPECT_EQ(action->getMask(), 0xF7);
2078     }
2079 
2080     // Test where fails: Element is not an object
2081     try
2082     {
2083         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2084         parseI2CCompareByte(element);
2085         ADD_FAILURE() << "Should not have reached this line.";
2086     }
2087     catch (const std::invalid_argument& e)
2088     {
2089         EXPECT_STREQ(e.what(), "Element is not an object");
2090     }
2091 
2092     // Test where fails: Invalid property specified
2093     try
2094     {
2095         const json element = R"(
2096             {
2097               "register": "0x0A",
2098               "value": "0xCC",
2099               "mask": "0xF7",
2100               "foo": 1
2101             }
2102         )"_json;
2103         parseI2CCompareByte(element);
2104         ADD_FAILURE() << "Should not have reached this line.";
2105     }
2106     catch (const std::invalid_argument& e)
2107     {
2108         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2109     }
2110 
2111     // Test where fails: register value is invalid
2112     try
2113     {
2114         const json element = R"(
2115             {
2116               "register": "0x0Z",
2117               "value": "0xCC",
2118               "mask": "0xF7"
2119             }
2120         )"_json;
2121         parseI2CCompareByte(element);
2122         ADD_FAILURE() << "Should not have reached this line.";
2123     }
2124     catch (const std::invalid_argument& e)
2125     {
2126         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2127     }
2128 
2129     // Test where fails: value value is invalid
2130     try
2131     {
2132         const json element = R"(
2133             {
2134               "register": "0x0A",
2135               "value": "0xCCC",
2136               "mask": "0xF7"
2137             }
2138         )"_json;
2139         parseI2CCompareByte(element);
2140         ADD_FAILURE() << "Should not have reached this line.";
2141     }
2142     catch (const std::invalid_argument& e)
2143     {
2144         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2145     }
2146 
2147     // Test where fails: mask value is invalid
2148     try
2149     {
2150         const json element = R"(
2151             {
2152               "register": "0x0A",
2153               "value": "0xCC",
2154               "mask": "F7"
2155             }
2156         )"_json;
2157         parseI2CCompareByte(element);
2158         ADD_FAILURE() << "Should not have reached this line.";
2159     }
2160     catch (const std::invalid_argument& e)
2161     {
2162         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2163     }
2164 
2165     // Test where fails: Required register property not specified
2166     try
2167     {
2168         const json element = R"(
2169             {
2170               "value": "0xCC",
2171               "mask": "0xF7"
2172             }
2173         )"_json;
2174         parseI2CCompareByte(element);
2175         ADD_FAILURE() << "Should not have reached this line.";
2176     }
2177     catch (const std::invalid_argument& e)
2178     {
2179         EXPECT_STREQ(e.what(), "Required property missing: register");
2180     }
2181 
2182     // Test where fails: Required value property not specified
2183     try
2184     {
2185         const json element = R"(
2186             {
2187               "register": "0x0A",
2188               "mask": "0xF7"
2189             }
2190         )"_json;
2191         parseI2CCompareByte(element);
2192         ADD_FAILURE() << "Should not have reached this line.";
2193     }
2194     catch (const std::invalid_argument& e)
2195     {
2196         EXPECT_STREQ(e.what(), "Required property missing: value");
2197     }
2198 }
2199 
2200 TEST(ConfigFileParserTests, ParseI2CCompareBytes)
2201 {
2202     // Test where works: Only required properties specified
2203     {
2204         const json element = R"(
2205             {
2206               "register": "0x0A",
2207               "values": [ "0xCC", "0xFF" ]
2208             }
2209         )"_json;
2210         std::unique_ptr<I2CCompareBytesAction> action =
2211             parseI2CCompareBytes(element);
2212         EXPECT_EQ(action->getRegister(), 0x0A);
2213         EXPECT_EQ(action->getValues().size(), 2);
2214         EXPECT_EQ(action->getValues()[0], 0xCC);
2215         EXPECT_EQ(action->getValues()[1], 0xFF);
2216         EXPECT_EQ(action->getMasks().size(), 2);
2217         EXPECT_EQ(action->getMasks()[0], 0xFF);
2218         EXPECT_EQ(action->getMasks()[1], 0xFF);
2219     }
2220 
2221     // Test where works: All properties specified
2222     {
2223         const json element = R"(
2224             {
2225               "register": "0x0A",
2226               "values": [ "0xCC", "0xFF" ],
2227               "masks":  [ "0x7F", "0x77" ]
2228             }
2229         )"_json;
2230         std::unique_ptr<I2CCompareBytesAction> action =
2231             parseI2CCompareBytes(element);
2232         EXPECT_EQ(action->getRegister(), 0x0A);
2233         EXPECT_EQ(action->getValues().size(), 2);
2234         EXPECT_EQ(action->getValues()[0], 0xCC);
2235         EXPECT_EQ(action->getValues()[1], 0xFF);
2236         EXPECT_EQ(action->getMasks().size(), 2);
2237         EXPECT_EQ(action->getMasks()[0], 0x7F);
2238         EXPECT_EQ(action->getMasks()[1], 0x77);
2239     }
2240 
2241     // Test where fails: Element is not an object
2242     try
2243     {
2244         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2245         parseI2CCompareBytes(element);
2246         ADD_FAILURE() << "Should not have reached this line.";
2247     }
2248     catch (const std::invalid_argument& e)
2249     {
2250         EXPECT_STREQ(e.what(), "Element is not an object");
2251     }
2252 
2253     // Test where fails: Invalid property specified
2254     try
2255     {
2256         const json element = R"(
2257             {
2258               "register": "0x0A",
2259               "values": [ "0xCC", "0xFF" ],
2260               "masks":  [ "0x7F", "0x7F" ],
2261               "foo": 1
2262             }
2263         )"_json;
2264         parseI2CCompareBytes(element);
2265         ADD_FAILURE() << "Should not have reached this line.";
2266     }
2267     catch (const std::invalid_argument& e)
2268     {
2269         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2270     }
2271 
2272     // Test where fails: register value is invalid
2273     try
2274     {
2275         const json element = R"(
2276             {
2277               "register": "0x0Z",
2278               "values": [ "0xCC", "0xFF" ],
2279               "masks":  [ "0x7F", "0x7F" ]
2280             }
2281         )"_json;
2282         parseI2CCompareBytes(element);
2283         ADD_FAILURE() << "Should not have reached this line.";
2284     }
2285     catch (const std::invalid_argument& e)
2286     {
2287         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2288     }
2289 
2290     // Test where fails: values value is invalid
2291     try
2292     {
2293         const json element = R"(
2294             {
2295               "register": "0x0A",
2296               "values": [ "0xCCC", "0xFF" ],
2297               "masks":  [ "0x7F", "0x7F" ]
2298             }
2299         )"_json;
2300         parseI2CCompareBytes(element);
2301         ADD_FAILURE() << "Should not have reached this line.";
2302     }
2303     catch (const std::invalid_argument& e)
2304     {
2305         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2306     }
2307 
2308     // Test where fails: masks value is invalid
2309     try
2310     {
2311         const json element = R"(
2312             {
2313               "register": "0x0A",
2314               "values": [ "0xCC", "0xFF" ],
2315               "masks":  [ "F", "0x7F" ]
2316             }
2317         )"_json;
2318         parseI2CCompareBytes(element);
2319         ADD_FAILURE() << "Should not have reached this line.";
2320     }
2321     catch (const std::invalid_argument& e)
2322     {
2323         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2324     }
2325 
2326     // Test where fails: number of elements in masks is invalid
2327     try
2328     {
2329         const json element = R"(
2330             {
2331               "register": "0x0A",
2332               "values": [ "0xCC", "0xFF" ],
2333               "masks":  [ "0x7F" ]
2334             }
2335         )"_json;
2336         parseI2CCompareBytes(element);
2337         ADD_FAILURE() << "Should not have reached this line.";
2338     }
2339     catch (const std::invalid_argument& e)
2340     {
2341         EXPECT_STREQ(e.what(), "Invalid number of elements in masks");
2342     }
2343 
2344     // Test where fails: Required register property not specified
2345     try
2346     {
2347         const json element = R"(
2348             {
2349               "values": [ "0xCC", "0xFF" ]
2350             }
2351         )"_json;
2352         parseI2CCompareBytes(element);
2353         ADD_FAILURE() << "Should not have reached this line.";
2354     }
2355     catch (const std::invalid_argument& e)
2356     {
2357         EXPECT_STREQ(e.what(), "Required property missing: register");
2358     }
2359 
2360     // Test where fails: Required values property not specified
2361     try
2362     {
2363         const json element = R"(
2364             {
2365               "register": "0x0A"
2366             }
2367         )"_json;
2368         parseI2CCompareBytes(element);
2369         ADD_FAILURE() << "Should not have reached this line.";
2370     }
2371     catch (const std::invalid_argument& e)
2372     {
2373         EXPECT_STREQ(e.what(), "Required property missing: values");
2374     }
2375 }
2376 
2377 TEST(ConfigFileParserTests, ParseI2CWriteBit)
2378 {
2379     // Test where works
2380     {
2381         const json element = R"(
2382             {
2383               "register": "0xA0",
2384               "position": 3,
2385               "value": 0
2386             }
2387         )"_json;
2388         std::unique_ptr<I2CWriteBitAction> action = parseI2CWriteBit(element);
2389         EXPECT_EQ(action->getRegister(), 0xA0);
2390         EXPECT_EQ(action->getPosition(), 3);
2391         EXPECT_EQ(action->getValue(), 0);
2392     }
2393 
2394     // Test where fails: Invalid property specified
2395     try
2396     {
2397         const json element = R"(
2398             {
2399               "register": "0xA0",
2400               "position": 3,
2401               "value": 0,
2402               "foo": 3
2403             }
2404         )"_json;
2405         parseI2CWriteBit(element);
2406         ADD_FAILURE() << "Should not have reached this line.";
2407     }
2408     catch (const std::invalid_argument& e)
2409     {
2410         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2411     }
2412 
2413     // Test where fails: Element is not an object
2414     try
2415     {
2416         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2417         parseI2CWriteBit(element);
2418         ADD_FAILURE() << "Should not have reached this line.";
2419     }
2420     catch (const std::invalid_argument& e)
2421     {
2422         EXPECT_STREQ(e.what(), "Element is not an object");
2423     }
2424 
2425     // Test where fails: register value is invalid
2426     try
2427     {
2428         const json element = R"(
2429             {
2430               "register": "0xAG",
2431               "position": 3,
2432               "value": 0
2433             }
2434         )"_json;
2435         parseI2CWriteBit(element);
2436         ADD_FAILURE() << "Should not have reached this line.";
2437     }
2438     catch (const std::invalid_argument& e)
2439     {
2440         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2441     }
2442 
2443     // Test where fails: position value is invalid
2444     try
2445     {
2446         const json element = R"(
2447                 {
2448                   "register": "0xA0",
2449                   "position": 8,
2450                   "value": 0
2451                 }
2452             )"_json;
2453         parseI2CWriteBit(element);
2454         ADD_FAILURE() << "Should not have reached this line.";
2455     }
2456     catch (const std::invalid_argument& e)
2457     {
2458         EXPECT_STREQ(e.what(), "Element is not a bit position");
2459     }
2460 
2461     // Test where fails: value value is invalid
2462     try
2463     {
2464         const json element = R"(
2465                 {
2466                   "register": "0xA0",
2467                   "position": 3,
2468                   "value": 2
2469                 }
2470             )"_json;
2471         parseI2CWriteBit(element);
2472         ADD_FAILURE() << "Should not have reached this line.";
2473     }
2474     catch (const std::invalid_argument& e)
2475     {
2476         EXPECT_STREQ(e.what(), "Element is not a bit value");
2477     }
2478 
2479     // Test where fails: Required register property not specified
2480     try
2481     {
2482         const json element = R"(
2483             {
2484               "position": 3,
2485               "value": 0
2486             }
2487         )"_json;
2488         parseI2CWriteBit(element);
2489         ADD_FAILURE() << "Should not have reached this line.";
2490     }
2491     catch (const std::invalid_argument& e)
2492     {
2493         EXPECT_STREQ(e.what(), "Required property missing: register");
2494     }
2495 
2496     // Test where fails: Required position property not specified
2497     try
2498     {
2499         const json element = R"(
2500             {
2501               "register": "0xA0",
2502               "value": 0
2503             }
2504         )"_json;
2505         parseI2CWriteBit(element);
2506         ADD_FAILURE() << "Should not have reached this line.";
2507     }
2508     catch (const std::invalid_argument& e)
2509     {
2510         EXPECT_STREQ(e.what(), "Required property missing: position");
2511     }
2512 
2513     // Test where fails: Required value property not specified
2514     try
2515     {
2516         const json element = R"(
2517             {
2518               "register": "0xA0",
2519               "position": 3
2520             }
2521         )"_json;
2522         parseI2CWriteBit(element);
2523         ADD_FAILURE() << "Should not have reached this line.";
2524     }
2525     catch (const std::invalid_argument& e)
2526     {
2527         EXPECT_STREQ(e.what(), "Required property missing: value");
2528     }
2529 }
2530 
2531 TEST(ConfigFileParserTests, ParseI2CWriteByte)
2532 {
2533     // Test where works: Only required properties specified
2534     {
2535         const json element = R"(
2536             {
2537               "register": "0x0A",
2538               "value": "0xCC"
2539             }
2540         )"_json;
2541         std::unique_ptr<I2CWriteByteAction> action = parseI2CWriteByte(element);
2542         EXPECT_EQ(action->getRegister(), 0x0A);
2543         EXPECT_EQ(action->getValue(), 0xCC);
2544         EXPECT_EQ(action->getMask(), 0xFF);
2545     }
2546 
2547     // Test where works: All properties specified
2548     {
2549         const json element = R"(
2550             {
2551               "register": "0x0A",
2552               "value": "0xCC",
2553               "mask": "0xF7"
2554             }
2555         )"_json;
2556         std::unique_ptr<I2CWriteByteAction> action = parseI2CWriteByte(element);
2557         EXPECT_EQ(action->getRegister(), 0x0A);
2558         EXPECT_EQ(action->getValue(), 0xCC);
2559         EXPECT_EQ(action->getMask(), 0xF7);
2560     }
2561 
2562     // Test where fails: Element is not an object
2563     try
2564     {
2565         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2566         parseI2CWriteByte(element);
2567         ADD_FAILURE() << "Should not have reached this line.";
2568     }
2569     catch (const std::invalid_argument& e)
2570     {
2571         EXPECT_STREQ(e.what(), "Element is not an object");
2572     }
2573 
2574     // Test where fails: Invalid property specified
2575     try
2576     {
2577         const json element = R"(
2578             {
2579               "register": "0x0A",
2580               "value": "0xCC",
2581               "mask": "0xF7",
2582               "foo": 1
2583             }
2584         )"_json;
2585         parseI2CWriteByte(element);
2586         ADD_FAILURE() << "Should not have reached this line.";
2587     }
2588     catch (const std::invalid_argument& e)
2589     {
2590         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2591     }
2592 
2593     // Test where fails: register value is invalid
2594     try
2595     {
2596         const json element = R"(
2597             {
2598               "register": "0x0Z",
2599               "value": "0xCC",
2600               "mask": "0xF7"
2601             }
2602         )"_json;
2603         parseI2CWriteByte(element);
2604         ADD_FAILURE() << "Should not have reached this line.";
2605     }
2606     catch (const std::invalid_argument& e)
2607     {
2608         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2609     }
2610 
2611     // Test where fails: value value is invalid
2612     try
2613     {
2614         const json element = R"(
2615             {
2616               "register": "0x0A",
2617               "value": "0xCCC",
2618               "mask": "0xF7"
2619             }
2620         )"_json;
2621         parseI2CWriteByte(element);
2622         ADD_FAILURE() << "Should not have reached this line.";
2623     }
2624     catch (const std::invalid_argument& e)
2625     {
2626         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2627     }
2628 
2629     // Test where fails: mask value is invalid
2630     try
2631     {
2632         const json element = R"(
2633             {
2634               "register": "0x0A",
2635               "value": "0xCC",
2636               "mask": "F7"
2637             }
2638         )"_json;
2639         parseI2CWriteByte(element);
2640         ADD_FAILURE() << "Should not have reached this line.";
2641     }
2642     catch (const std::invalid_argument& e)
2643     {
2644         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2645     }
2646 
2647     // Test where fails: Required register property not specified
2648     try
2649     {
2650         const json element = R"(
2651             {
2652               "value": "0xCC",
2653               "mask": "0xF7"
2654             }
2655         )"_json;
2656         parseI2CWriteByte(element);
2657         ADD_FAILURE() << "Should not have reached this line.";
2658     }
2659     catch (const std::invalid_argument& e)
2660     {
2661         EXPECT_STREQ(e.what(), "Required property missing: register");
2662     }
2663 
2664     // Test where fails: Required value property not specified
2665     try
2666     {
2667         const json element = R"(
2668             {
2669               "register": "0x0A",
2670               "mask": "0xF7"
2671             }
2672         )"_json;
2673         parseI2CWriteByte(element);
2674         ADD_FAILURE() << "Should not have reached this line.";
2675     }
2676     catch (const std::invalid_argument& e)
2677     {
2678         EXPECT_STREQ(e.what(), "Required property missing: value");
2679     }
2680 }
2681 
2682 TEST(ConfigFileParserTests, ParseI2CWriteBytes)
2683 {
2684     // Test where works: Only required properties specified
2685     {
2686         const json element = R"(
2687             {
2688               "register": "0x0A",
2689               "values": [ "0xCC", "0xFF" ]
2690             }
2691         )"_json;
2692         std::unique_ptr<I2CWriteBytesAction> action =
2693             parseI2CWriteBytes(element);
2694         EXPECT_EQ(action->getRegister(), 0x0A);
2695         EXPECT_EQ(action->getValues().size(), 2);
2696         EXPECT_EQ(action->getValues()[0], 0xCC);
2697         EXPECT_EQ(action->getValues()[1], 0xFF);
2698         EXPECT_EQ(action->getMasks().size(), 0);
2699     }
2700 
2701     // Test where works: All properties specified
2702     {
2703         const json element = R"(
2704             {
2705               "register": "0x0A",
2706               "values": [ "0xCC", "0xFF" ],
2707               "masks":  [ "0x7F", "0x77" ]
2708             }
2709         )"_json;
2710         std::unique_ptr<I2CWriteBytesAction> action =
2711             parseI2CWriteBytes(element);
2712         EXPECT_EQ(action->getRegister(), 0x0A);
2713         EXPECT_EQ(action->getValues().size(), 2);
2714         EXPECT_EQ(action->getValues()[0], 0xCC);
2715         EXPECT_EQ(action->getValues()[1], 0xFF);
2716         EXPECT_EQ(action->getMasks().size(), 2);
2717         EXPECT_EQ(action->getMasks()[0], 0x7F);
2718         EXPECT_EQ(action->getMasks()[1], 0x77);
2719     }
2720 
2721     // Test where fails: Element is not an object
2722     try
2723     {
2724         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2725         parseI2CWriteBytes(element);
2726         ADD_FAILURE() << "Should not have reached this line.";
2727     }
2728     catch (const std::invalid_argument& e)
2729     {
2730         EXPECT_STREQ(e.what(), "Element is not an object");
2731     }
2732 
2733     // Test where fails: Invalid property specified
2734     try
2735     {
2736         const json element = R"(
2737             {
2738               "register": "0x0A",
2739               "values": [ "0xCC", "0xFF" ],
2740               "masks":  [ "0x7F", "0x7F" ],
2741               "foo": 1
2742             }
2743         )"_json;
2744         parseI2CWriteBytes(element);
2745         ADD_FAILURE() << "Should not have reached this line.";
2746     }
2747     catch (const std::invalid_argument& e)
2748     {
2749         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2750     }
2751 
2752     // Test where fails: register value is invalid
2753     try
2754     {
2755         const json element = R"(
2756             {
2757               "register": "0x0Z",
2758               "values": [ "0xCC", "0xFF" ],
2759               "masks":  [ "0x7F", "0x7F" ]
2760             }
2761         )"_json;
2762         parseI2CWriteBytes(element);
2763         ADD_FAILURE() << "Should not have reached this line.";
2764     }
2765     catch (const std::invalid_argument& e)
2766     {
2767         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2768     }
2769 
2770     // Test where fails: values value is invalid
2771     try
2772     {
2773         const json element = R"(
2774             {
2775               "register": "0x0A",
2776               "values": [ "0xCCC", "0xFF" ],
2777               "masks":  [ "0x7F", "0x7F" ]
2778             }
2779         )"_json;
2780         parseI2CWriteBytes(element);
2781         ADD_FAILURE() << "Should not have reached this line.";
2782     }
2783     catch (const std::invalid_argument& e)
2784     {
2785         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2786     }
2787 
2788     // Test where fails: masks value is invalid
2789     try
2790     {
2791         const json element = R"(
2792             {
2793               "register": "0x0A",
2794               "values": [ "0xCC", "0xFF" ],
2795               "masks":  [ "F", "0x7F" ]
2796             }
2797         )"_json;
2798         parseI2CWriteBytes(element);
2799         ADD_FAILURE() << "Should not have reached this line.";
2800     }
2801     catch (const std::invalid_argument& e)
2802     {
2803         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2804     }
2805 
2806     // Test where fails: number of elements in masks is invalid
2807     try
2808     {
2809         const json element = R"(
2810             {
2811               "register": "0x0A",
2812               "values": [ "0xCC", "0xFF" ],
2813               "masks":  [ "0x7F" ]
2814             }
2815         )"_json;
2816         parseI2CWriteBytes(element);
2817         ADD_FAILURE() << "Should not have reached this line.";
2818     }
2819     catch (const std::invalid_argument& e)
2820     {
2821         EXPECT_STREQ(e.what(), "Invalid number of elements in masks");
2822     }
2823 
2824     // Test where fails: Required register property not specified
2825     try
2826     {
2827         const json element = R"(
2828             {
2829               "values": [ "0xCC", "0xFF" ]
2830             }
2831         )"_json;
2832         parseI2CWriteBytes(element);
2833         ADD_FAILURE() << "Should not have reached this line.";
2834     }
2835     catch (const std::invalid_argument& e)
2836     {
2837         EXPECT_STREQ(e.what(), "Required property missing: register");
2838     }
2839 
2840     // Test where fails: Required values property not specified
2841     try
2842     {
2843         const json element = R"(
2844             {
2845               "register": "0x0A"
2846             }
2847         )"_json;
2848         parseI2CWriteBytes(element);
2849         ADD_FAILURE() << "Should not have reached this line.";
2850     }
2851     catch (const std::invalid_argument& e)
2852     {
2853         EXPECT_STREQ(e.what(), "Required property missing: values");
2854     }
2855 }
2856 
2857 TEST(ConfigFileParserTests, ParseIf)
2858 {
2859     // Test where works: Only required properties specified
2860     {
2861         const json element = R"(
2862             {
2863               "condition": { "run_rule": "is_downlevel_regulator" },
2864               "then": [ { "run_rule": "configure_downlevel_regulator" },
2865                         { "run_rule": "configure_standard_regulator" } ]
2866             }
2867         )"_json;
2868         std::unique_ptr<IfAction> action = parseIf(element);
2869         EXPECT_NE(action->getConditionAction().get(), nullptr);
2870         EXPECT_EQ(action->getThenActions().size(), 2);
2871         EXPECT_EQ(action->getElseActions().size(), 0);
2872     }
2873 
2874     // Test where works: All properties specified
2875     {
2876         const json element = R"(
2877             {
2878               "condition": { "run_rule": "is_downlevel_regulator" },
2879               "then": [ { "run_rule": "configure_downlevel_regulator" } ],
2880               "else": [ { "run_rule": "configure_standard_regulator" } ]
2881             }
2882         )"_json;
2883         std::unique_ptr<IfAction> action = parseIf(element);
2884         EXPECT_NE(action->getConditionAction().get(), nullptr);
2885         EXPECT_EQ(action->getThenActions().size(), 1);
2886         EXPECT_EQ(action->getElseActions().size(), 1);
2887     }
2888 
2889     // Test where fails: Required condition property not specified
2890     try
2891     {
2892         const json element = R"(
2893             {
2894               "then": [ { "run_rule": "configure_downlevel_regulator" } ],
2895               "else": [ { "run_rule": "configure_standard_regulator" } ]
2896             }
2897         )"_json;
2898         parseIf(element);
2899         ADD_FAILURE() << "Should not have reached this line.";
2900     }
2901     catch (const std::invalid_argument& e)
2902     {
2903         EXPECT_STREQ(e.what(), "Required property missing: condition");
2904     }
2905 
2906     // Test where fails: Required then property not specified
2907     try
2908     {
2909         const json element = R"(
2910             {
2911               "condition": { "run_rule": "is_downlevel_regulator" },
2912               "else": [ { "run_rule": "configure_standard_regulator" } ]
2913             }
2914         )"_json;
2915         parseIf(element);
2916         ADD_FAILURE() << "Should not have reached this line.";
2917     }
2918     catch (const std::invalid_argument& e)
2919     {
2920         EXPECT_STREQ(e.what(), "Required property missing: then");
2921     }
2922 
2923     // Test where fails: condition value is invalid
2924     try
2925     {
2926         const json element = R"(
2927             {
2928               "condition": 1,
2929               "then": [ { "run_rule": "configure_downlevel_regulator" } ],
2930               "else": [ { "run_rule": "configure_standard_regulator" } ]
2931             }
2932         )"_json;
2933         parseIf(element);
2934         ADD_FAILURE() << "Should not have reached this line.";
2935     }
2936     catch (const std::invalid_argument& e)
2937     {
2938         EXPECT_STREQ(e.what(), "Element is not an object");
2939     }
2940 
2941     // Test where fails: then value is invalid
2942     try
2943     {
2944         const json element = R"(
2945             {
2946               "condition": { "run_rule": "is_downlevel_regulator" },
2947               "then": "foo",
2948               "else": [ { "run_rule": "configure_standard_regulator" } ]
2949             }
2950         )"_json;
2951         parseIf(element);
2952         ADD_FAILURE() << "Should not have reached this line.";
2953     }
2954     catch (const std::invalid_argument& e)
2955     {
2956         EXPECT_STREQ(e.what(), "Element is not an array");
2957     }
2958 
2959     // Test where fails: else value is invalid
2960     try
2961     {
2962         const json element = R"(
2963             {
2964               "condition": { "run_rule": "is_downlevel_regulator" },
2965               "then": [ { "run_rule": "configure_downlevel_regulator" } ],
2966               "else": 1
2967             }
2968         )"_json;
2969         parseIf(element);
2970         ADD_FAILURE() << "Should not have reached this line.";
2971     }
2972     catch (const std::invalid_argument& e)
2973     {
2974         EXPECT_STREQ(e.what(), "Element is not an array");
2975     }
2976 
2977     // Test where fails: Invalid property specified
2978     try
2979     {
2980         const json element = R"(
2981             {
2982               "condition": { "run_rule": "is_downlevel_regulator" },
2983               "then": [ { "run_rule": "configure_downlevel_regulator" } ],
2984               "foo": "bar"
2985             }
2986         )"_json;
2987         parseIf(element);
2988         ADD_FAILURE() << "Should not have reached this line.";
2989     }
2990     catch (const std::invalid_argument& e)
2991     {
2992         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2993     }
2994 
2995     // Test where fails: Element is not an object
2996     try
2997     {
2998         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2999         parseIf(element);
3000         ADD_FAILURE() << "Should not have reached this line.";
3001     }
3002     catch (const std::invalid_argument& e)
3003     {
3004         EXPECT_STREQ(e.what(), "Element is not an object");
3005     }
3006 }
3007 
3008 TEST(ConfigFileParserTests, ParseInt8)
3009 {
3010     // Test where works: INT8_MIN
3011     {
3012         const json element = R"( -128 )"_json;
3013         int8_t value = parseInt8(element);
3014         EXPECT_EQ(value, -128);
3015     }
3016 
3017     // Test where works: INT8_MAX
3018     {
3019         const json element = R"( 127 )"_json;
3020         int8_t value = parseInt8(element);
3021         EXPECT_EQ(value, 127);
3022     }
3023 
3024     // Test where fails: Element is not an integer
3025     try
3026     {
3027         const json element = R"( 1.03 )"_json;
3028         parseInt8(element);
3029         ADD_FAILURE() << "Should not have reached this line.";
3030     }
3031     catch (const std::invalid_argument& e)
3032     {
3033         EXPECT_STREQ(e.what(), "Element is not an integer");
3034     }
3035 
3036     // Test where fails: Value < INT8_MIN
3037     try
3038     {
3039         const json element = R"( -129 )"_json;
3040         parseInt8(element);
3041         ADD_FAILURE() << "Should not have reached this line.";
3042     }
3043     catch (const std::invalid_argument& e)
3044     {
3045         EXPECT_STREQ(e.what(), "Element is not an 8-bit signed integer");
3046     }
3047 
3048     // Test where fails: Value > INT8_MAX
3049     try
3050     {
3051         const json element = R"( 128 )"_json;
3052         parseInt8(element);
3053         ADD_FAILURE() << "Should not have reached this line.";
3054     }
3055     catch (const std::invalid_argument& e)
3056     {
3057         EXPECT_STREQ(e.what(), "Element is not an 8-bit signed integer");
3058     }
3059 }
3060 
3061 TEST(ConfigFileParserTests, ParseInventoryPath)
3062 {
3063     // Test where works: Inventory path has a leading '/'
3064     {
3065         const json element = "/system/chassis/motherboard/cpu3";
3066         std::string value = parseInventoryPath(element);
3067         EXPECT_EQ(
3068             value,
3069             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu3");
3070     }
3071 
3072     // Test where works: Inventory path does not have a leading '/'
3073     {
3074         const json element = "system/chassis/motherboard/cpu1";
3075         std::string value = parseInventoryPath(element);
3076         EXPECT_EQ(
3077             value,
3078             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1");
3079     }
3080 
3081     // Test where fails: JSON element is not a string
3082     try
3083     {
3084         const json element = R"( { "foo": "bar" } )"_json;
3085         parseInventoryPath(element);
3086         ADD_FAILURE() << "Should not have reached this line.";
3087     }
3088     catch (const std::invalid_argument& e)
3089     {
3090         EXPECT_STREQ(e.what(), "Element is not a string");
3091     }
3092 
3093     // Test where fails: JSON element contains an empty string
3094     try
3095     {
3096         const json element = "";
3097         parseInventoryPath(element);
3098         ADD_FAILURE() << "Should not have reached this line.";
3099     }
3100     catch (const std::invalid_argument& e)
3101     {
3102         EXPECT_STREQ(e.what(), "Element contains an empty string");
3103     }
3104 }
3105 
3106 TEST(ConfigFileParserTests, ParseNot)
3107 {
3108     // Test where works
3109     {
3110         const json element = R"(
3111             { "i2c_compare_byte": { "register": "0xA0", "value": "0x00" } }
3112         )"_json;
3113         std::unique_ptr<NotAction> action = parseNot(element);
3114         EXPECT_NE(action->getAction().get(), nullptr);
3115     }
3116 
3117     // Test where fails: Element is not an object
3118     try
3119     {
3120         const json element = R"( [ "0xFF", "0x01" ] )"_json;
3121         parseNot(element);
3122         ADD_FAILURE() << "Should not have reached this line.";
3123     }
3124     catch (const std::invalid_argument& e)
3125     {
3126         EXPECT_STREQ(e.what(), "Element is not an object");
3127     }
3128 }
3129 
3130 TEST(ConfigFileParserTests, ParseOr)
3131 {
3132     // Test where works: Element is an array with 2 actions
3133     {
3134         const json element = R"(
3135             [
3136               { "i2c_compare_byte": { "register": "0xA0", "value": "0x00" } },
3137               { "i2c_compare_byte": { "register": "0xA1", "value": "0x00" } }
3138             ]
3139         )"_json;
3140         std::unique_ptr<OrAction> action = parseOr(element);
3141         EXPECT_EQ(action->getActions().size(), 2);
3142     }
3143 
3144     // Test where fails: Element is an array with 1 action
3145     try
3146     {
3147         const json element = R"(
3148             [
3149               { "i2c_compare_byte": { "register": "0xA0", "value": "0x00" } }
3150             ]
3151         )"_json;
3152         parseOr(element);
3153         ADD_FAILURE() << "Should not have reached this line.";
3154     }
3155     catch (const std::invalid_argument& e)
3156     {
3157         EXPECT_STREQ(e.what(), "Array must contain two or more actions");
3158     }
3159 
3160     // Test where fails: Element is not an array
3161     try
3162     {
3163         const json element = R"(
3164             {
3165               "foo": "bar"
3166             }
3167         )"_json;
3168         parseOr(element);
3169         ADD_FAILURE() << "Should not have reached this line.";
3170     }
3171     catch (const std::invalid_argument& e)
3172     {
3173         EXPECT_STREQ(e.what(), "Element is not an array");
3174     }
3175 }
3176 
3177 TEST(ConfigFileParserTests, ParsePMBusReadSensor)
3178 {
3179     // Test where works: Only required properties specified
3180     {
3181         const json element = R"(
3182             {
3183               "type": "iout",
3184               "command": "0x8C",
3185               "format": "linear_11"
3186             }
3187         )"_json;
3188         std::unique_ptr<PMBusReadSensorAction> action =
3189             parsePMBusReadSensor(element);
3190         EXPECT_EQ(action->getType(), pmbus_utils::SensorValueType::iout);
3191         EXPECT_EQ(action->getCommand(), 0x8C);
3192         EXPECT_EQ(action->getFormat(),
3193                   pmbus_utils::SensorDataFormat::linear_11);
3194         EXPECT_EQ(action->getExponent().has_value(), false);
3195     }
3196 
3197     // Test where works: All properties specified
3198     {
3199         const json element = R"(
3200             {
3201               "type": "temperature",
3202               "command": "0x7A",
3203               "format": "linear_16",
3204               "exponent": -8
3205             }
3206         )"_json;
3207         std::unique_ptr<PMBusReadSensorAction> action =
3208             parsePMBusReadSensor(element);
3209         EXPECT_EQ(action->getType(), pmbus_utils::SensorValueType::temperature);
3210         EXPECT_EQ(action->getCommand(), 0x7A);
3211         EXPECT_EQ(action->getFormat(),
3212                   pmbus_utils::SensorDataFormat::linear_16);
3213         EXPECT_EQ(action->getExponent().has_value(), true);
3214         EXPECT_EQ(action->getExponent().value(), -8);
3215     }
3216 
3217     // Test where fails: Element is not an object
3218     try
3219     {
3220         const json element = R"( [ "0xFF", "0x01" ] )"_json;
3221         parsePMBusReadSensor(element);
3222         ADD_FAILURE() << "Should not have reached this line.";
3223     }
3224     catch (const std::invalid_argument& e)
3225     {
3226         EXPECT_STREQ(e.what(), "Element is not an object");
3227     }
3228 
3229     // Test where fails: Invalid property specified
3230     try
3231     {
3232         const json element = R"(
3233             {
3234               "type": "iout",
3235               "command": "0x8C",
3236               "format": "linear_11",
3237               "foo": 1
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(), "Element contains an invalid property");
3246     }
3247 
3248     // Test where fails: Required type property not specified
3249     try
3250     {
3251         const json element = R"(
3252             {
3253               "command": "0x8C",
3254               "format": "linear_11"
3255             }
3256         )"_json;
3257         parsePMBusReadSensor(element);
3258         ADD_FAILURE() << "Should not have reached this line.";
3259     }
3260     catch (const std::invalid_argument& e)
3261     {
3262         EXPECT_STREQ(e.what(), "Required property missing: type");
3263     }
3264 
3265     // Test where fails: Required command property not specified
3266     try
3267     {
3268         const json element = R"(
3269             {
3270               "type": "iout",
3271               "format": "linear_11"
3272             }
3273         )"_json;
3274         parsePMBusReadSensor(element);
3275         ADD_FAILURE() << "Should not have reached this line.";
3276     }
3277     catch (const std::invalid_argument& e)
3278     {
3279         EXPECT_STREQ(e.what(), "Required property missing: command");
3280     }
3281 
3282     // Test where fails: Required format property not specified
3283     try
3284     {
3285         const json element = R"(
3286             {
3287               "type": "iout",
3288               "command": "0x8C"
3289             }
3290         )"_json;
3291         parsePMBusReadSensor(element);
3292         ADD_FAILURE() << "Should not have reached this line.";
3293     }
3294     catch (const std::invalid_argument& e)
3295     {
3296         EXPECT_STREQ(e.what(), "Required property missing: format");
3297     }
3298 
3299     // Test where fails: type value is invalid
3300     try
3301     {
3302         const json element = R"(
3303             {
3304               "type": 1,
3305               "command": "0x7A",
3306               "format": "linear_16"
3307             }
3308         )"_json;
3309         parsePMBusReadSensor(element);
3310         ADD_FAILURE() << "Should not have reached this line.";
3311     }
3312     catch (const std::invalid_argument& e)
3313     {
3314         EXPECT_STREQ(e.what(), "Element is not a string");
3315     }
3316 
3317     // Test where fails: command value is invalid
3318     try
3319     {
3320         const json element = R"(
3321             {
3322               "type": "temperature",
3323               "command": 0,
3324               "format": "linear_16"
3325             }
3326         )"_json;
3327         parsePMBusReadSensor(element);
3328         ADD_FAILURE() << "Should not have reached this line.";
3329     }
3330     catch (const std::invalid_argument& e)
3331     {
3332         EXPECT_STREQ(e.what(), "Element is not a string");
3333     }
3334 
3335     // Test where fails: format value is invalid
3336     try
3337     {
3338         const json element = R"(
3339             {
3340               "type": "temperature",
3341               "command": "0x7A",
3342               "format": 1
3343             }
3344         )"_json;
3345         parsePMBusReadSensor(element);
3346         ADD_FAILURE() << "Should not have reached this line.";
3347     }
3348     catch (const std::invalid_argument& e)
3349     {
3350         EXPECT_STREQ(e.what(), "Element is not a string");
3351     }
3352 
3353     // Test where fails: exponent value is invalid
3354     try
3355     {
3356         const json element = R"(
3357             {
3358               "type": "temperature",
3359               "command": "0x7A",
3360               "format": "linear_16",
3361               "exponent": 1.3
3362             }
3363         )"_json;
3364         parsePMBusReadSensor(element);
3365         ADD_FAILURE() << "Should not have reached this line.";
3366     }
3367     catch (const std::invalid_argument& e)
3368     {
3369         EXPECT_STREQ(e.what(), "Element is not an integer");
3370     }
3371 }
3372 
3373 TEST(ConfigFileParserTests, ParsePMBusWriteVoutCommand)
3374 {
3375     // Test where works: Only required properties specified
3376     {
3377         const json element = R"(
3378             {
3379               "format": "linear"
3380             }
3381         )"_json;
3382         std::unique_ptr<PMBusWriteVoutCommandAction> action =
3383             parsePMBusWriteVoutCommand(element);
3384         EXPECT_EQ(action->getVolts().has_value(), false);
3385         EXPECT_EQ(action->getFormat(), pmbus_utils::VoutDataFormat::linear);
3386         EXPECT_EQ(action->getExponent().has_value(), false);
3387         EXPECT_EQ(action->isVerified(), false);
3388     }
3389 
3390     // Test where works: All properties specified
3391     {
3392         const json element = R"(
3393             {
3394               "volts": 1.03,
3395               "format": "linear",
3396               "exponent": -8,
3397               "is_verified": true
3398             }
3399         )"_json;
3400         std::unique_ptr<PMBusWriteVoutCommandAction> action =
3401             parsePMBusWriteVoutCommand(element);
3402         EXPECT_EQ(action->getVolts().has_value(), true);
3403         EXPECT_EQ(action->getVolts().value(), 1.03);
3404         EXPECT_EQ(action->getFormat(), pmbus_utils::VoutDataFormat::linear);
3405         EXPECT_EQ(action->getExponent().has_value(), true);
3406         EXPECT_EQ(action->getExponent().value(), -8);
3407         EXPECT_EQ(action->isVerified(), true);
3408     }
3409 
3410     // Test where fails: Element is not an object
3411     try
3412     {
3413         const json element = R"( [ "0xFF", "0x01" ] )"_json;
3414         parsePMBusWriteVoutCommand(element);
3415         ADD_FAILURE() << "Should not have reached this line.";
3416     }
3417     catch (const std::invalid_argument& e)
3418     {
3419         EXPECT_STREQ(e.what(), "Element is not an object");
3420     }
3421 
3422     // Test where fails: volts value is invalid
3423     try
3424     {
3425         const json element = R"(
3426             {
3427               "volts": "foo",
3428               "format": "linear"
3429             }
3430         )"_json;
3431         parsePMBusWriteVoutCommand(element);
3432         ADD_FAILURE() << "Should not have reached this line.";
3433     }
3434     catch (const std::invalid_argument& e)
3435     {
3436         EXPECT_STREQ(e.what(), "Element is not a number");
3437     }
3438 
3439     // Test where fails: Required format property not specified
3440     try
3441     {
3442         const json element = R"(
3443             {
3444               "volts": 1.03,
3445               "is_verified": true
3446             }
3447         )"_json;
3448         parsePMBusWriteVoutCommand(element);
3449         ADD_FAILURE() << "Should not have reached this line.";
3450     }
3451     catch (const std::invalid_argument& e)
3452     {
3453         EXPECT_STREQ(e.what(), "Required property missing: format");
3454     }
3455 
3456     // Test where fails: format value is invalid
3457     try
3458     {
3459         const json element = R"(
3460             {
3461               "format": "linear_11"
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(), "Invalid format value: linear_11");
3470     }
3471 
3472     // Test where fails: exponent value is invalid
3473     try
3474     {
3475         const json element = R"(
3476             {
3477               "format": "linear",
3478               "exponent": 1.3
3479             }
3480         )"_json;
3481         parsePMBusWriteVoutCommand(element);
3482         ADD_FAILURE() << "Should not have reached this line.";
3483     }
3484     catch (const std::invalid_argument& e)
3485     {
3486         EXPECT_STREQ(e.what(), "Element is not an integer");
3487     }
3488 
3489     // Test where fails: is_verified value is invalid
3490     try
3491     {
3492         const json element = R"(
3493             {
3494               "format": "linear",
3495               "is_verified": "true"
3496             }
3497         )"_json;
3498         parsePMBusWriteVoutCommand(element);
3499         ADD_FAILURE() << "Should not have reached this line.";
3500     }
3501     catch (const std::invalid_argument& e)
3502     {
3503         EXPECT_STREQ(e.what(), "Element is not a boolean");
3504     }
3505 
3506     // Test where fails: Invalid property specified
3507     try
3508     {
3509         const json element = R"(
3510             {
3511               "format": "linear",
3512               "foo": "bar"
3513             }
3514         )"_json;
3515         parsePMBusWriteVoutCommand(element);
3516         ADD_FAILURE() << "Should not have reached this line.";
3517     }
3518     catch (const std::invalid_argument& e)
3519     {
3520         EXPECT_STREQ(e.what(), "Element contains an invalid property");
3521     }
3522 }
3523 
3524 TEST(ConfigFileParserTests, ParsePresenceDetection)
3525 {
3526     // Test where works: actions property specified
3527     {
3528         const json element = R"(
3529             {
3530               "actions": [
3531                 { "run_rule": "read_sensors_rule" }
3532               ]
3533             }
3534         )"_json;
3535         std::unique_ptr<PresenceDetection> presenceDetection =
3536             parsePresenceDetection(element);
3537         EXPECT_EQ(presenceDetection->getActions().size(), 1);
3538     }
3539 
3540     // Test where works: rule_id property specified
3541     {
3542         const json element = R"(
3543             {
3544               "comments": [ "comments property" ],
3545               "rule_id": "set_voltage_rule"
3546             }
3547         )"_json;
3548         std::unique_ptr<PresenceDetection> presenceDetection =
3549             parsePresenceDetection(element);
3550         EXPECT_EQ(presenceDetection->getActions().size(), 1);
3551     }
3552 
3553     // Test where fails: actions object is invalid
3554     try
3555     {
3556         const json element = R"(
3557             {
3558               "actions": 1
3559             }
3560         )"_json;
3561         parsePresenceDetection(element);
3562         ADD_FAILURE() << "Should not have reached this line.";
3563     }
3564     catch (const std::invalid_argument& e)
3565     {
3566         EXPECT_STREQ(e.what(), "Element is not an array");
3567     }
3568 
3569     // Test where fails: rule_id value is invalid
3570     try
3571     {
3572         const json element = R"(
3573             {
3574               "rule_id": 1
3575             }
3576         )"_json;
3577         parsePresenceDetection(element);
3578         ADD_FAILURE() << "Should not have reached this line.";
3579     }
3580     catch (const std::invalid_argument& e)
3581     {
3582         EXPECT_STREQ(e.what(), "Element is not a string");
3583     }
3584 
3585     // Test where fails: Required actions or rule_id property not specified
3586     try
3587     {
3588         const json element = R"(
3589             {
3590               "comments": [ "comments property" ]
3591             }
3592         )"_json;
3593         parsePresenceDetection(element);
3594         ADD_FAILURE() << "Should not have reached this line.";
3595     }
3596     catch (const std::invalid_argument& e)
3597     {
3598         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
3599                                "either rule_id or actions");
3600     }
3601 
3602     // Test where fails: Required actions or rule_id property both specified
3603     try
3604     {
3605         const json element = R"(
3606             {
3607               "rule_id": "set_voltage_rule",
3608               "actions": [
3609                 { "run_rule": "read_sensors_rule" }
3610               ]
3611             }
3612         )"_json;
3613         parsePresenceDetection(element);
3614         ADD_FAILURE() << "Should not have reached this line.";
3615     }
3616     catch (const std::invalid_argument& e)
3617     {
3618         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
3619                                "either rule_id or actions");
3620     }
3621 
3622     // Test where fails: Element is not an object
3623     try
3624     {
3625         const json element = R"( [ "foo", "bar" ] )"_json;
3626         parsePresenceDetection(element);
3627         ADD_FAILURE() << "Should not have reached this line.";
3628     }
3629     catch (const std::invalid_argument& e)
3630     {
3631         EXPECT_STREQ(e.what(), "Element is not an object");
3632     }
3633 
3634     // Test where fails: Invalid property specified
3635     try
3636     {
3637         const json element = R"(
3638             {
3639               "foo": "bar",
3640               "actions": [
3641                 { "run_rule": "read_sensors_rule" }
3642               ]
3643             }
3644         )"_json;
3645         parsePresenceDetection(element);
3646         ADD_FAILURE() << "Should not have reached this line.";
3647     }
3648     catch (const std::invalid_argument& e)
3649     {
3650         EXPECT_STREQ(e.what(), "Element contains an invalid property");
3651     }
3652 }
3653 
3654 TEST(ConfigFileParserTests, ParseRail)
3655 {
3656     // Test where works: Only required properties specified
3657     {
3658         const json element = R"(
3659             {
3660               "id": "vdd"
3661             }
3662         )"_json;
3663         std::unique_ptr<Rail> rail = parseRail(element);
3664         EXPECT_EQ(rail->getID(), "vdd");
3665         EXPECT_EQ(rail->getConfiguration(), nullptr);
3666         EXPECT_EQ(rail->getSensorMonitoring(), nullptr);
3667     }
3668 
3669     // Test where works: All properties specified
3670     {
3671         const json element = R"(
3672             {
3673               "comments": [ "comments property" ],
3674               "id": "vdd",
3675               "configuration": {
3676                 "volts": 1.1,
3677                 "actions": [
3678                   {
3679                     "pmbus_write_vout_command": {
3680                       "format": "linear"
3681                     }
3682                   }
3683                 ]
3684               },
3685               "sensor_monitoring": {
3686                 "actions": [
3687                   { "run_rule": "read_sensors_rule" }
3688                 ]
3689               }
3690             }
3691         )"_json;
3692         std::unique_ptr<Rail> rail = parseRail(element);
3693         EXPECT_EQ(rail->getID(), "vdd");
3694         EXPECT_NE(rail->getConfiguration(), nullptr);
3695         EXPECT_NE(rail->getSensorMonitoring(), nullptr);
3696     }
3697 
3698     // Test where fails: id property not specified
3699     try
3700     {
3701         const json element = R"(
3702             {
3703               "configuration": {
3704                 "volts": 1.1,
3705                 "actions": [
3706                   {
3707                     "pmbus_write_vout_command": {
3708                       "format": "linear"
3709                     }
3710                   }
3711                 ]
3712               }
3713             }
3714         )"_json;
3715         parseRail(element);
3716         ADD_FAILURE() << "Should not have reached this line.";
3717     }
3718     catch (const std::invalid_argument& e)
3719     {
3720         EXPECT_STREQ(e.what(), "Required property missing: id");
3721     }
3722 
3723     // Test where fails: id property is invalid
3724     try
3725     {
3726         const json element = R"(
3727             {
3728               "id": "",
3729               "configuration": {
3730                 "volts": 1.1,
3731                 "actions": [
3732                   {
3733                     "pmbus_write_vout_command": {
3734                       "format": "linear"
3735                     }
3736                   }
3737                 ]
3738               }
3739             }
3740         )"_json;
3741         parseRail(element);
3742         ADD_FAILURE() << "Should not have reached this line.";
3743     }
3744     catch (const std::invalid_argument& e)
3745     {
3746         EXPECT_STREQ(e.what(), "Element contains an empty string");
3747     }
3748 
3749     // Test where fails: Element is not an object
3750     try
3751     {
3752         const json element = R"( [ "0xFF", "0x01" ] )"_json;
3753         parseRail(element);
3754         ADD_FAILURE() << "Should not have reached this line.";
3755     }
3756     catch (const std::invalid_argument& e)
3757     {
3758         EXPECT_STREQ(e.what(), "Element is not an object");
3759     }
3760 
3761     // Test where fails: configuration value is invalid
3762     try
3763     {
3764         const json element = R"(
3765             {
3766               "id": "vdd",
3767               "configuration": "config"
3768             }
3769         )"_json;
3770         parseRail(element);
3771         ADD_FAILURE() << "Should not have reached this line.";
3772     }
3773     catch (const std::invalid_argument& e)
3774     {
3775         EXPECT_STREQ(e.what(), "Element is not an object");
3776     }
3777 
3778     // Test where fails: sensor_monitoring value is invalid
3779     try
3780     {
3781         const json element = R"(
3782             {
3783               "comments": [ "comments property" ],
3784               "id": "vdd",
3785               "configuration": {
3786                 "volts": 1.1,
3787                 "actions": [
3788                   {
3789                     "pmbus_write_vout_command": {
3790                       "format": "linear"
3791                     }
3792                   }
3793                 ]
3794               },
3795               "sensor_monitoring": 1
3796             }
3797         )"_json;
3798         parseRail(element);
3799         ADD_FAILURE() << "Should not have reached this line.";
3800     }
3801     catch (const std::invalid_argument& e)
3802     {
3803         EXPECT_STREQ(e.what(), "Element is not an object");
3804     }
3805 
3806     // Test where fails: Invalid property specified
3807     try
3808     {
3809         const json element = R"(
3810             {
3811               "id": "vdd",
3812               "foo" : true
3813             }
3814         )"_json;
3815         parseRail(element);
3816         ADD_FAILURE() << "Should not have reached this line.";
3817     }
3818     catch (const std::invalid_argument& e)
3819     {
3820         EXPECT_STREQ(e.what(), "Element contains an invalid property");
3821     }
3822 }
3823 
3824 TEST(ConfigFileParserTests, ParseRailArray)
3825 {
3826     // Test where works
3827     {
3828         const json element = R"(
3829             [
3830               { "id": "vdd" },
3831               { "id": "vio" }
3832             ]
3833         )"_json;
3834         std::vector<std::unique_ptr<Rail>> rails = parseRailArray(element);
3835         EXPECT_EQ(rails.size(), 2);
3836         EXPECT_EQ(rails[0]->getID(), "vdd");
3837         EXPECT_EQ(rails[1]->getID(), "vio");
3838     }
3839 
3840     // Test where fails: Element is not an array
3841     try
3842     {
3843         const json element = R"(
3844             {
3845               "foo": "bar"
3846             }
3847         )"_json;
3848         parseRailArray(element);
3849         ADD_FAILURE() << "Should not have reached this line.";
3850     }
3851     catch (const std::invalid_argument& e)
3852     {
3853         EXPECT_STREQ(e.what(), "Element is not an array");
3854     }
3855 }
3856 
3857 TEST(ConfigFileParserTests, ParseRoot)
3858 {
3859     // Test where works: Only required properties specified
3860     {
3861         const json element = R"(
3862             {
3863               "chassis": [
3864                 { "number": 1 }
3865               ]
3866             }
3867         )"_json;
3868         std::vector<std::unique_ptr<Rule>> rules{};
3869         std::vector<std::unique_ptr<Chassis>> chassis{};
3870         std::tie(rules, chassis) = parseRoot(element);
3871         EXPECT_EQ(rules.size(), 0);
3872         EXPECT_EQ(chassis.size(), 1);
3873     }
3874 
3875     // Test where works: All properties specified
3876     {
3877         const json element = R"(
3878             {
3879               "comments": [ "Config file for a FooBar one-chassis system" ],
3880               "rules": [
3881                 {
3882                   "id": "set_voltage_rule",
3883                   "actions": [
3884                     { "pmbus_write_vout_command": { "format": "linear" } }
3885                   ]
3886                 }
3887               ],
3888               "chassis": [
3889                 { "number": 1 },
3890                 { "number": 3 }
3891               ]
3892             }
3893         )"_json;
3894         std::vector<std::unique_ptr<Rule>> rules{};
3895         std::vector<std::unique_ptr<Chassis>> chassis{};
3896         std::tie(rules, chassis) = parseRoot(element);
3897         EXPECT_EQ(rules.size(), 1);
3898         EXPECT_EQ(chassis.size(), 2);
3899     }
3900 
3901     // Test where fails: Element is not an object
3902     try
3903     {
3904         const json element = R"( [ "0xFF", "0x01" ] )"_json;
3905         parseRoot(element);
3906         ADD_FAILURE() << "Should not have reached this line.";
3907     }
3908     catch (const std::invalid_argument& e)
3909     {
3910         EXPECT_STREQ(e.what(), "Element is not an object");
3911     }
3912 
3913     // Test where fails: chassis property not specified
3914     try
3915     {
3916         const json element = R"(
3917             {
3918               "rules": [
3919                 {
3920                   "id": "set_voltage_rule",
3921                   "actions": [
3922                     { "pmbus_write_vout_command": { "format": "linear" } }
3923                   ]
3924                 }
3925               ]
3926             }
3927         )"_json;
3928         parseRoot(element);
3929         ADD_FAILURE() << "Should not have reached this line.";
3930     }
3931     catch (const std::invalid_argument& e)
3932     {
3933         EXPECT_STREQ(e.what(), "Required property missing: chassis");
3934     }
3935 
3936     // Test where fails: Invalid property specified
3937     try
3938     {
3939         const json element = R"(
3940             {
3941               "remarks": [ "Config file for a FooBar one-chassis system" ],
3942               "chassis": [
3943                 { "number": 1 }
3944               ]
3945             }
3946         )"_json;
3947         parseRoot(element);
3948         ADD_FAILURE() << "Should not have reached this line.";
3949     }
3950     catch (const std::invalid_argument& e)
3951     {
3952         EXPECT_STREQ(e.what(), "Element contains an invalid property");
3953     }
3954 }
3955 
3956 TEST(ConfigFileParserTests, ParseRule)
3957 {
3958     // Test where works: comments property specified
3959     {
3960         const json element = R"(
3961             {
3962               "comments": [ "Set voltage rule" ],
3963               "id": "set_voltage_rule",
3964               "actions": [
3965                 { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } },
3966                 { "pmbus_write_vout_command": { "volts": 1.03, "format": "linear" } }
3967               ]
3968             }
3969         )"_json;
3970         std::unique_ptr<Rule> rule = parseRule(element);
3971         EXPECT_EQ(rule->getID(), "set_voltage_rule");
3972         EXPECT_EQ(rule->getActions().size(), 2);
3973     }
3974 
3975     // Test where works: comments property not specified
3976     {
3977         const json element = R"(
3978             {
3979               "id": "set_voltage_rule",
3980               "actions": [
3981                 { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } },
3982                 { "pmbus_write_vout_command": { "volts": 1.03, "format": "linear" } },
3983                 { "pmbus_write_vout_command": { "volts": 1.05, "format": "linear" } }
3984               ]
3985             }
3986         )"_json;
3987         std::unique_ptr<Rule> rule = parseRule(element);
3988         EXPECT_EQ(rule->getID(), "set_voltage_rule");
3989         EXPECT_EQ(rule->getActions().size(), 3);
3990     }
3991 
3992     // Test where fails: Element is not an object
3993     try
3994     {
3995         const json element = R"( [ "0xFF", "0x01" ] )"_json;
3996         parseRule(element);
3997         ADD_FAILURE() << "Should not have reached this line.";
3998     }
3999     catch (const std::invalid_argument& e)
4000     {
4001         EXPECT_STREQ(e.what(), "Element is not an object");
4002     }
4003 
4004     // Test where fails: id property not specified
4005     try
4006     {
4007         const json element = R"(
4008             {
4009               "actions": [
4010                 { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
4011               ]
4012             }
4013         )"_json;
4014         parseRule(element);
4015         ADD_FAILURE() << "Should not have reached this line.";
4016     }
4017     catch (const std::invalid_argument& e)
4018     {
4019         EXPECT_STREQ(e.what(), "Required property missing: id");
4020     }
4021 
4022     // Test where fails: id property is invalid
4023     try
4024     {
4025         const json element = R"(
4026             {
4027               "id": "",
4028               "actions": [
4029                 { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
4030               ]
4031             }
4032         )"_json;
4033         parseRule(element);
4034         ADD_FAILURE() << "Should not have reached this line.";
4035     }
4036     catch (const std::invalid_argument& e)
4037     {
4038         EXPECT_STREQ(e.what(), "Element contains an empty string");
4039     }
4040 
4041     // Test where fails: actions property not specified
4042     try
4043     {
4044         const json element = R"(
4045             {
4046               "comments": [ "Set voltage rule" ],
4047               "id": "set_voltage_rule"
4048             }
4049         )"_json;
4050         parseRule(element);
4051         ADD_FAILURE() << "Should not have reached this line.";
4052     }
4053     catch (const std::invalid_argument& e)
4054     {
4055         EXPECT_STREQ(e.what(), "Required property missing: actions");
4056     }
4057 
4058     // Test where fails: actions property is invalid
4059     try
4060     {
4061         const json element = R"(
4062             {
4063               "id": "set_voltage_rule",
4064               "actions": true
4065             }
4066         )"_json;
4067         parseRule(element);
4068         ADD_FAILURE() << "Should not have reached this line.";
4069     }
4070     catch (const std::invalid_argument& e)
4071     {
4072         EXPECT_STREQ(e.what(), "Element is not an array");
4073     }
4074 
4075     // Test where fails: Invalid property specified
4076     try
4077     {
4078         const json element = R"(
4079             {
4080               "remarks": [ "Set voltage rule" ],
4081               "id": "set_voltage_rule",
4082               "actions": [
4083                 { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
4084               ]
4085             }
4086         )"_json;
4087         parseRule(element);
4088         ADD_FAILURE() << "Should not have reached this line.";
4089     }
4090     catch (const std::invalid_argument& e)
4091     {
4092         EXPECT_STREQ(e.what(), "Element contains an invalid property");
4093     }
4094 }
4095 
4096 TEST(ConfigFileParserTests, ParseRuleArray)
4097 {
4098     // Test where works
4099     {
4100         const json element = R"(
4101             [
4102               {
4103                 "id": "set_voltage_rule1",
4104                 "actions": [
4105                   { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
4106                 ]
4107               },
4108               {
4109                 "id": "set_voltage_rule2",
4110                 "actions": [
4111                   { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } },
4112                   { "pmbus_write_vout_command": { "volts": 1.11, "format": "linear" } }
4113                 ]
4114               }
4115             ]
4116         )"_json;
4117         std::vector<std::unique_ptr<Rule>> rules = parseRuleArray(element);
4118         EXPECT_EQ(rules.size(), 2);
4119         EXPECT_EQ(rules[0]->getID(), "set_voltage_rule1");
4120         EXPECT_EQ(rules[0]->getActions().size(), 1);
4121         EXPECT_EQ(rules[1]->getID(), "set_voltage_rule2");
4122         EXPECT_EQ(rules[1]->getActions().size(), 2);
4123     }
4124 
4125     // Test where fails: Element is not an array
4126     try
4127     {
4128         const json element = R"( { "id": "set_voltage_rule" } )"_json;
4129         parseRuleArray(element);
4130         ADD_FAILURE() << "Should not have reached this line.";
4131     }
4132     catch (const std::invalid_argument& e)
4133     {
4134         EXPECT_STREQ(e.what(), "Element is not an array");
4135     }
4136 }
4137 
4138 TEST(ConfigFileParserTests, ParseRuleIDOrActionsProperty)
4139 {
4140     // Test where works: actions specified
4141     {
4142         const json element = R"(
4143             {
4144               "actions": [
4145                 { "pmbus_write_vout_command": { "format": "linear" } },
4146                 { "run_rule": "set_voltage_rule" }
4147               ]
4148             }
4149         )"_json;
4150         std::vector<std::unique_ptr<Action>> actions =
4151             parseRuleIDOrActionsProperty(element);
4152         EXPECT_EQ(actions.size(), 2);
4153     }
4154 
4155     // Test where works: rule_id specified
4156     {
4157         const json element = R"(
4158             {
4159               "rule_id": "set_voltage_rule"
4160             }
4161         )"_json;
4162         std::vector<std::unique_ptr<Action>> actions =
4163             parseRuleIDOrActionsProperty(element);
4164         EXPECT_EQ(actions.size(), 1);
4165     }
4166 
4167     // Test where fails: Element is not an object
4168     try
4169     {
4170         const json element = R"( [ "foo", "bar" ] )"_json;
4171         parseRuleIDOrActionsProperty(element);
4172         ADD_FAILURE() << "Should not have reached this line.";
4173     }
4174     catch (const std::invalid_argument& e)
4175     {
4176         EXPECT_STREQ(e.what(), "Element is not an object");
4177     }
4178 
4179     // Test where fails: rule_id is invalid
4180     try
4181     {
4182         const json element = R"(
4183             { "rule_id": 1 }
4184         )"_json;
4185         parseRuleIDOrActionsProperty(element);
4186         ADD_FAILURE() << "Should not have reached this line.";
4187     }
4188     catch (const std::invalid_argument& e)
4189     {
4190         EXPECT_STREQ(e.what(), "Element is not a string");
4191     }
4192 
4193     // Test where fails: actions is invalid
4194     try
4195     {
4196         const json element = R"(
4197             { "actions": 1 }
4198         )"_json;
4199         parseRuleIDOrActionsProperty(element);
4200         ADD_FAILURE() << "Should not have reached this line.";
4201     }
4202     catch (const std::invalid_argument& e)
4203     {
4204         EXPECT_STREQ(e.what(), "Element is not an array");
4205     }
4206 
4207     // Test where fails: Neither rule_id nor actions specified
4208     try
4209     {
4210         const json element = R"(
4211             {
4212               "volts": 1.03
4213             }
4214         )"_json;
4215         parseRuleIDOrActionsProperty(element);
4216         ADD_FAILURE() << "Should not have reached this line.";
4217     }
4218     catch (const std::invalid_argument& e)
4219     {
4220         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
4221                                "either rule_id or actions");
4222     }
4223 
4224     // Test where fails: Both rule_id and actions specified
4225     try
4226     {
4227         const json element = R"(
4228             {
4229               "volts": 1.03,
4230               "rule_id": "set_voltage_rule",
4231               "actions": [
4232                 {
4233                   "pmbus_write_vout_command": {
4234                     "format": "linear"
4235                   }
4236                 }
4237               ]
4238             }
4239         )"_json;
4240         parseRuleIDOrActionsProperty(element);
4241         ADD_FAILURE() << "Should not have reached this line.";
4242     }
4243     catch (const std::invalid_argument& e)
4244     {
4245         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
4246                                "either rule_id or actions");
4247     }
4248 }
4249 
4250 TEST(ConfigFileParserTests, ParseRunRule)
4251 {
4252     // Test where works
4253     {
4254         const json element = "vdd_regulator";
4255         std::unique_ptr<RunRuleAction> action = parseRunRule(element);
4256         EXPECT_EQ(action->getRuleID(), "vdd_regulator");
4257     }
4258 
4259     // Test where fails: Element is not a string
4260     try
4261     {
4262         const json element = 1;
4263         parseRunRule(element);
4264         ADD_FAILURE() << "Should not have reached this line.";
4265     }
4266     catch (const std::invalid_argument& e)
4267     {
4268         EXPECT_STREQ(e.what(), "Element is not a string");
4269     }
4270 
4271     // Test where fails: Empty string
4272     try
4273     {
4274         const json element = "";
4275         parseRunRule(element);
4276         ADD_FAILURE() << "Should not have reached this line.";
4277     }
4278     catch (const std::invalid_argument& e)
4279     {
4280         EXPECT_STREQ(e.what(), "Element contains an empty string");
4281     }
4282 }
4283 
4284 TEST(ConfigFileParserTests, ParseSensorDataFormat)
4285 {
4286     // Test where works: linear_11
4287     {
4288         const json element = "linear_11";
4289         pmbus_utils::SensorDataFormat value = parseSensorDataFormat(element);
4290         pmbus_utils::SensorDataFormat format =
4291             pmbus_utils::SensorDataFormat::linear_11;
4292         EXPECT_EQ(value, format);
4293     }
4294 
4295     // Test where works: linear_16
4296     {
4297         const json element = "linear_16";
4298         pmbus_utils::SensorDataFormat value = parseSensorDataFormat(element);
4299         pmbus_utils::SensorDataFormat format =
4300             pmbus_utils::SensorDataFormat::linear_16;
4301         EXPECT_EQ(value, format);
4302     }
4303 
4304     // Test where fails: Element is not a sensor data format
4305     try
4306     {
4307         const json element = "foo";
4308         parseSensorDataFormat(element);
4309         ADD_FAILURE() << "Should not have reached this line.";
4310     }
4311     catch (const std::invalid_argument& e)
4312     {
4313         EXPECT_STREQ(e.what(), "Element is not a sensor data format");
4314     }
4315 
4316     // Test where fails: Element is not a string
4317     try
4318     {
4319         const json element = R"( { "foo": "bar" } )"_json;
4320         parseSensorDataFormat(element);
4321         ADD_FAILURE() << "Should not have reached this line.";
4322     }
4323     catch (const std::invalid_argument& e)
4324     {
4325         EXPECT_STREQ(e.what(), "Element is not a string");
4326     }
4327 }
4328 
4329 TEST(ConfigFileParserTests, ParseSensorMonitoring)
4330 {
4331     // Test where works: actions property specified
4332     {
4333         const json element = R"(
4334             {
4335               "actions": [
4336                 { "run_rule": "read_sensors_rule" }
4337               ]
4338             }
4339         )"_json;
4340         std::unique_ptr<SensorMonitoring> sensorMonitoring =
4341             parseSensorMonitoring(element);
4342         EXPECT_EQ(sensorMonitoring->getActions().size(), 1);
4343     }
4344 
4345     // Test where works: rule_id property specified
4346     {
4347         const json element = R"(
4348             {
4349               "comments": [ "comments property" ],
4350               "rule_id": "set_voltage_rule"
4351             }
4352         )"_json;
4353         std::unique_ptr<SensorMonitoring> sensorMonitoring =
4354             parseSensorMonitoring(element);
4355         EXPECT_EQ(sensorMonitoring->getActions().size(), 1);
4356     }
4357 
4358     // Test where fails: actions object is invalid
4359     try
4360     {
4361         const json element = R"(
4362             {
4363               "actions": 1
4364             }
4365         )"_json;
4366         parseSensorMonitoring(element);
4367         ADD_FAILURE() << "Should not have reached this line.";
4368     }
4369     catch (const std::invalid_argument& e)
4370     {
4371         EXPECT_STREQ(e.what(), "Element is not an array");
4372     }
4373 
4374     // Test where fails: rule_id value is invalid
4375     try
4376     {
4377         const json element = R"(
4378             {
4379               "rule_id": 1
4380             }
4381         )"_json;
4382         parseSensorMonitoring(element);
4383         ADD_FAILURE() << "Should not have reached this line.";
4384     }
4385     catch (const std::invalid_argument& e)
4386     {
4387         EXPECT_STREQ(e.what(), "Element is not a string");
4388     }
4389 
4390     // Test where fails: Required actions or rule_id property not specified
4391     try
4392     {
4393         const json element = R"(
4394             {
4395               "comments": [ "comments property" ]
4396             }
4397         )"_json;
4398         parseSensorMonitoring(element);
4399         ADD_FAILURE() << "Should not have reached this line.";
4400     }
4401     catch (const std::invalid_argument& e)
4402     {
4403         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
4404                                "either rule_id or actions");
4405     }
4406 
4407     // Test where fails: Required actions or rule_id property both specified
4408     try
4409     {
4410         const json element = R"(
4411             {
4412               "rule_id": "set_voltage_rule",
4413               "actions": [
4414                 { "run_rule": "read_sensors_rule" }
4415               ]
4416             }
4417         )"_json;
4418         parseSensorMonitoring(element);
4419         ADD_FAILURE() << "Should not have reached this line.";
4420     }
4421     catch (const std::invalid_argument& e)
4422     {
4423         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
4424                                "either rule_id or actions");
4425     }
4426 
4427     // Test where fails: Element is not an object
4428     try
4429     {
4430         const json element = R"( [ "foo", "bar" ] )"_json;
4431         parseSensorMonitoring(element);
4432         ADD_FAILURE() << "Should not have reached this line.";
4433     }
4434     catch (const std::invalid_argument& e)
4435     {
4436         EXPECT_STREQ(e.what(), "Element is not an object");
4437     }
4438 
4439     // Test where fails: Invalid property specified
4440     try
4441     {
4442         const json element = R"(
4443             {
4444               "foo": "bar",
4445               "actions": [
4446                 { "run_rule": "read_sensors_rule" }
4447               ]
4448             }
4449         )"_json;
4450         parseSensorMonitoring(element);
4451         ADD_FAILURE() << "Should not have reached this line.";
4452     }
4453     catch (const std::invalid_argument& e)
4454     {
4455         EXPECT_STREQ(e.what(), "Element contains an invalid property");
4456     }
4457 }
4458 
4459 TEST(ConfigFileParserTests, ParseSensorValueType)
4460 {
4461     // Test where works: iout
4462     {
4463         const json element = "iout";
4464         pmbus_utils::SensorValueType value = parseSensorValueType(element);
4465         pmbus_utils::SensorValueType type = pmbus_utils::SensorValueType::iout;
4466         EXPECT_EQ(value, type);
4467     }
4468 
4469     // Test where works: iout_peak
4470     {
4471         const json element = "iout_peak";
4472         pmbus_utils::SensorValueType value = parseSensorValueType(element);
4473         pmbus_utils::SensorValueType type =
4474             pmbus_utils::SensorValueType::iout_peak;
4475         EXPECT_EQ(value, type);
4476     }
4477 
4478     // Test where works: iout_valley
4479     {
4480         const json element = "iout_valley";
4481         pmbus_utils::SensorValueType value = parseSensorValueType(element);
4482         pmbus_utils::SensorValueType type =
4483             pmbus_utils::SensorValueType::iout_valley;
4484         EXPECT_EQ(value, type);
4485     }
4486 
4487     // Test where works: pout
4488     {
4489         const json element = "pout";
4490         pmbus_utils::SensorValueType value = parseSensorValueType(element);
4491         pmbus_utils::SensorValueType type = pmbus_utils::SensorValueType::pout;
4492         EXPECT_EQ(value, type);
4493     }
4494 
4495     // Test where works: temperature
4496     {
4497         const json element = "temperature";
4498         pmbus_utils::SensorValueType value = parseSensorValueType(element);
4499         pmbus_utils::SensorValueType type =
4500             pmbus_utils::SensorValueType::temperature;
4501         EXPECT_EQ(value, type);
4502     }
4503 
4504     // Test where works: temperature_peak
4505     {
4506         const json element = "temperature_peak";
4507         pmbus_utils::SensorValueType value = parseSensorValueType(element);
4508         pmbus_utils::SensorValueType type =
4509             pmbus_utils::SensorValueType::temperature_peak;
4510         EXPECT_EQ(value, type);
4511     }
4512 
4513     // Test where works: vout
4514     {
4515         const json element = "vout";
4516         pmbus_utils::SensorValueType value = parseSensorValueType(element);
4517         pmbus_utils::SensorValueType type = pmbus_utils::SensorValueType::vout;
4518         EXPECT_EQ(value, type);
4519     }
4520 
4521     // Test where works: vout_peak
4522     {
4523         const json element = "vout_peak";
4524         pmbus_utils::SensorValueType value = parseSensorValueType(element);
4525         pmbus_utils::SensorValueType type =
4526             pmbus_utils::SensorValueType::vout_peak;
4527         EXPECT_EQ(value, type);
4528     }
4529 
4530     // Test where works: vout_valley
4531     {
4532         const json element = "vout_valley";
4533         pmbus_utils::SensorValueType value = parseSensorValueType(element);
4534         pmbus_utils::SensorValueType type =
4535             pmbus_utils::SensorValueType::vout_valley;
4536         EXPECT_EQ(value, type);
4537     }
4538 
4539     // Test where fails: Element is not a sensor value type
4540     try
4541     {
4542         const json element = "foo";
4543         parseSensorValueType(element);
4544         ADD_FAILURE() << "Should not have reached this line.";
4545     }
4546     catch (const std::invalid_argument& e)
4547     {
4548         EXPECT_STREQ(e.what(), "Element is not a sensor value type");
4549     }
4550 
4551     // Test where fails: Element is not a string
4552     try
4553     {
4554         const json element = R"( { "foo": "bar" } )"_json;
4555         parseSensorValueType(element);
4556         ADD_FAILURE() << "Should not have reached this line.";
4557     }
4558     catch (const std::invalid_argument& e)
4559     {
4560         EXPECT_STREQ(e.what(), "Element is not a string");
4561     }
4562 }
4563 
4564 TEST(ConfigFileParserTests, ParseSetDevice)
4565 {
4566     // Test where works
4567     {
4568         const json element = "regulator1";
4569         std::unique_ptr<SetDeviceAction> action = parseSetDevice(element);
4570         EXPECT_EQ(action->getDeviceID(), "regulator1");
4571     }
4572 
4573     // Test where fails: Element is not a string
4574     try
4575     {
4576         const json element = 1;
4577         parseSetDevice(element);
4578         ADD_FAILURE() << "Should not have reached this line.";
4579     }
4580     catch (const std::invalid_argument& e)
4581     {
4582         EXPECT_STREQ(e.what(), "Element is not a string");
4583     }
4584 
4585     // Test where fails: Empty string
4586     try
4587     {
4588         const json element = "";
4589         parseSetDevice(element);
4590         ADD_FAILURE() << "Should not have reached this line.";
4591     }
4592     catch (const std::invalid_argument& e)
4593     {
4594         EXPECT_STREQ(e.what(), "Element contains an empty string");
4595     }
4596 }
4597 
4598 TEST(ConfigFileParserTests, ParseString)
4599 {
4600     // Test where works: Empty string
4601     {
4602         const json element = "";
4603         std::string value = parseString(element, true);
4604         EXPECT_EQ(value, "");
4605     }
4606 
4607     // Test where works: Non-empty string
4608     {
4609         const json element = "vdd_regulator";
4610         std::string value = parseString(element, false);
4611         EXPECT_EQ(value, "vdd_regulator");
4612     }
4613 
4614     // Test where fails: Element is not a string
4615     try
4616     {
4617         const json element = R"( { "foo": "bar" } )"_json;
4618         parseString(element);
4619         ADD_FAILURE() << "Should not have reached this line.";
4620     }
4621     catch (const std::invalid_argument& e)
4622     {
4623         EXPECT_STREQ(e.what(), "Element is not a string");
4624     }
4625 
4626     // Test where fails: Empty string
4627     try
4628     {
4629         const json element = "";
4630         parseString(element);
4631         ADD_FAILURE() << "Should not have reached this line.";
4632     }
4633     catch (const std::invalid_argument& e)
4634     {
4635         EXPECT_STREQ(e.what(), "Element contains an empty string");
4636     }
4637 }
4638 
4639 TEST(ConfigFileParserTests, ParseUint8)
4640 {
4641     // Test where works: 0
4642     {
4643         const json element = R"( 0 )"_json;
4644         uint8_t value = parseUint8(element);
4645         EXPECT_EQ(value, 0);
4646     }
4647 
4648     // Test where works: UINT8_MAX
4649     {
4650         const json element = R"( 255 )"_json;
4651         uint8_t value = parseUint8(element);
4652         EXPECT_EQ(value, 255);
4653     }
4654 
4655     // Test where fails: Element is not an integer
4656     try
4657     {
4658         const json element = R"( 1.03 )"_json;
4659         parseUint8(element);
4660         ADD_FAILURE() << "Should not have reached this line.";
4661     }
4662     catch (const std::invalid_argument& e)
4663     {
4664         EXPECT_STREQ(e.what(), "Element is not an integer");
4665     }
4666 
4667     // Test where fails: Value < 0
4668     try
4669     {
4670         const json element = R"( -1 )"_json;
4671         parseUint8(element);
4672         ADD_FAILURE() << "Should not have reached this line.";
4673     }
4674     catch (const std::invalid_argument& e)
4675     {
4676         EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
4677     }
4678 
4679     // Test where fails: Value > UINT8_MAX
4680     try
4681     {
4682         const json element = R"( 256 )"_json;
4683         parseUint8(element);
4684         ADD_FAILURE() << "Should not have reached this line.";
4685     }
4686     catch (const std::invalid_argument& e)
4687     {
4688         EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
4689     }
4690 }
4691 
4692 TEST(ConfigFileParserTests, ParseUnsignedInteger)
4693 {
4694     // Test where works: 1
4695     {
4696         const json element = R"( 1 )"_json;
4697         unsigned int value = parseUnsignedInteger(element);
4698         EXPECT_EQ(value, 1);
4699     }
4700 
4701     // Test where fails: Element is not an integer
4702     try
4703     {
4704         const json element = R"( 1.5 )"_json;
4705         parseUnsignedInteger(element);
4706         ADD_FAILURE() << "Should not have reached this line.";
4707     }
4708     catch (const std::invalid_argument& e)
4709     {
4710         EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
4711     }
4712 
4713     // Test where fails: Value < 0
4714     try
4715     {
4716         const json element = R"( -1 )"_json;
4717         parseUnsignedInteger(element);
4718         ADD_FAILURE() << "Should not have reached this line.";
4719     }
4720     catch (const std::invalid_argument& e)
4721     {
4722         EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
4723     }
4724 }
4725 
4726 TEST(ConfigFileParserTests, ParseVoutDataFormat)
4727 {
4728     // Test where works: linear
4729     {
4730         const json element = "linear";
4731         pmbus_utils::VoutDataFormat value = parseVoutDataFormat(element);
4732         pmbus_utils::VoutDataFormat format =
4733             pmbus_utils::VoutDataFormat::linear;
4734         EXPECT_EQ(value, format);
4735     }
4736 
4737     // Test where works: vid
4738     {
4739         const json element = "vid";
4740         pmbus_utils::VoutDataFormat value = parseVoutDataFormat(element);
4741         pmbus_utils::VoutDataFormat format = pmbus_utils::VoutDataFormat::vid;
4742         EXPECT_EQ(value, format);
4743     }
4744 
4745     // Test where works: direct
4746     {
4747         const json element = "direct";
4748         pmbus_utils::VoutDataFormat value = parseVoutDataFormat(element);
4749         pmbus_utils::VoutDataFormat format =
4750             pmbus_utils::VoutDataFormat::direct;
4751         EXPECT_EQ(value, format);
4752     }
4753 
4754     // Test where works: ieee
4755     {
4756         const json element = "ieee";
4757         pmbus_utils::VoutDataFormat value = parseVoutDataFormat(element);
4758         pmbus_utils::VoutDataFormat format = pmbus_utils::VoutDataFormat::ieee;
4759         EXPECT_EQ(value, format);
4760     }
4761 
4762     // Test where fails: Element is not a vout data format
4763     try
4764     {
4765         const json element = "foo";
4766         parseVoutDataFormat(element);
4767         ADD_FAILURE() << "Should not have reached this line.";
4768     }
4769     catch (const std::invalid_argument& e)
4770     {
4771         EXPECT_STREQ(e.what(), "Element is not a vout data format");
4772     }
4773 
4774     // Test where fails: Element is not a string
4775     try
4776     {
4777         const json element = R"( { "foo": "bar" } )"_json;
4778         parseVoutDataFormat(element);
4779         ADD_FAILURE() << "Should not have reached this line.";
4780     }
4781     catch (const std::invalid_argument& e)
4782     {
4783         EXPECT_STREQ(e.what(), "Element is not a string");
4784     }
4785 }
4786 
4787 TEST(ConfigFileParserTests, VerifyIsArray)
4788 {
4789     // Test where element is an array
4790     try
4791     {
4792         const json element = R"( [ "foo", "bar" ] )"_json;
4793         verifyIsArray(element);
4794     }
4795     catch (const std::exception& e)
4796     {
4797         ADD_FAILURE() << "Should not have caught exception.";
4798     }
4799 
4800     // Test where element is not an array
4801     try
4802     {
4803         const json element = R"( { "foo": "bar" } )"_json;
4804         verifyIsArray(element);
4805         ADD_FAILURE() << "Should not have reached this line.";
4806     }
4807     catch (const std::invalid_argument& e)
4808     {
4809         EXPECT_STREQ(e.what(), "Element is not an array");
4810     }
4811 }
4812 
4813 TEST(ConfigFileParserTests, VerifyIsObject)
4814 {
4815     // Test where element is an object
4816     try
4817     {
4818         const json element = R"( { "foo": "bar" } )"_json;
4819         verifyIsObject(element);
4820     }
4821     catch (const std::exception& e)
4822     {
4823         ADD_FAILURE() << "Should not have caught exception.";
4824     }
4825 
4826     // Test where element is not an object
4827     try
4828     {
4829         const json element = R"( [ "foo", "bar" ] )"_json;
4830         verifyIsObject(element);
4831         ADD_FAILURE() << "Should not have reached this line.";
4832     }
4833     catch (const std::invalid_argument& e)
4834     {
4835         EXPECT_STREQ(e.what(), "Element is not an object");
4836     }
4837 }
4838 
4839 TEST(ConfigFileParserTests, VerifyPropertyCount)
4840 {
4841     // Test where element has expected number of properties
4842     try
4843     {
4844         const json element = R"(
4845             {
4846               "comments": [ "Set voltage rule" ],
4847               "id": "set_voltage_rule"
4848             }
4849         )"_json;
4850         verifyPropertyCount(element, 2);
4851     }
4852     catch (const std::exception& e)
4853     {
4854         ADD_FAILURE() << "Should not have caught exception.";
4855     }
4856 
4857     // Test where element has unexpected number of properties
4858     try
4859     {
4860         const json element = R"(
4861             {
4862               "comments": [ "Set voltage rule" ],
4863               "id": "set_voltage_rule",
4864               "foo": 1.3
4865             }
4866         )"_json;
4867         verifyPropertyCount(element, 2);
4868         ADD_FAILURE() << "Should not have reached this line.";
4869     }
4870     catch (const std::invalid_argument& e)
4871     {
4872         EXPECT_STREQ(e.what(), "Element contains an invalid property");
4873     }
4874 }
4875