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