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