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