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 "chassis.hpp"
18 #include "config_file_parser.hpp"
19 #include "config_file_parser_error.hpp"
20 #include "configuration.hpp"
21 #include "device.hpp"
22 #include "i2c_interface.hpp"
23 #include "i2c_write_bit_action.hpp"
24 #include "i2c_write_byte_action.hpp"
25 #include "i2c_write_bytes_action.hpp"
26 #include "pmbus_utils.hpp"
27 #include "pmbus_write_vout_command_action.hpp"
28 #include "presence_detection.hpp"
29 #include "rail.hpp"
30 #include "rule.hpp"
31 #include "run_rule_action.hpp"
32 #include "sensor_monitoring.hpp"
33 #include "tmp_file.hpp"
34 
35 #include <sys/stat.h> // for chmod()
36 
37 #include <nlohmann/json.hpp>
38 
39 #include <cstdint>
40 #include <cstring>
41 #include <exception>
42 #include <filesystem>
43 #include <fstream>
44 #include <memory>
45 #include <optional>
46 #include <stdexcept>
47 #include <string>
48 #include <tuple>
49 #include <vector>
50 
51 #include <gtest/gtest.h>
52 
53 using namespace phosphor::power::regulators;
54 using namespace phosphor::power::regulators::config_file_parser;
55 using namespace phosphor::power::regulators::config_file_parser::internal;
56 using json = nlohmann::json;
57 
58 void writeConfigFile(const std::filesystem::path& pathName,
59                      const std::string& contents)
60 {
61     std::ofstream file{pathName};
62     file << contents;
63 }
64 
65 void writeConfigFile(const std::filesystem::path& pathName,
66                      const json& contents)
67 {
68     std::ofstream file{pathName};
69     file << contents;
70 }
71 
72 TEST(ConfigFileParserTests, Parse)
73 {
74     // Test where works
75     {
76         const json configFileContents = R"(
77             {
78               "rules": [
79                 {
80                   "id": "set_voltage_rule1",
81                   "actions": [
82                     { "pmbus_write_vout_command": { "volts": 1.03, "format": "linear" } }
83                   ]
84                 },
85                 {
86                   "id": "set_voltage_rule2",
87                   "actions": [
88                     { "pmbus_write_vout_command": { "volts": 1.33, "format": "linear" } }
89                   ]
90                 }
91               ],
92               "chassis": [
93                 { "number": 1 },
94                 { "number": 2 },
95                 { "number": 3 }
96               ]
97             }
98         )"_json;
99 
100         TmpFile configFile;
101         std::filesystem::path pathName{configFile.getName()};
102         writeConfigFile(pathName, configFileContents);
103 
104         std::vector<std::unique_ptr<Rule>> rules{};
105         std::vector<std::unique_ptr<Chassis>> chassis{};
106         std::tie(rules, chassis) = parse(pathName);
107 
108         EXPECT_EQ(rules.size(), 2);
109         EXPECT_EQ(rules[0]->getID(), "set_voltage_rule1");
110         EXPECT_EQ(rules[1]->getID(), "set_voltage_rule2");
111 
112         EXPECT_EQ(chassis.size(), 3);
113         EXPECT_EQ(chassis[0]->getNumber(), 1);
114         EXPECT_EQ(chassis[1]->getNumber(), 2);
115         EXPECT_EQ(chassis[2]->getNumber(), 3);
116     }
117 
118     // Test where fails: File does not exist
119     try
120     {
121         std::filesystem::path pathName{"/tmp/non_existent_file"};
122         parse(pathName);
123         ADD_FAILURE() << "Should not have reached this line.";
124     }
125     catch (const ConfigFileParserError& e)
126     {
127         // Expected exception; what() message will vary
128     }
129 
130     // Test where fails: File is not readable
131     try
132     {
133         const json configFileContents = R"(
134             {
135               "chassis": [ { "number": 1 } ]
136             }
137         )"_json;
138 
139         TmpFile configFile;
140         std::filesystem::path pathName{configFile.getName()};
141         writeConfigFile(pathName, configFileContents);
142 
143         chmod(pathName.c_str(), 0222);
144 
145         parse(pathName);
146         ADD_FAILURE() << "Should not have reached this line.";
147     }
148     catch (const ConfigFileParserError& e)
149     {
150         // Expected exception; what() message will vary
151     }
152 
153     // Test where fails: File is not valid JSON
154     try
155     {
156         const std::string configFileContents = "] foo [";
157 
158         TmpFile configFile;
159         std::filesystem::path pathName{configFile.getName()};
160         writeConfigFile(pathName, configFileContents);
161 
162         parse(pathName);
163         ADD_FAILURE() << "Should not have reached this line.";
164     }
165     catch (const ConfigFileParserError& e)
166     {
167         // Expected exception; what() message will vary
168     }
169 
170     // Test where fails: Error when parsing JSON elements
171     try
172     {
173         const json configFileContents = R"( { "foo": "bar" } )"_json;
174 
175         TmpFile configFile;
176         std::filesystem::path pathName{configFile.getName()};
177         writeConfigFile(pathName, configFileContents);
178 
179         parse(pathName);
180         ADD_FAILURE() << "Should not have reached this line.";
181     }
182     catch (const ConfigFileParserError& e)
183     {
184         // Expected exception; what() message will vary
185     }
186 }
187 
188 TEST(ConfigFileParserTests, GetRequiredProperty)
189 {
190     // Test where property exists
191     {
192         const json element = R"( { "format": "linear" } )"_json;
193         const json& propertyElement = getRequiredProperty(element, "format");
194         EXPECT_EQ(propertyElement.get<std::string>(), "linear");
195     }
196 
197     // Test where property does not exist
198     try
199     {
200         const json element = R"( { "volts": 1.03 } )"_json;
201         getRequiredProperty(element, "format");
202         ADD_FAILURE() << "Should not have reached this line.";
203     }
204     catch (const std::invalid_argument& e)
205     {
206         EXPECT_STREQ(e.what(), "Required property missing: format");
207     }
208 }
209 
210 TEST(ConfigFileParserTests, ParseAction)
211 {
212     // Test where works: comments property specified
213     {
214         const json element = R"(
215             {
216               "comments": [ "Set output voltage." ],
217               "pmbus_write_vout_command": {
218                 "format": "linear"
219               }
220             }
221         )"_json;
222         std::unique_ptr<Action> action = parseAction(element);
223         EXPECT_NE(action.get(), nullptr);
224     }
225 
226     // Test where works: comments property not specified
227     {
228         const json element = R"(
229             {
230               "pmbus_write_vout_command": {
231                 "format": "linear"
232               }
233             }
234         )"_json;
235         std::unique_ptr<Action> action = parseAction(element);
236         EXPECT_NE(action.get(), nullptr);
237     }
238 
239     // Test where works: and action type specified
240     // TODO: Not implemented yet
241 
242     // Test where works: compare_presence action type specified
243     // TODO: Not implemented yet
244 
245     // Test where works: compare_vpd action type specified
246     // TODO: Not implemented yet
247 
248     // Test where works: i2c_compare_bit action type specified
249     // TODO: Not implemented yet
250 
251     // Test where works: i2c_compare_byte action type specified
252     // TODO: Not implemented yet
253 
254     // Test where works: i2c_compare_bytes action type specified
255     // TODO: Not implemented yet
256 
257     // Test where works: i2c_write_bit action type specified
258     {
259         const json element = R"(
260             {
261               "i2c_write_bit": {
262                 "register": "0xA0",
263                 "position": 3,
264                 "value": 0
265               }
266             }
267         )"_json;
268         std::unique_ptr<Action> action = parseAction(element);
269         EXPECT_NE(action.get(), nullptr);
270     }
271 
272     // Test where works: i2c_write_byte action type specified
273     {
274         const json element = R"(
275             {
276               "i2c_write_byte": {
277                 "register": "0x0A",
278                 "value": "0xCC"
279               }
280             }
281         )"_json;
282         std::unique_ptr<Action> action = parseAction(element);
283         EXPECT_NE(action.get(), nullptr);
284     }
285 
286     // Test where works: i2c_write_bytes action type specified
287     {
288         const json element = R"(
289             {
290               "i2c_write_bytes": {
291                 "register": "0x0A",
292                 "values": [ "0xCC", "0xFF" ]
293               }
294             }
295         )"_json;
296         std::unique_ptr<Action> action = parseAction(element);
297         EXPECT_NE(action.get(), nullptr);
298     }
299 
300     // Test where works: if action type specified
301     // TODO: Not implemented yet
302 
303     // Test where works: not action type specified
304     // TODO: Not implemented yet
305 
306     // Test where works: or action type specified
307     // TODO: Not implemented yet
308 
309     // Test where works: pmbus_read_sensor action type specified
310     // TODO: Not implemented yet
311 
312     // Test where works: pmbus_write_vout_command action type specified
313     {
314         const json element = R"(
315             {
316               "pmbus_write_vout_command": {
317                 "format": "linear"
318               }
319             }
320         )"_json;
321         std::unique_ptr<Action> action = parseAction(element);
322         EXPECT_NE(action.get(), nullptr);
323     }
324 
325     // Test where works: run_rule action type specified
326     {
327         const json element = R"(
328             {
329               "run_rule": "set_voltage_rule"
330             }
331         )"_json;
332         std::unique_ptr<Action> action = parseAction(element);
333         EXPECT_NE(action.get(), nullptr);
334     }
335 
336     // Test where works: set_device action type specified
337     // TODO: Not implemented yet
338 
339     // Test where fails: Element is not an object
340     try
341     {
342         const json element = R"( [ "0xFF", "0x01" ] )"_json;
343         parseAction(element);
344         ADD_FAILURE() << "Should not have reached this line.";
345     }
346     catch (const std::invalid_argument& e)
347     {
348         EXPECT_STREQ(e.what(), "Element is not an object");
349     }
350 
351     // Test where fails: No action type specified
352     try
353     {
354         const json element = R"(
355             {
356               "comments": [ "Set output voltage." ]
357             }
358         )"_json;
359         parseAction(element);
360         ADD_FAILURE() << "Should not have reached this line.";
361     }
362     catch (const std::invalid_argument& e)
363     {
364         EXPECT_STREQ(e.what(), "Required action type property missing");
365     }
366 
367     // Test where fails: Multiple action types specified
368     try
369     {
370         const json element = R"(
371             {
372               "pmbus_write_vout_command": { "format": "linear" },
373               "run_rule": "set_voltage_rule"
374             }
375         )"_json;
376         parseAction(element);
377         ADD_FAILURE() << "Should not have reached this line.";
378     }
379     catch (const std::invalid_argument& e)
380     {
381         EXPECT_STREQ(e.what(), "Element contains an invalid property");
382     }
383 
384     // Test where fails: Invalid property specified
385     try
386     {
387         const json element = R"(
388             {
389               "remarks": [ "Set output voltage." ],
390               "pmbus_write_vout_command": {
391                 "format": "linear"
392               }
393             }
394         )"_json;
395         parseAction(element);
396         ADD_FAILURE() << "Should not have reached this line.";
397     }
398     catch (const std::invalid_argument& e)
399     {
400         EXPECT_STREQ(e.what(), "Element contains an invalid property");
401     }
402 }
403 
404 TEST(ConfigFileParserTests, ParseActionArray)
405 {
406     // Test where works
407     {
408         const json element = R"(
409             [
410               { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } },
411               { "pmbus_write_vout_command": { "volts": 1.03, "format": "linear" } }
412             ]
413         )"_json;
414         std::vector<std::unique_ptr<Action>> actions =
415             parseActionArray(element);
416         EXPECT_EQ(actions.size(), 2);
417     }
418 
419     // Test where fails: Element is not an array
420     try
421     {
422         const json element = R"(
423             {
424               "foo": "bar"
425             }
426         )"_json;
427         parseActionArray(element);
428         ADD_FAILURE() << "Should not have reached this line.";
429     }
430     catch (const std::invalid_argument& e)
431     {
432         EXPECT_STREQ(e.what(), "Element is not an array");
433     }
434 }
435 
436 TEST(ConfigFileParserTests, ParseBitPosition)
437 {
438     // Test where works: 0
439     {
440         const json element = R"( 0 )"_json;
441         uint8_t value = parseBitPosition(element);
442         EXPECT_EQ(value, 0);
443     }
444 
445     // Test where works: 7
446     {
447         const json element = R"( 7 )"_json;
448         uint8_t value = parseBitPosition(element);
449         EXPECT_EQ(value, 7);
450     }
451 
452     // Test where fails: Element is not an integer
453     try
454     {
455         const json element = R"( 1.03 )"_json;
456         parseBitPosition(element);
457         ADD_FAILURE() << "Should not have reached this line.";
458     }
459     catch (const std::invalid_argument& e)
460     {
461         EXPECT_STREQ(e.what(), "Element is not an integer");
462     }
463 
464     // Test where fails: Value < 0
465     try
466     {
467         const json element = R"( -1 )"_json;
468         parseBitPosition(element);
469         ADD_FAILURE() << "Should not have reached this line.";
470     }
471     catch (const std::invalid_argument& e)
472     {
473         EXPECT_STREQ(e.what(), "Element is not a bit position");
474     }
475 
476     // Test where fails: Value > 7
477     try
478     {
479         const json element = R"( 8 )"_json;
480         parseBitPosition(element);
481         ADD_FAILURE() << "Should not have reached this line.";
482     }
483     catch (const std::invalid_argument& e)
484     {
485         EXPECT_STREQ(e.what(), "Element is not a bit position");
486     }
487 }
488 
489 TEST(ConfigFileParserTests, ParseBitValue)
490 {
491     // Test where works: 0
492     {
493         const json element = R"( 0 )"_json;
494         uint8_t value = parseBitValue(element);
495         EXPECT_EQ(value, 0);
496     }
497 
498     // Test where works: 1
499     {
500         const json element = R"( 1 )"_json;
501         uint8_t value = parseBitValue(element);
502         EXPECT_EQ(value, 1);
503     }
504 
505     // Test where fails: Element is not an integer
506     try
507     {
508         const json element = R"( 0.5 )"_json;
509         parseBitValue(element);
510         ADD_FAILURE() << "Should not have reached this line.";
511     }
512     catch (const std::invalid_argument& e)
513     {
514         EXPECT_STREQ(e.what(), "Element is not an integer");
515     }
516 
517     // Test where fails: Value < 0
518     try
519     {
520         const json element = R"( -1 )"_json;
521         parseBitValue(element);
522         ADD_FAILURE() << "Should not have reached this line.";
523     }
524     catch (const std::invalid_argument& e)
525     {
526         EXPECT_STREQ(e.what(), "Element is not a bit value");
527     }
528 
529     // Test where fails: Value > 1
530     try
531     {
532         const json element = R"( 2 )"_json;
533         parseBitValue(element);
534         ADD_FAILURE() << "Should not have reached this line.";
535     }
536     catch (const std::invalid_argument& e)
537     {
538         EXPECT_STREQ(e.what(), "Element is not a bit value");
539     }
540 }
541 
542 TEST(ConfigFileParserTests, ParseBoolean)
543 {
544     // Test where works: true
545     {
546         const json element = R"( true )"_json;
547         bool value = parseBoolean(element);
548         EXPECT_EQ(value, true);
549     }
550 
551     // Test where works: false
552     {
553         const json element = R"( false )"_json;
554         bool value = parseBoolean(element);
555         EXPECT_EQ(value, false);
556     }
557 
558     // Test where fails: Element is not a boolean
559     try
560     {
561         const json element = R"( 1 )"_json;
562         parseBoolean(element);
563         ADD_FAILURE() << "Should not have reached this line.";
564     }
565     catch (const std::invalid_argument& e)
566     {
567         EXPECT_STREQ(e.what(), "Element is not a boolean");
568     }
569 }
570 
571 TEST(ConfigFileParserTests, ParseChassis)
572 {
573     // Test where works: Only required properties specified
574     {
575         const json element = R"(
576             {
577               "number": 1
578             }
579         )"_json;
580         std::unique_ptr<Chassis> chassis = parseChassis(element);
581         EXPECT_EQ(chassis->getNumber(), 1);
582         EXPECT_EQ(chassis->getDevices().size(), 0);
583     }
584 
585     // Test where works: All properties specified
586     {
587         const json element = R"(
588             {
589               "comments": [ "comments property" ],
590               "number": 2,
591               "devices": [
592                 {
593                   "id": "vdd_regulator",
594                   "is_regulator": true,
595                   "fru": "/system/chassis/motherboard/regulator2",
596                   "i2c_interface":
597                   {
598                       "bus": 1,
599                       "address": "0x70"
600                   }
601                 }
602               ]
603             }
604         )"_json;
605         std::unique_ptr<Chassis> chassis = parseChassis(element);
606         EXPECT_EQ(chassis->getNumber(), 2);
607         EXPECT_EQ(chassis->getDevices().size(), 1);
608         EXPECT_EQ(chassis->getDevices()[0]->getID(), "vdd_regulator");
609     }
610 
611     // Test where fails: number value is invalid
612     try
613     {
614         const json element = R"(
615             {
616               "number": 0.5
617             }
618         )"_json;
619         parseChassis(element);
620         ADD_FAILURE() << "Should not have reached this line.";
621     }
622     catch (const std::invalid_argument& e)
623     {
624         EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
625     }
626 
627     // Test where fails: Invalid property specified
628     try
629     {
630         const json element = R"(
631             {
632               "number": 1,
633               "foo": 2
634             }
635         )"_json;
636         parseChassis(element);
637         ADD_FAILURE() << "Should not have reached this line.";
638     }
639     catch (const std::invalid_argument& e)
640     {
641         EXPECT_STREQ(e.what(), "Element contains an invalid property");
642     }
643 
644     // Test where fails: Required number property not specified
645     try
646     {
647         const json element = R"(
648             {
649               "devices": [
650                 {
651                   "id": "vdd_regulator",
652                   "is_regulator": true,
653                   "fru": "/system/chassis/motherboard/regulator2",
654                   "i2c_interface":
655                   {
656                       "bus": 1,
657                       "address": "0x70"
658                   }
659                 }
660               ]
661             }
662         )"_json;
663         parseChassis(element);
664         ADD_FAILURE() << "Should not have reached this line.";
665     }
666     catch (const std::invalid_argument& e)
667     {
668         EXPECT_STREQ(e.what(), "Required property missing: number");
669     }
670 
671     // Test where fails: Element is not an object
672     try
673     {
674         const json element = R"( [ "0xFF", "0x01" ] )"_json;
675         parseChassis(element);
676         ADD_FAILURE() << "Should not have reached this line.";
677     }
678     catch (const std::invalid_argument& e)
679     {
680         EXPECT_STREQ(e.what(), "Element is not an object");
681     }
682 
683     // Test where fails: number value is < 1
684     try
685     {
686         const json element = R"(
687             {
688               "number": 0
689             }
690         )"_json;
691         parseChassis(element);
692         ADD_FAILURE() << "Should not have reached this line.";
693     }
694     catch (const std::invalid_argument& e)
695     {
696         EXPECT_STREQ(e.what(), "Invalid chassis number: Must be > 0");
697     }
698 
699     // Test where fails: devices value is invalid
700     try
701     {
702         const json element = R"(
703             {
704               "number": 1,
705               "devices": 2
706             }
707         )"_json;
708         parseChassis(element);
709         ADD_FAILURE() << "Should not have reached this line.";
710     }
711     catch (const std::invalid_argument& e)
712     {
713         EXPECT_STREQ(e.what(), "Element is not an array");
714     }
715 }
716 
717 TEST(ConfigFileParserTests, ParseChassisArray)
718 {
719     // Test where works
720     {
721         const json element = R"(
722             [
723               { "number": 1 },
724               { "number": 2 }
725             ]
726         )"_json;
727         std::vector<std::unique_ptr<Chassis>> chassis =
728             parseChassisArray(element);
729         EXPECT_EQ(chassis.size(), 2);
730         EXPECT_EQ(chassis[0]->getNumber(), 1);
731         EXPECT_EQ(chassis[1]->getNumber(), 2);
732     }
733 
734     // Test where fails: Element is not an array
735     try
736     {
737         const json element = R"(
738             {
739               "foo": "bar"
740             }
741         )"_json;
742         parseChassisArray(element);
743         ADD_FAILURE() << "Should not have reached this line.";
744     }
745     catch (const std::invalid_argument& e)
746     {
747         EXPECT_STREQ(e.what(), "Element is not an array");
748     }
749 }
750 
751 TEST(ConfigFileParserTests, ParseConfiguration)
752 {
753     // Test where works: actions required property specified
754     {
755         const json element = R"(
756             {
757               "actions": [
758                 {
759                   "pmbus_write_vout_command": {
760                     "format": "linear"
761                   }
762                 }
763               ]
764             }
765         )"_json;
766         std::unique_ptr<Configuration> configuration =
767             parseConfiguration(element);
768         EXPECT_EQ(configuration->getActions().size(), 1);
769         EXPECT_EQ(configuration->getVolts().has_value(), false);
770     }
771 
772     // Test where works: volts and actions properties specified
773     {
774         const json element = R"(
775             {
776               "comments": [ "comments property" ],
777               "volts": 1.03,
778               "actions": [
779                 { "pmbus_write_vout_command": { "format": "linear" } },
780                 { "run_rule": "set_voltage_rule" }
781               ]
782             }
783         )"_json;
784         std::unique_ptr<Configuration> configuration =
785             parseConfiguration(element);
786         EXPECT_EQ(configuration->getVolts().has_value(), true);
787         EXPECT_EQ(configuration->getVolts().value(), 1.03);
788         EXPECT_EQ(configuration->getActions().size(), 2);
789     }
790 
791     // Test where works: volts and rule_id properties specified
792     {
793         const json element = R"(
794             {
795               "volts": 1.05,
796               "rule_id": "set_voltage_rule"
797             }
798         )"_json;
799         std::unique_ptr<Configuration> configuration =
800             parseConfiguration(element);
801         EXPECT_EQ(configuration->getVolts().has_value(), true);
802         EXPECT_EQ(configuration->getVolts().value(), 1.05);
803         EXPECT_EQ(configuration->getActions().size(), 1);
804     }
805 
806     // Test where fails: volts value is invalid
807     try
808     {
809         const json element = R"(
810             {
811               "volts": "foo",
812               "actions": [
813                 {
814                   "pmbus_write_vout_command": {
815                     "format": "linear"
816                   }
817                 }
818               ]
819             }
820         )"_json;
821         parseConfiguration(element);
822         ADD_FAILURE() << "Should not have reached this line.";
823     }
824     catch (const std::invalid_argument& e)
825     {
826         EXPECT_STREQ(e.what(), "Element is not a number");
827     }
828 
829     // Test where fails: actions object is invalid
830     try
831     {
832         const json element = R"(
833             {
834               "volts": 1.03,
835               "actions": 1
836             }
837         )"_json;
838         parseConfiguration(element);
839         ADD_FAILURE() << "Should not have reached this line.";
840     }
841     catch (const std::invalid_argument& e)
842     {
843         EXPECT_STREQ(e.what(), "Element is not an array");
844     }
845 
846     // Test where fails: rule_id value is invalid
847     try
848     {
849         const json element = R"(
850             {
851               "volts": 1.05,
852               "rule_id": 1
853             }
854         )"_json;
855         parseConfiguration(element);
856         ADD_FAILURE() << "Should not have reached this line.";
857     }
858     catch (const std::invalid_argument& e)
859     {
860         EXPECT_STREQ(e.what(), "Element is not a string");
861     }
862 
863     // Test where fails: Required actions or rule_id property not specified
864     try
865     {
866         const json element = R"(
867             {
868               "volts": 1.03
869             }
870         )"_json;
871         parseConfiguration(element);
872         ADD_FAILURE() << "Should not have reached this line.";
873     }
874     catch (const std::invalid_argument& e)
875     {
876         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
877                                "either rule_id or actions");
878     }
879 
880     // Test where fails: Required actions or rule_id property both specified
881     try
882     {
883         const json element = R"(
884             {
885               "volts": 1.03,
886               "rule_id": "set_voltage_rule",
887               "actions": [
888                 {
889                   "pmbus_write_vout_command": {
890                     "format": "linear"
891                   }
892                 }
893               ]
894             }
895         )"_json;
896         parseConfiguration(element);
897         ADD_FAILURE() << "Should not have reached this line.";
898     }
899     catch (const std::invalid_argument& e)
900     {
901         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
902                                "either rule_id or actions");
903     }
904 
905     // Test where fails: Element is not an object
906     try
907     {
908         const json element = R"( [ "0xFF", "0x01" ] )"_json;
909         parseConfiguration(element);
910         ADD_FAILURE() << "Should not have reached this line.";
911     }
912     catch (const std::invalid_argument& e)
913     {
914         EXPECT_STREQ(e.what(), "Element is not an object");
915     }
916 
917     // Test where fails: Invalid property specified
918     try
919     {
920         const json element = R"(
921             {
922               "volts": 1.03,
923               "rule_id": "set_voltage_rule",
924               "foo": 1
925             }
926         )"_json;
927         parseConfiguration(element);
928         ADD_FAILURE() << "Should not have reached this line.";
929     }
930     catch (const std::invalid_argument& e)
931     {
932         EXPECT_STREQ(e.what(), "Element contains an invalid property");
933     }
934 }
935 
936 TEST(ConfigFileParserTests, ParseDevice)
937 {
938     // Test where works: Only required properties specified
939     {
940         const json element = R"(
941             {
942               "id": "vdd_regulator",
943               "is_regulator": true,
944               "fru": "/system/chassis/motherboard/regulator2",
945               "i2c_interface": { "bus": 1, "address": "0x70" }
946             }
947         )"_json;
948         std::unique_ptr<Device> device = parseDevice(element);
949         EXPECT_EQ(device->getID(), "vdd_regulator");
950         EXPECT_EQ(device->isRegulator(), true);
951         EXPECT_EQ(device->getFRU(), "/system/chassis/motherboard/regulator2");
952         EXPECT_NE(&(device->getI2CInterface()), nullptr);
953         EXPECT_EQ(device->getPresenceDetection(), nullptr);
954         EXPECT_EQ(device->getConfiguration(), nullptr);
955         EXPECT_EQ(device->getRails().size(), 0);
956     }
957 
958     // Test where works: All properties specified
959     {
960         // TODO : add presence_detection property
961         const json element = R"(
962             {
963               "id": "vdd_regulator",
964               "is_regulator": true,
965               "fru": "/system/chassis/motherboard/regulator2",
966               "i2c_interface":
967               {
968                   "bus": 1,
969                   "address": "0x70"
970               },
971               "configuration":
972               {
973                   "rule_id": "configure_ir35221_rule"
974               },
975               "rails":
976               [
977                 {
978                   "id": "vdd"
979                 }
980               ]
981             }
982         )"_json;
983         std::unique_ptr<Device> device = parseDevice(element);
984         EXPECT_EQ(device->getID(), "vdd_regulator");
985         EXPECT_EQ(device->isRegulator(), true);
986         EXPECT_EQ(device->getFRU(), "/system/chassis/motherboard/regulator2");
987         EXPECT_NE(&(device->getI2CInterface()), nullptr);
988         // EXPECT_NE(device->getPresenceDetection(), nullptr);
989         EXPECT_NE(device->getConfiguration(), nullptr);
990         EXPECT_EQ(device->getRails().size(), 1);
991     }
992 
993     // Test where fails: rails property exists and is_regulator is false
994     try
995     {
996         const json element = R"(
997             {
998               "id": "vdd_regulator",
999               "is_regulator": false,
1000               "fru": "/system/chassis/motherboard/regulator2",
1001               "i2c_interface":
1002               {
1003                   "bus": 1,
1004                   "address": "0x70"
1005               },
1006               "configuration":
1007               {
1008                   "rule_id": "configure_ir35221_rule"
1009               },
1010               "rails":
1011               [
1012                 {
1013                   "id": "vdd"
1014                 }
1015               ]
1016             }
1017         )"_json;
1018         parseDevice(element);
1019         ADD_FAILURE() << "Should not have reached this line.";
1020     }
1021     catch (const std::invalid_argument& e)
1022     {
1023         EXPECT_STREQ(e.what(),
1024                      "Invalid rails property when is_regulator is false");
1025     }
1026 
1027     // Test where fails: id value is invalid
1028     try
1029     {
1030         const json element = R"(
1031             {
1032               "id": 3,
1033               "is_regulator": true,
1034               "fru": "/system/chassis/motherboard/regulator2",
1035               "i2c_interface":
1036               {
1037                   "bus": 1,
1038                   "address": "0x70"
1039               }
1040             }
1041         )"_json;
1042         parseDevice(element);
1043         ADD_FAILURE() << "Should not have reached this line.";
1044     }
1045     catch (const std::invalid_argument& e)
1046     {
1047         EXPECT_STREQ(e.what(), "Element is not a string");
1048     }
1049 
1050     // Test where fails: is_regulator value is invalid
1051     try
1052     {
1053         const json element = R"(
1054             {
1055               "id": "vdd_regulator",
1056               "is_regulator": 3,
1057               "fru": "/system/chassis/motherboard/regulator2",
1058               "i2c_interface":
1059               {
1060                   "bus": 1,
1061                   "address": "0x70"
1062               }
1063             }
1064         )"_json;
1065         parseDevice(element);
1066         ADD_FAILURE() << "Should not have reached this line.";
1067     }
1068     catch (const std::invalid_argument& e)
1069     {
1070         EXPECT_STREQ(e.what(), "Element is not a boolean");
1071     }
1072 
1073     // Test where fails: fru value is invalid
1074     try
1075     {
1076         const json element = R"(
1077             {
1078               "id": "vdd_regulator",
1079               "is_regulator": true,
1080               "fru": 2,
1081               "i2c_interface":
1082               {
1083                   "bus": 1,
1084                   "address": "0x70"
1085               }
1086             }
1087         )"_json;
1088         parseDevice(element);
1089         ADD_FAILURE() << "Should not have reached this line.";
1090     }
1091     catch (const std::invalid_argument& e)
1092     {
1093         EXPECT_STREQ(e.what(), "Element is not a string");
1094     }
1095 
1096     // Test where fails: i2c_interface value is invalid
1097     try
1098     {
1099         const json element = R"(
1100             {
1101               "id": "vdd_regulator",
1102               "is_regulator": true,
1103               "fru": "/system/chassis/motherboard/regulator2",
1104               "i2c_interface": 3
1105             }
1106         )"_json;
1107         parseDevice(element);
1108         ADD_FAILURE() << "Should not have reached this line.";
1109     }
1110     catch (const std::invalid_argument& e)
1111     {
1112         EXPECT_STREQ(e.what(), "Element is not an object");
1113     }
1114 
1115     // Test where fails: Required id property not specified
1116     try
1117     {
1118         const json element = R"(
1119             {
1120               "is_regulator": true,
1121               "fru": "/system/chassis/motherboard/regulator2",
1122               "i2c_interface":
1123               {
1124                   "bus": 1,
1125                   "address": "0x70"
1126               }
1127             }
1128         )"_json;
1129         parseDevice(element);
1130         ADD_FAILURE() << "Should not have reached this line.";
1131     }
1132     catch (const std::invalid_argument& e)
1133     {
1134         EXPECT_STREQ(e.what(), "Required property missing: id");
1135     }
1136 
1137     // Test where fails: Required is_regulator property not specified
1138     try
1139     {
1140         const json element = R"(
1141             {
1142               "id": "vdd_regulator",
1143               "fru": "/system/chassis/motherboard/regulator2",
1144               "i2c_interface":
1145               {
1146                   "bus": 1,
1147                   "address": "0x70"
1148               }
1149             }
1150         )"_json;
1151         parseDevice(element);
1152         ADD_FAILURE() << "Should not have reached this line.";
1153     }
1154     catch (const std::invalid_argument& e)
1155     {
1156         EXPECT_STREQ(e.what(), "Required property missing: is_regulator");
1157     }
1158 
1159     // Test where fails: Required fru property not specified
1160     try
1161     {
1162         const json element = R"(
1163             {
1164               "id": "vdd_regulator",
1165               "is_regulator": true,
1166               "i2c_interface":
1167               {
1168                   "bus": 1,
1169                   "address": "0x70"
1170               }
1171             }
1172         )"_json;
1173         parseDevice(element);
1174         ADD_FAILURE() << "Should not have reached this line.";
1175     }
1176     catch (const std::invalid_argument& e)
1177     {
1178         EXPECT_STREQ(e.what(), "Required property missing: fru");
1179     }
1180 
1181     // Test where fails: Required i2c_interface property not specified
1182     try
1183     {
1184         const json element = R"(
1185             {
1186               "id": "vdd_regulator",
1187               "is_regulator": true,
1188               "fru": "/system/chassis/motherboard/regulator2"
1189             }
1190         )"_json;
1191         parseDevice(element);
1192         ADD_FAILURE() << "Should not have reached this line.";
1193     }
1194     catch (const std::invalid_argument& e)
1195     {
1196         EXPECT_STREQ(e.what(), "Required property missing: i2c_interface");
1197     }
1198 
1199     // Test where fails: Element is not an object
1200     try
1201     {
1202         const json element = R"( [ "0xFF", "0x01" ] )"_json;
1203         parseDevice(element);
1204         ADD_FAILURE() << "Should not have reached this line.";
1205     }
1206     catch (const std::invalid_argument& e)
1207     {
1208         EXPECT_STREQ(e.what(), "Element is not an object");
1209     }
1210 
1211     // Test where fails: Invalid property specified
1212     try
1213     {
1214         const json element = R"(
1215             {
1216               "id": "vdd_regulator",
1217               "is_regulator": true,
1218               "fru": "/system/chassis/motherboard/regulator2",
1219               "i2c_interface": { "bus": 1, "address": "0x70" },
1220               "foo" : true
1221             }
1222         )"_json;
1223         parseDevice(element);
1224         ADD_FAILURE() << "Should not have reached this line.";
1225     }
1226     catch (const std::invalid_argument& e)
1227     {
1228         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1229     }
1230 }
1231 
1232 TEST(ConfigFileParserTests, ParseDeviceArray)
1233 {
1234     // Test where works
1235     {
1236         const json element = R"(
1237             [
1238               {
1239                 "id": "vdd_regulator",
1240                 "is_regulator": true,
1241                 "fru": "/system/chassis/motherboard/regulator2",
1242                 "i2c_interface": { "bus": 1, "address": "0x70" }
1243               },
1244               {
1245                 "id": "vio_regulator",
1246                 "is_regulator": true,
1247                 "fru": "/system/chassis/motherboard/regulator2",
1248                 "i2c_interface": { "bus": 1, "address": "0x71" }
1249               }
1250             ]
1251         )"_json;
1252         std::vector<std::unique_ptr<Device>> devices =
1253             parseDeviceArray(element);
1254         EXPECT_EQ(devices.size(), 2);
1255         EXPECT_EQ(devices[0]->getID(), "vdd_regulator");
1256         EXPECT_EQ(devices[1]->getID(), "vio_regulator");
1257     }
1258 
1259     // Test where fails: Element is not an array
1260     try
1261     {
1262         const json element = R"(
1263             {
1264               "foo": "bar"
1265             }
1266         )"_json;
1267         parseDeviceArray(element);
1268         ADD_FAILURE() << "Should not have reached this line.";
1269     }
1270     catch (const std::invalid_argument& e)
1271     {
1272         EXPECT_STREQ(e.what(), "Element is not an array");
1273     }
1274 }
1275 
1276 TEST(ConfigFileParserTests, ParseDouble)
1277 {
1278     // Test where works: floating point value
1279     {
1280         const json element = R"( 1.03 )"_json;
1281         double value = parseDouble(element);
1282         EXPECT_EQ(value, 1.03);
1283     }
1284 
1285     // Test where works: integer value
1286     {
1287         const json element = R"( 24 )"_json;
1288         double value = parseDouble(element);
1289         EXPECT_EQ(value, 24.0);
1290     }
1291 
1292     // Test where fails: Element is not a number
1293     try
1294     {
1295         const json element = R"( true )"_json;
1296         parseDouble(element);
1297         ADD_FAILURE() << "Should not have reached this line.";
1298     }
1299     catch (const std::invalid_argument& e)
1300     {
1301         EXPECT_STREQ(e.what(), "Element is not a number");
1302     }
1303 }
1304 
1305 TEST(ConfigFileParserTests, ParseHexByte)
1306 {
1307     // Test where works: "0xFF"
1308     {
1309         const json element = R"( "0xFF" )"_json;
1310         uint8_t value = parseHexByte(element);
1311         EXPECT_EQ(value, 0xFF);
1312     }
1313 
1314     // Test where works: "0xff"
1315     {
1316         const json element = R"( "0xff" )"_json;
1317         uint8_t value = parseHexByte(element);
1318         EXPECT_EQ(value, 0xff);
1319     }
1320 
1321     // Test where works: "0xf"
1322     {
1323         const json element = R"( "0xf" )"_json;
1324         uint8_t value = parseHexByte(element);
1325         EXPECT_EQ(value, 0xf);
1326     }
1327 
1328     // Test where fails: "0xfff"
1329     try
1330     {
1331         const json element = R"( "0xfff" )"_json;
1332         parseHexByte(element);
1333         ADD_FAILURE() << "Should not have reached this line.";
1334     }
1335     catch (const std::invalid_argument& e)
1336     {
1337         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1338     }
1339 
1340     // Test where fails: "0xAG"
1341     try
1342     {
1343         const json element = R"( "0xAG" )"_json;
1344         parseHexByte(element);
1345         ADD_FAILURE() << "Should not have reached this line.";
1346     }
1347     catch (const std::invalid_argument& e)
1348     {
1349         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1350     }
1351 
1352     // Test where fails: "ff"
1353     try
1354     {
1355         const json element = R"( "ff" )"_json;
1356         parseHexByte(element);
1357         ADD_FAILURE() << "Should not have reached this line.";
1358     }
1359     catch (const std::invalid_argument& e)
1360     {
1361         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1362     }
1363 
1364     // Test where fails: ""
1365     try
1366     {
1367         const json element = "";
1368         parseHexByte(element);
1369         ADD_FAILURE() << "Should not have reached this line.";
1370     }
1371     catch (const std::invalid_argument& e)
1372     {
1373         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1374     }
1375 
1376     // Test where fails: "f"
1377     try
1378     {
1379         const json element = R"( "f" )"_json;
1380         parseHexByte(element);
1381         ADD_FAILURE() << "Should not have reached this line.";
1382     }
1383     catch (const std::invalid_argument& e)
1384     {
1385         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1386     }
1387 
1388     // Test where fails: "0x"
1389     try
1390     {
1391         const json element = R"( "0x" )"_json;
1392         parseHexByte(element);
1393         ADD_FAILURE() << "Should not have reached this line.";
1394     }
1395     catch (const std::invalid_argument& e)
1396     {
1397         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1398     }
1399 
1400     // Test where fails: "0Xff"
1401     try
1402     {
1403         const json element = R"( "0XFF" )"_json;
1404         parseHexByte(element);
1405         ADD_FAILURE() << "Should not have reached this line.";
1406     }
1407     catch (const std::invalid_argument& e)
1408     {
1409         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1410     }
1411 }
1412 
1413 TEST(ConfigFileParserTests, ParseHexByteArray)
1414 {
1415     // Test where works
1416     {
1417         const json element = R"( [ "0xCC", "0xFF" ] )"_json;
1418         std::vector<uint8_t> hexBytes = parseHexByteArray(element);
1419         std::vector<uint8_t> expected = {0xcc, 0xff};
1420         EXPECT_EQ(hexBytes, expected);
1421     }
1422 
1423     // Test where fails: Element is not an array
1424     try
1425     {
1426         const json element = 0;
1427         parseHexByteArray(element);
1428         ADD_FAILURE() << "Should not have reached this line.";
1429     }
1430     catch (const std::invalid_argument& e)
1431     {
1432         EXPECT_STREQ(e.what(), "Element is not an array");
1433     }
1434 }
1435 
1436 TEST(ConfigFileParserTests, ParseI2CWriteBit)
1437 {
1438     // Test where works
1439     {
1440         const json element = R"(
1441             {
1442               "register": "0xA0",
1443               "position": 3,
1444               "value": 0
1445             }
1446         )"_json;
1447         std::unique_ptr<I2CWriteBitAction> action = parseI2CWriteBit(element);
1448         EXPECT_EQ(action->getRegister(), 0xA0);
1449         EXPECT_EQ(action->getPosition(), 3);
1450         EXPECT_EQ(action->getValue(), 0);
1451     }
1452 
1453     // Test where fails: Invalid property specified
1454     try
1455     {
1456         const json element = R"(
1457             {
1458               "register": "0xA0",
1459               "position": 3,
1460               "value": 0,
1461               "foo": 3
1462             }
1463         )"_json;
1464         parseI2CWriteBit(element);
1465         ADD_FAILURE() << "Should not have reached this line.";
1466     }
1467     catch (const std::invalid_argument& e)
1468     {
1469         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1470     }
1471 
1472     // Test where fails: Element is not an object
1473     try
1474     {
1475         const json element = R"( [ "0xFF", "0x01" ] )"_json;
1476         parseI2CWriteBit(element);
1477         ADD_FAILURE() << "Should not have reached this line.";
1478     }
1479     catch (const std::invalid_argument& e)
1480     {
1481         EXPECT_STREQ(e.what(), "Element is not an object");
1482     }
1483 
1484     // Test where fails: register value is invalid
1485     try
1486     {
1487         const json element = R"(
1488             {
1489               "register": "0xAG",
1490               "position": 3,
1491               "value": 0
1492             }
1493         )"_json;
1494         parseI2CWriteBit(element);
1495         ADD_FAILURE() << "Should not have reached this line.";
1496     }
1497     catch (const std::invalid_argument& e)
1498     {
1499         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1500     }
1501 
1502     // Test where fails: position value is invalid
1503     try
1504     {
1505         const json element = R"(
1506                 {
1507                   "register": "0xA0",
1508                   "position": 8,
1509                   "value": 0
1510                 }
1511             )"_json;
1512         parseI2CWriteBit(element);
1513         ADD_FAILURE() << "Should not have reached this line.";
1514     }
1515     catch (const std::invalid_argument& e)
1516     {
1517         EXPECT_STREQ(e.what(), "Element is not a bit position");
1518     }
1519 
1520     // Test where fails: value value is invalid
1521     try
1522     {
1523         const json element = R"(
1524                 {
1525                   "register": "0xA0",
1526                   "position": 3,
1527                   "value": 2
1528                 }
1529             )"_json;
1530         parseI2CWriteBit(element);
1531         ADD_FAILURE() << "Should not have reached this line.";
1532     }
1533     catch (const std::invalid_argument& e)
1534     {
1535         EXPECT_STREQ(e.what(), "Element is not a bit value");
1536     }
1537 
1538     // Test where fails: Required register property not specified
1539     try
1540     {
1541         const json element = R"(
1542             {
1543               "position": 3,
1544               "value": 0
1545             }
1546         )"_json;
1547         parseI2CWriteBit(element);
1548         ADD_FAILURE() << "Should not have reached this line.";
1549     }
1550     catch (const std::invalid_argument& e)
1551     {
1552         EXPECT_STREQ(e.what(), "Required property missing: register");
1553     }
1554 
1555     // Test where fails: Required position property not specified
1556     try
1557     {
1558         const json element = R"(
1559             {
1560               "register": "0xA0",
1561               "value": 0
1562             }
1563         )"_json;
1564         parseI2CWriteBit(element);
1565         ADD_FAILURE() << "Should not have reached this line.";
1566     }
1567     catch (const std::invalid_argument& e)
1568     {
1569         EXPECT_STREQ(e.what(), "Required property missing: position");
1570     }
1571 
1572     // Test where fails: Required value property not specified
1573     try
1574     {
1575         const json element = R"(
1576             {
1577               "register": "0xA0",
1578               "position": 3
1579             }
1580         )"_json;
1581         parseI2CWriteBit(element);
1582         ADD_FAILURE() << "Should not have reached this line.";
1583     }
1584     catch (const std::invalid_argument& e)
1585     {
1586         EXPECT_STREQ(e.what(), "Required property missing: value");
1587     }
1588 }
1589 
1590 TEST(ConfigFileParserTests, ParseI2CWriteByte)
1591 {
1592     // Test where works: Only required properties specified
1593     {
1594         const json element = R"(
1595             {
1596               "register": "0x0A",
1597               "value": "0xCC"
1598             }
1599         )"_json;
1600         std::unique_ptr<I2CWriteByteAction> action = parseI2CWriteByte(element);
1601         EXPECT_EQ(action->getRegister(), 0x0A);
1602         EXPECT_EQ(action->getValue(), 0xCC);
1603         EXPECT_EQ(action->getMask(), 0xFF);
1604     }
1605 
1606     // Test where works: All properties specified
1607     {
1608         const json element = R"(
1609             {
1610               "register": "0x0A",
1611               "value": "0xCC",
1612               "mask": "0xF7"
1613             }
1614         )"_json;
1615         std::unique_ptr<I2CWriteByteAction> action = parseI2CWriteByte(element);
1616         EXPECT_EQ(action->getRegister(), 0x0A);
1617         EXPECT_EQ(action->getValue(), 0xCC);
1618         EXPECT_EQ(action->getMask(), 0xF7);
1619     }
1620 
1621     // Test where fails: Element is not an object
1622     try
1623     {
1624         const json element = R"( [ "0xFF", "0x01" ] )"_json;
1625         parseI2CWriteByte(element);
1626         ADD_FAILURE() << "Should not have reached this line.";
1627     }
1628     catch (const std::invalid_argument& e)
1629     {
1630         EXPECT_STREQ(e.what(), "Element is not an object");
1631     }
1632 
1633     // Test where fails: Invalid property specified
1634     try
1635     {
1636         const json element = R"(
1637             {
1638               "register": "0x0A",
1639               "value": "0xCC",
1640               "mask": "0xF7",
1641               "foo": 1
1642             }
1643         )"_json;
1644         parseI2CWriteByte(element);
1645         ADD_FAILURE() << "Should not have reached this line.";
1646     }
1647     catch (const std::invalid_argument& e)
1648     {
1649         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1650     }
1651 
1652     // Test where fails: register value is invalid
1653     try
1654     {
1655         const json element = R"(
1656             {
1657               "register": "0x0Z",
1658               "value": "0xCC",
1659               "mask": "0xF7"
1660             }
1661         )"_json;
1662         parseI2CWriteByte(element);
1663         ADD_FAILURE() << "Should not have reached this line.";
1664     }
1665     catch (const std::invalid_argument& e)
1666     {
1667         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1668     }
1669 
1670     // Test where fails: value value is invalid
1671     try
1672     {
1673         const json element = R"(
1674             {
1675               "register": "0x0A",
1676               "value": "0xCCC",
1677               "mask": "0xF7"
1678             }
1679         )"_json;
1680         parseI2CWriteByte(element);
1681         ADD_FAILURE() << "Should not have reached this line.";
1682     }
1683     catch (const std::invalid_argument& e)
1684     {
1685         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1686     }
1687 
1688     // Test where fails: mask value is invalid
1689     try
1690     {
1691         const json element = R"(
1692             {
1693               "register": "0x0A",
1694               "value": "0xCC",
1695               "mask": "F7"
1696             }
1697         )"_json;
1698         parseI2CWriteByte(element);
1699         ADD_FAILURE() << "Should not have reached this line.";
1700     }
1701     catch (const std::invalid_argument& e)
1702     {
1703         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1704     }
1705 
1706     // Test where fails: Required register property not specified
1707     try
1708     {
1709         const json element = R"(
1710             {
1711               "value": "0xCC",
1712               "mask": "0xF7"
1713             }
1714         )"_json;
1715         parseI2CWriteByte(element);
1716         ADD_FAILURE() << "Should not have reached this line.";
1717     }
1718     catch (const std::invalid_argument& e)
1719     {
1720         EXPECT_STREQ(e.what(), "Required property missing: register");
1721     }
1722 
1723     // Test where fails: Required value property not specified
1724     try
1725     {
1726         const json element = R"(
1727             {
1728               "register": "0x0A",
1729               "mask": "0xF7"
1730             }
1731         )"_json;
1732         parseI2CWriteByte(element);
1733         ADD_FAILURE() << "Should not have reached this line.";
1734     }
1735     catch (const std::invalid_argument& e)
1736     {
1737         EXPECT_STREQ(e.what(), "Required property missing: value");
1738     }
1739 }
1740 
1741 TEST(ConfigFileParserTests, ParseI2CWriteBytes)
1742 {
1743     // Test where works: Only required properties specified
1744     {
1745         const json element = R"(
1746             {
1747               "register": "0x0A",
1748               "values": [ "0xCC", "0xFF" ]
1749             }
1750         )"_json;
1751         std::unique_ptr<I2CWriteBytesAction> action =
1752             parseI2CWriteBytes(element);
1753         EXPECT_EQ(action->getRegister(), 0x0A);
1754         EXPECT_EQ(action->getValues().size(), 2);
1755         EXPECT_EQ(action->getValues()[0], 0xCC);
1756         EXPECT_EQ(action->getValues()[1], 0xFF);
1757         EXPECT_EQ(action->getMasks().size(), 0);
1758     }
1759 
1760     // Test where works: All properties specified
1761     {
1762         const json element = R"(
1763             {
1764               "register": "0x0A",
1765               "values": [ "0xCC", "0xFF" ],
1766               "masks":  [ "0x7F", "0x77" ]
1767             }
1768         )"_json;
1769         std::unique_ptr<I2CWriteBytesAction> action =
1770             parseI2CWriteBytes(element);
1771         EXPECT_EQ(action->getRegister(), 0x0A);
1772         EXPECT_EQ(action->getValues().size(), 2);
1773         EXPECT_EQ(action->getValues()[0], 0xCC);
1774         EXPECT_EQ(action->getValues()[1], 0xFF);
1775         EXPECT_EQ(action->getMasks().size(), 2);
1776         EXPECT_EQ(action->getMasks()[0], 0x7F);
1777         EXPECT_EQ(action->getMasks()[1], 0x77);
1778     }
1779 
1780     // Test where fails: Element is not an object
1781     try
1782     {
1783         const json element = R"( [ "0xFF", "0x01" ] )"_json;
1784         parseI2CWriteBytes(element);
1785         ADD_FAILURE() << "Should not have reached this line.";
1786     }
1787     catch (const std::invalid_argument& e)
1788     {
1789         EXPECT_STREQ(e.what(), "Element is not an object");
1790     }
1791 
1792     // Test where fails: Invalid property specified
1793     try
1794     {
1795         const json element = R"(
1796             {
1797               "register": "0x0A",
1798               "values": [ "0xCC", "0xFF" ],
1799               "masks":  [ "0x7F", "0x7F" ],
1800               "foo": 1
1801             }
1802         )"_json;
1803         parseI2CWriteBytes(element);
1804         ADD_FAILURE() << "Should not have reached this line.";
1805     }
1806     catch (const std::invalid_argument& e)
1807     {
1808         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1809     }
1810 
1811     // Test where fails: register value is invalid
1812     try
1813     {
1814         const json element = R"(
1815             {
1816               "register": "0x0Z",
1817               "values": [ "0xCC", "0xFF" ],
1818               "masks":  [ "0x7F", "0x7F" ]
1819             }
1820         )"_json;
1821         parseI2CWriteBytes(element);
1822         ADD_FAILURE() << "Should not have reached this line.";
1823     }
1824     catch (const std::invalid_argument& e)
1825     {
1826         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1827     }
1828 
1829     // Test where fails: values value is invalid
1830     try
1831     {
1832         const json element = R"(
1833             {
1834               "register": "0x0A",
1835               "values": [ "0xCCC", "0xFF" ],
1836               "masks":  [ "0x7F", "0x7F" ]
1837             }
1838         )"_json;
1839         parseI2CWriteBytes(element);
1840         ADD_FAILURE() << "Should not have reached this line.";
1841     }
1842     catch (const std::invalid_argument& e)
1843     {
1844         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1845     }
1846 
1847     // Test where fails: masks value is invalid
1848     try
1849     {
1850         const json element = R"(
1851             {
1852               "register": "0x0A",
1853               "values": [ "0xCC", "0xFF" ],
1854               "masks":  [ "F", "0x7F" ]
1855             }
1856         )"_json;
1857         parseI2CWriteBytes(element);
1858         ADD_FAILURE() << "Should not have reached this line.";
1859     }
1860     catch (const std::invalid_argument& e)
1861     {
1862         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
1863     }
1864 
1865     // Test where fails: number of elements in masks is invalid
1866     try
1867     {
1868         const json element = R"(
1869             {
1870               "register": "0x0A",
1871               "values": [ "0xCC", "0xFF" ],
1872               "masks":  [ "0x7F" ]
1873             }
1874         )"_json;
1875         parseI2CWriteBytes(element);
1876         ADD_FAILURE() << "Should not have reached this line.";
1877     }
1878     catch (const std::invalid_argument& e)
1879     {
1880         EXPECT_STREQ(e.what(), "Invalid number of elements in masks");
1881     }
1882 
1883     // Test where fails: Required register property not specified
1884     try
1885     {
1886         const json element = R"(
1887             {
1888               "values": [ "0xCC", "0xFF" ]
1889             }
1890         )"_json;
1891         parseI2CWriteBytes(element);
1892         ADD_FAILURE() << "Should not have reached this line.";
1893     }
1894     catch (const std::invalid_argument& e)
1895     {
1896         EXPECT_STREQ(e.what(), "Required property missing: register");
1897     }
1898 
1899     // Test where fails: Required values property not specified
1900     try
1901     {
1902         const json element = R"(
1903             {
1904               "register": "0x0A"
1905             }
1906         )"_json;
1907         parseI2CWriteBytes(element);
1908         ADD_FAILURE() << "Should not have reached this line.";
1909     }
1910     catch (const std::invalid_argument& e)
1911     {
1912         EXPECT_STREQ(e.what(), "Required property missing: values");
1913     }
1914 }
1915 
1916 TEST(ConfigFileParserTests, ParseInt8)
1917 {
1918     // Test where works: INT8_MIN
1919     {
1920         const json element = R"( -128 )"_json;
1921         int8_t value = parseInt8(element);
1922         EXPECT_EQ(value, -128);
1923     }
1924 
1925     // Test where works: INT8_MAX
1926     {
1927         const json element = R"( 127 )"_json;
1928         int8_t value = parseInt8(element);
1929         EXPECT_EQ(value, 127);
1930     }
1931 
1932     // Test where fails: Element is not an integer
1933     try
1934     {
1935         const json element = R"( 1.03 )"_json;
1936         parseInt8(element);
1937         ADD_FAILURE() << "Should not have reached this line.";
1938     }
1939     catch (const std::invalid_argument& e)
1940     {
1941         EXPECT_STREQ(e.what(), "Element is not an integer");
1942     }
1943 
1944     // Test where fails: Value < INT8_MIN
1945     try
1946     {
1947         const json element = R"( -129 )"_json;
1948         parseInt8(element);
1949         ADD_FAILURE() << "Should not have reached this line.";
1950     }
1951     catch (const std::invalid_argument& e)
1952     {
1953         EXPECT_STREQ(e.what(), "Element is not an 8-bit signed integer");
1954     }
1955 
1956     // Test where fails: Value > INT8_MAX
1957     try
1958     {
1959         const json element = R"( 128 )"_json;
1960         parseInt8(element);
1961         ADD_FAILURE() << "Should not have reached this line.";
1962     }
1963     catch (const std::invalid_argument& e)
1964     {
1965         EXPECT_STREQ(e.what(), "Element is not an 8-bit signed integer");
1966     }
1967 }
1968 
1969 TEST(ConfigFileParserTests, ParsePMBusWriteVoutCommand)
1970 {
1971     // Test where works: Only required properties specified
1972     {
1973         const json element = R"(
1974             {
1975               "format": "linear"
1976             }
1977         )"_json;
1978         std::unique_ptr<PMBusWriteVoutCommandAction> action =
1979             parsePMBusWriteVoutCommand(element);
1980         EXPECT_EQ(action->getVolts().has_value(), false);
1981         EXPECT_EQ(action->getFormat(), pmbus_utils::VoutDataFormat::linear);
1982         EXPECT_EQ(action->getExponent().has_value(), false);
1983         EXPECT_EQ(action->isVerified(), false);
1984     }
1985 
1986     // Test where works: All properties specified
1987     {
1988         const json element = R"(
1989             {
1990               "volts": 1.03,
1991               "format": "linear",
1992               "exponent": -8,
1993               "is_verified": true
1994             }
1995         )"_json;
1996         std::unique_ptr<PMBusWriteVoutCommandAction> action =
1997             parsePMBusWriteVoutCommand(element);
1998         EXPECT_EQ(action->getVolts().has_value(), true);
1999         EXPECT_EQ(action->getVolts().value(), 1.03);
2000         EXPECT_EQ(action->getFormat(), pmbus_utils::VoutDataFormat::linear);
2001         EXPECT_EQ(action->getExponent().has_value(), true);
2002         EXPECT_EQ(action->getExponent().value(), -8);
2003         EXPECT_EQ(action->isVerified(), true);
2004     }
2005 
2006     // Test where fails: Element is not an object
2007     try
2008     {
2009         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2010         parsePMBusWriteVoutCommand(element);
2011         ADD_FAILURE() << "Should not have reached this line.";
2012     }
2013     catch (const std::invalid_argument& e)
2014     {
2015         EXPECT_STREQ(e.what(), "Element is not an object");
2016     }
2017 
2018     // Test where fails: volts value is invalid
2019     try
2020     {
2021         const json element = R"(
2022             {
2023               "volts": "foo",
2024               "format": "linear"
2025             }
2026         )"_json;
2027         parsePMBusWriteVoutCommand(element);
2028         ADD_FAILURE() << "Should not have reached this line.";
2029     }
2030     catch (const std::invalid_argument& e)
2031     {
2032         EXPECT_STREQ(e.what(), "Element is not a number");
2033     }
2034 
2035     // Test where fails: Required format property not specified
2036     try
2037     {
2038         const json element = R"(
2039             {
2040               "volts": 1.03,
2041               "is_verified": true
2042             }
2043         )"_json;
2044         parsePMBusWriteVoutCommand(element);
2045         ADD_FAILURE() << "Should not have reached this line.";
2046     }
2047     catch (const std::invalid_argument& e)
2048     {
2049         EXPECT_STREQ(e.what(), "Required property missing: format");
2050     }
2051 
2052     // Test where fails: format value is invalid
2053     try
2054     {
2055         const json element = R"(
2056             {
2057               "format": "linear_11"
2058             }
2059         )"_json;
2060         parsePMBusWriteVoutCommand(element);
2061         ADD_FAILURE() << "Should not have reached this line.";
2062     }
2063     catch (const std::invalid_argument& e)
2064     {
2065         EXPECT_STREQ(e.what(), "Invalid format value: linear_11");
2066     }
2067 
2068     // Test where fails: exponent value is invalid
2069     try
2070     {
2071         const json element = R"(
2072             {
2073               "format": "linear",
2074               "exponent": 1.3
2075             }
2076         )"_json;
2077         parsePMBusWriteVoutCommand(element);
2078         ADD_FAILURE() << "Should not have reached this line.";
2079     }
2080     catch (const std::invalid_argument& e)
2081     {
2082         EXPECT_STREQ(e.what(), "Element is not an integer");
2083     }
2084 
2085     // Test where fails: is_verified value is invalid
2086     try
2087     {
2088         const json element = R"(
2089             {
2090               "format": "linear",
2091               "is_verified": "true"
2092             }
2093         )"_json;
2094         parsePMBusWriteVoutCommand(element);
2095         ADD_FAILURE() << "Should not have reached this line.";
2096     }
2097     catch (const std::invalid_argument& e)
2098     {
2099         EXPECT_STREQ(e.what(), "Element is not a boolean");
2100     }
2101 
2102     // Test where fails: Invalid property specified
2103     try
2104     {
2105         const json element = R"(
2106             {
2107               "format": "linear",
2108               "foo": "bar"
2109             }
2110         )"_json;
2111         parsePMBusWriteVoutCommand(element);
2112         ADD_FAILURE() << "Should not have reached this line.";
2113     }
2114     catch (const std::invalid_argument& e)
2115     {
2116         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2117     }
2118 }
2119 
2120 TEST(ConfigFileParserTests, ParseRail)
2121 {
2122     // Test where works: Only required properties specified
2123     {
2124         const json element = R"(
2125             {
2126               "id": "vdd"
2127             }
2128         )"_json;
2129         std::unique_ptr<Rail> rail = parseRail(element);
2130         EXPECT_EQ(rail->getID(), "vdd");
2131         EXPECT_EQ(rail->getConfiguration(), nullptr);
2132         EXPECT_EQ(rail->getSensorMonitoring(), nullptr);
2133     }
2134 
2135     // Test where works: All properties specified
2136     {
2137         const json element = R"(
2138             {
2139               "comments": [ "comments property" ],
2140               "id": "vdd",
2141               "configuration": {
2142                 "volts": 1.1,
2143                 "actions": [
2144                   {
2145                     "pmbus_write_vout_command": {
2146                       "format": "linear"
2147                     }
2148                   }
2149                 ]
2150               },
2151               "sensor_monitoring": {
2152                 "actions": [
2153                   { "run_rule": "read_sensors_rule" }
2154                 ]
2155               }
2156             }
2157         )"_json;
2158         std::unique_ptr<Rail> rail = parseRail(element);
2159         EXPECT_EQ(rail->getID(), "vdd");
2160         EXPECT_NE(rail->getConfiguration(), nullptr);
2161         EXPECT_NE(rail->getSensorMonitoring(), nullptr);
2162     }
2163 
2164     // Test where fails: id property not specified
2165     try
2166     {
2167         const json element = R"(
2168             {
2169               "configuration": {
2170                 "volts": 1.1,
2171                 "actions": [
2172                   {
2173                     "pmbus_write_vout_command": {
2174                       "format": "linear"
2175                     }
2176                   }
2177                 ]
2178               }
2179             }
2180         )"_json;
2181         parseRail(element);
2182         ADD_FAILURE() << "Should not have reached this line.";
2183     }
2184     catch (const std::invalid_argument& e)
2185     {
2186         EXPECT_STREQ(e.what(), "Required property missing: id");
2187     }
2188 
2189     // Test where fails: id property is invalid
2190     try
2191     {
2192         const json element = R"(
2193             {
2194               "id": "",
2195               "configuration": {
2196                 "volts": 1.1,
2197                 "actions": [
2198                   {
2199                     "pmbus_write_vout_command": {
2200                       "format": "linear"
2201                     }
2202                   }
2203                 ]
2204               }
2205             }
2206         )"_json;
2207         parseRail(element);
2208         ADD_FAILURE() << "Should not have reached this line.";
2209     }
2210     catch (const std::invalid_argument& e)
2211     {
2212         EXPECT_STREQ(e.what(), "Element contains an empty string");
2213     }
2214 
2215     // Test where fails: Element is not an object
2216     try
2217     {
2218         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2219         parseRail(element);
2220         ADD_FAILURE() << "Should not have reached this line.";
2221     }
2222     catch (const std::invalid_argument& e)
2223     {
2224         EXPECT_STREQ(e.what(), "Element is not an object");
2225     }
2226 
2227     // Test where fails: configuration value is invalid
2228     try
2229     {
2230         const json element = R"(
2231             {
2232               "id": "vdd",
2233               "configuration": "config"
2234             }
2235         )"_json;
2236         parseRail(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 an object");
2242     }
2243 
2244     // Test where fails: sensor_monitoring value is invalid
2245     try
2246     {
2247         const json element = R"(
2248             {
2249               "comments": [ "comments property" ],
2250               "id": "vdd",
2251               "configuration": {
2252                 "volts": 1.1,
2253                 "actions": [
2254                   {
2255                     "pmbus_write_vout_command": {
2256                       "format": "linear"
2257                     }
2258                   }
2259                 ]
2260               },
2261               "sensor_monitoring": 1
2262             }
2263         )"_json;
2264         parseRail(element);
2265         ADD_FAILURE() << "Should not have reached this line.";
2266     }
2267     catch (const std::invalid_argument& e)
2268     {
2269         EXPECT_STREQ(e.what(), "Element is not an object");
2270     }
2271 
2272     // Test where fails: Invalid property specified
2273     try
2274     {
2275         const json element = R"(
2276             {
2277               "id": "vdd",
2278               "foo" : true
2279             }
2280         )"_json;
2281         parseRail(element);
2282         ADD_FAILURE() << "Should not have reached this line.";
2283     }
2284     catch (const std::invalid_argument& e)
2285     {
2286         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2287     }
2288 }
2289 
2290 TEST(ConfigFileParserTests, ParseRailArray)
2291 {
2292     // Test where works
2293     {
2294         const json element = R"(
2295             [
2296               { "id": "vdd" },
2297               { "id": "vio" }
2298             ]
2299         )"_json;
2300         std::vector<std::unique_ptr<Rail>> rails = parseRailArray(element);
2301         EXPECT_EQ(rails.size(), 2);
2302         EXPECT_EQ(rails[0]->getID(), "vdd");
2303         EXPECT_EQ(rails[1]->getID(), "vio");
2304     }
2305 
2306     // Test where fails: Element is not an array
2307     try
2308     {
2309         const json element = R"(
2310             {
2311               "foo": "bar"
2312             }
2313         )"_json;
2314         parseRailArray(element);
2315         ADD_FAILURE() << "Should not have reached this line.";
2316     }
2317     catch (const std::invalid_argument& e)
2318     {
2319         EXPECT_STREQ(e.what(), "Element is not an array");
2320     }
2321 }
2322 
2323 TEST(ConfigFileParserTests, ParseRoot)
2324 {
2325     // Test where works: Only required properties specified
2326     {
2327         const json element = R"(
2328             {
2329               "chassis": [
2330                 { "number": 1 }
2331               ]
2332             }
2333         )"_json;
2334         std::vector<std::unique_ptr<Rule>> rules{};
2335         std::vector<std::unique_ptr<Chassis>> chassis{};
2336         std::tie(rules, chassis) = parseRoot(element);
2337         EXPECT_EQ(rules.size(), 0);
2338         EXPECT_EQ(chassis.size(), 1);
2339     }
2340 
2341     // Test where works: All properties specified
2342     {
2343         const json element = R"(
2344             {
2345               "comments": [ "Config file for a FooBar one-chassis system" ],
2346               "rules": [
2347                 {
2348                   "id": "set_voltage_rule",
2349                   "actions": [
2350                     { "pmbus_write_vout_command": { "format": "linear" } }
2351                   ]
2352                 }
2353               ],
2354               "chassis": [
2355                 { "number": 1 },
2356                 { "number": 3 }
2357               ]
2358             }
2359         )"_json;
2360         std::vector<std::unique_ptr<Rule>> rules{};
2361         std::vector<std::unique_ptr<Chassis>> chassis{};
2362         std::tie(rules, chassis) = parseRoot(element);
2363         EXPECT_EQ(rules.size(), 1);
2364         EXPECT_EQ(chassis.size(), 2);
2365     }
2366 
2367     // Test where fails: Element is not an object
2368     try
2369     {
2370         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2371         parseRoot(element);
2372         ADD_FAILURE() << "Should not have reached this line.";
2373     }
2374     catch (const std::invalid_argument& e)
2375     {
2376         EXPECT_STREQ(e.what(), "Element is not an object");
2377     }
2378 
2379     // Test where fails: chassis property not specified
2380     try
2381     {
2382         const json element = R"(
2383             {
2384               "rules": [
2385                 {
2386                   "id": "set_voltage_rule",
2387                   "actions": [
2388                     { "pmbus_write_vout_command": { "format": "linear" } }
2389                   ]
2390                 }
2391               ]
2392             }
2393         )"_json;
2394         parseRoot(element);
2395         ADD_FAILURE() << "Should not have reached this line.";
2396     }
2397     catch (const std::invalid_argument& e)
2398     {
2399         EXPECT_STREQ(e.what(), "Required property missing: chassis");
2400     }
2401 
2402     // Test where fails: Invalid property specified
2403     try
2404     {
2405         const json element = R"(
2406             {
2407               "remarks": [ "Config file for a FooBar one-chassis system" ],
2408               "chassis": [
2409                 { "number": 1 }
2410               ]
2411             }
2412         )"_json;
2413         parseRoot(element);
2414         ADD_FAILURE() << "Should not have reached this line.";
2415     }
2416     catch (const std::invalid_argument& e)
2417     {
2418         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2419     }
2420 }
2421 
2422 TEST(ConfigFileParserTests, ParseRule)
2423 {
2424     // Test where works: comments property specified
2425     {
2426         const json element = R"(
2427             {
2428               "comments": [ "Set voltage rule" ],
2429               "id": "set_voltage_rule",
2430               "actions": [
2431                 { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } },
2432                 { "pmbus_write_vout_command": { "volts": 1.03, "format": "linear" } }
2433               ]
2434             }
2435         )"_json;
2436         std::unique_ptr<Rule> rule = parseRule(element);
2437         EXPECT_EQ(rule->getID(), "set_voltage_rule");
2438         EXPECT_EQ(rule->getActions().size(), 2);
2439     }
2440 
2441     // Test where works: comments property not specified
2442     {
2443         const json element = R"(
2444             {
2445               "id": "set_voltage_rule",
2446               "actions": [
2447                 { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } },
2448                 { "pmbus_write_vout_command": { "volts": 1.03, "format": "linear" } },
2449                 { "pmbus_write_vout_command": { "volts": 1.05, "format": "linear" } }
2450               ]
2451             }
2452         )"_json;
2453         std::unique_ptr<Rule> rule = parseRule(element);
2454         EXPECT_EQ(rule->getID(), "set_voltage_rule");
2455         EXPECT_EQ(rule->getActions().size(), 3);
2456     }
2457 
2458     // Test where fails: Element is not an object
2459     try
2460     {
2461         const json element = R"( [ "0xFF", "0x01" ] )"_json;
2462         parseRule(element);
2463         ADD_FAILURE() << "Should not have reached this line.";
2464     }
2465     catch (const std::invalid_argument& e)
2466     {
2467         EXPECT_STREQ(e.what(), "Element is not an object");
2468     }
2469 
2470     // Test where fails: id property not specified
2471     try
2472     {
2473         const json element = R"(
2474             {
2475               "actions": [
2476                 { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
2477               ]
2478             }
2479         )"_json;
2480         parseRule(element);
2481         ADD_FAILURE() << "Should not have reached this line.";
2482     }
2483     catch (const std::invalid_argument& e)
2484     {
2485         EXPECT_STREQ(e.what(), "Required property missing: id");
2486     }
2487 
2488     // Test where fails: id property is invalid
2489     try
2490     {
2491         const json element = R"(
2492             {
2493               "id": "",
2494               "actions": [
2495                 { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
2496               ]
2497             }
2498         )"_json;
2499         parseRule(element);
2500         ADD_FAILURE() << "Should not have reached this line.";
2501     }
2502     catch (const std::invalid_argument& e)
2503     {
2504         EXPECT_STREQ(e.what(), "Element contains an empty string");
2505     }
2506 
2507     // Test where fails: actions property not specified
2508     try
2509     {
2510         const json element = R"(
2511             {
2512               "comments": [ "Set voltage rule" ],
2513               "id": "set_voltage_rule"
2514             }
2515         )"_json;
2516         parseRule(element);
2517         ADD_FAILURE() << "Should not have reached this line.";
2518     }
2519     catch (const std::invalid_argument& e)
2520     {
2521         EXPECT_STREQ(e.what(), "Required property missing: actions");
2522     }
2523 
2524     // Test where fails: actions property is invalid
2525     try
2526     {
2527         const json element = R"(
2528             {
2529               "id": "set_voltage_rule",
2530               "actions": true
2531             }
2532         )"_json;
2533         parseRule(element);
2534         ADD_FAILURE() << "Should not have reached this line.";
2535     }
2536     catch (const std::invalid_argument& e)
2537     {
2538         EXPECT_STREQ(e.what(), "Element is not an array");
2539     }
2540 
2541     // Test where fails: Invalid property specified
2542     try
2543     {
2544         const json element = R"(
2545             {
2546               "remarks": [ "Set voltage rule" ],
2547               "id": "set_voltage_rule",
2548               "actions": [
2549                 { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
2550               ]
2551             }
2552         )"_json;
2553         parseRule(element);
2554         ADD_FAILURE() << "Should not have reached this line.";
2555     }
2556     catch (const std::invalid_argument& e)
2557     {
2558         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2559     }
2560 }
2561 
2562 TEST(ConfigFileParserTests, ParseRuleArray)
2563 {
2564     // Test where works
2565     {
2566         const json element = R"(
2567             [
2568               {
2569                 "id": "set_voltage_rule1",
2570                 "actions": [
2571                   { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } }
2572                 ]
2573               },
2574               {
2575                 "id": "set_voltage_rule2",
2576                 "actions": [
2577                   { "pmbus_write_vout_command": { "volts": 1.01, "format": "linear" } },
2578                   { "pmbus_write_vout_command": { "volts": 1.11, "format": "linear" } }
2579                 ]
2580               }
2581             ]
2582         )"_json;
2583         std::vector<std::unique_ptr<Rule>> rules = parseRuleArray(element);
2584         EXPECT_EQ(rules.size(), 2);
2585         EXPECT_EQ(rules[0]->getID(), "set_voltage_rule1");
2586         EXPECT_EQ(rules[0]->getActions().size(), 1);
2587         EXPECT_EQ(rules[1]->getID(), "set_voltage_rule2");
2588         EXPECT_EQ(rules[1]->getActions().size(), 2);
2589     }
2590 
2591     // Test where fails: Element is not an array
2592     try
2593     {
2594         const json element = R"( { "id": "set_voltage_rule" } )"_json;
2595         parseRuleArray(element);
2596         ADD_FAILURE() << "Should not have reached this line.";
2597     }
2598     catch (const std::invalid_argument& e)
2599     {
2600         EXPECT_STREQ(e.what(), "Element is not an array");
2601     }
2602 }
2603 
2604 TEST(ConfigFileParserTests, ParseRuleIDOrActionsProperty)
2605 {
2606     // Test where works: actions specified
2607     {
2608         const json element = R"(
2609             {
2610               "actions": [
2611                 { "pmbus_write_vout_command": { "format": "linear" } },
2612                 { "run_rule": "set_voltage_rule" }
2613               ]
2614             }
2615         )"_json;
2616         std::vector<std::unique_ptr<Action>> actions =
2617             parseRuleIDOrActionsProperty(element);
2618         EXPECT_EQ(actions.size(), 2);
2619     }
2620 
2621     // Test where works: rule_id specified
2622     {
2623         const json element = R"(
2624             {
2625               "rule_id": "set_voltage_rule"
2626             }
2627         )"_json;
2628         std::vector<std::unique_ptr<Action>> actions =
2629             parseRuleIDOrActionsProperty(element);
2630         EXPECT_EQ(actions.size(), 1);
2631     }
2632 
2633     // Test where fails: Element is not an object
2634     try
2635     {
2636         const json element = R"( [ "foo", "bar" ] )"_json;
2637         parseRuleIDOrActionsProperty(element);
2638         ADD_FAILURE() << "Should not have reached this line.";
2639     }
2640     catch (const std::invalid_argument& e)
2641     {
2642         EXPECT_STREQ(e.what(), "Element is not an object");
2643     }
2644 
2645     // Test where fails: rule_id is invalid
2646     try
2647     {
2648         const json element = R"(
2649             { "rule_id": 1 }
2650         )"_json;
2651         parseRuleIDOrActionsProperty(element);
2652         ADD_FAILURE() << "Should not have reached this line.";
2653     }
2654     catch (const std::invalid_argument& e)
2655     {
2656         EXPECT_STREQ(e.what(), "Element is not a string");
2657     }
2658 
2659     // Test where fails: actions is invalid
2660     try
2661     {
2662         const json element = R"(
2663             { "actions": 1 }
2664         )"_json;
2665         parseRuleIDOrActionsProperty(element);
2666         ADD_FAILURE() << "Should not have reached this line.";
2667     }
2668     catch (const std::invalid_argument& e)
2669     {
2670         EXPECT_STREQ(e.what(), "Element is not an array");
2671     }
2672 
2673     // Test where fails: Neither rule_id nor actions specified
2674     try
2675     {
2676         const json element = R"(
2677             {
2678               "volts": 1.03
2679             }
2680         )"_json;
2681         parseRuleIDOrActionsProperty(element);
2682         ADD_FAILURE() << "Should not have reached this line.";
2683     }
2684     catch (const std::invalid_argument& e)
2685     {
2686         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
2687                                "either rule_id or actions");
2688     }
2689 
2690     // Test where fails: Both rule_id and actions specified
2691     try
2692     {
2693         const json element = R"(
2694             {
2695               "volts": 1.03,
2696               "rule_id": "set_voltage_rule",
2697               "actions": [
2698                 {
2699                   "pmbus_write_vout_command": {
2700                     "format": "linear"
2701                   }
2702                 }
2703               ]
2704             }
2705         )"_json;
2706         parseRuleIDOrActionsProperty(element);
2707         ADD_FAILURE() << "Should not have reached this line.";
2708     }
2709     catch (const std::invalid_argument& e)
2710     {
2711         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
2712                                "either rule_id or actions");
2713     }
2714 }
2715 
2716 TEST(ConfigFileParserTests, ParseRunRule)
2717 {
2718     // Test where works
2719     {
2720         const json element = "vdd_regulator";
2721         std::unique_ptr<RunRuleAction> action = parseRunRule(element);
2722         EXPECT_EQ(action->getRuleID(), "vdd_regulator");
2723     }
2724 
2725     // Test where fails: Element is not a string
2726     try
2727     {
2728         const json element = 1;
2729         parseRunRule(element);
2730         ADD_FAILURE() << "Should not have reached this line.";
2731     }
2732     catch (const std::invalid_argument& e)
2733     {
2734         EXPECT_STREQ(e.what(), "Element is not a string");
2735     }
2736 
2737     // Test where fails: Empty string
2738     try
2739     {
2740         const json element = "";
2741         parseRunRule(element);
2742         ADD_FAILURE() << "Should not have reached this line.";
2743     }
2744     catch (const std::invalid_argument& e)
2745     {
2746         EXPECT_STREQ(e.what(), "Element contains an empty string");
2747     }
2748 }
2749 
2750 TEST(ConfigFileParserTests, ParseSensorMonitoring)
2751 {
2752     // Test where works: actions property specified
2753     {
2754         const json element = R"(
2755             {
2756               "actions": [
2757                 { "run_rule": "read_sensors_rule" }
2758               ]
2759             }
2760         )"_json;
2761         std::unique_ptr<SensorMonitoring> sensorMonitoring =
2762             parseSensorMonitoring(element);
2763         EXPECT_EQ(sensorMonitoring->getActions().size(), 1);
2764     }
2765 
2766     // Test where works: rule_id property specified
2767     {
2768         const json element = R"(
2769             {
2770               "comments": [ "comments property" ],
2771               "rule_id": "set_voltage_rule"
2772             }
2773         )"_json;
2774         std::unique_ptr<SensorMonitoring> sensorMonitoring =
2775             parseSensorMonitoring(element);
2776         EXPECT_EQ(sensorMonitoring->getActions().size(), 1);
2777     }
2778 
2779     // Test where fails: actions object is invalid
2780     try
2781     {
2782         const json element = R"(
2783             {
2784               "actions": 1
2785             }
2786         )"_json;
2787         parseSensorMonitoring(element);
2788         ADD_FAILURE() << "Should not have reached this line.";
2789     }
2790     catch (const std::invalid_argument& e)
2791     {
2792         EXPECT_STREQ(e.what(), "Element is not an array");
2793     }
2794 
2795     // Test where fails: rule_id value is invalid
2796     try
2797     {
2798         const json element = R"(
2799             {
2800               "rule_id": 1
2801             }
2802         )"_json;
2803         parseSensorMonitoring(element);
2804         ADD_FAILURE() << "Should not have reached this line.";
2805     }
2806     catch (const std::invalid_argument& e)
2807     {
2808         EXPECT_STREQ(e.what(), "Element is not a string");
2809     }
2810 
2811     // Test where fails: Required actions or rule_id property not specified
2812     try
2813     {
2814         const json element = R"(
2815             {
2816               "comments": [ "comments property" ]
2817             }
2818         )"_json;
2819         parseSensorMonitoring(element);
2820         ADD_FAILURE() << "Should not have reached this line.";
2821     }
2822     catch (const std::invalid_argument& e)
2823     {
2824         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
2825                                "either rule_id or actions");
2826     }
2827 
2828     // Test where fails: Required actions or rule_id property both specified
2829     try
2830     {
2831         const json element = R"(
2832             {
2833               "rule_id": "set_voltage_rule",
2834               "actions": [
2835                 { "run_rule": "read_sensors_rule" }
2836               ]
2837             }
2838         )"_json;
2839         parseSensorMonitoring(element);
2840         ADD_FAILURE() << "Should not have reached this line.";
2841     }
2842     catch (const std::invalid_argument& e)
2843     {
2844         EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
2845                                "either rule_id or actions");
2846     }
2847 
2848     // Test where fails: Element is not an object
2849     try
2850     {
2851         const json element = R"( [ "foo", "bar" ] )"_json;
2852         parseSensorMonitoring(element);
2853         ADD_FAILURE() << "Should not have reached this line.";
2854     }
2855     catch (const std::invalid_argument& e)
2856     {
2857         EXPECT_STREQ(e.what(), "Element is not an object");
2858     }
2859 
2860     // Test where fails: Invalid property specified
2861     try
2862     {
2863         const json element = R"(
2864             {
2865               "foo": "bar",
2866               "actions": [
2867                 { "run_rule": "read_sensors_rule" }
2868               ]
2869             }
2870         )"_json;
2871         parseSensorMonitoring(element);
2872         ADD_FAILURE() << "Should not have reached this line.";
2873     }
2874     catch (const std::invalid_argument& e)
2875     {
2876         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2877     }
2878 }
2879 
2880 TEST(ConfigFileParserTests, ParseString)
2881 {
2882     // Test where works: Empty string
2883     {
2884         const json element = "";
2885         std::string value = parseString(element, true);
2886         EXPECT_EQ(value, "");
2887     }
2888 
2889     // Test where works: Non-empty string
2890     {
2891         const json element = "vdd_regulator";
2892         std::string value = parseString(element, false);
2893         EXPECT_EQ(value, "vdd_regulator");
2894     }
2895 
2896     // Test where fails: Element is not a string
2897     try
2898     {
2899         const json element = R"( { "foo": "bar" } )"_json;
2900         parseString(element);
2901         ADD_FAILURE() << "Should not have reached this line.";
2902     }
2903     catch (const std::invalid_argument& e)
2904     {
2905         EXPECT_STREQ(e.what(), "Element is not a string");
2906     }
2907 
2908     // Test where fails: Empty string
2909     try
2910     {
2911         const json element = "";
2912         parseString(element);
2913         ADD_FAILURE() << "Should not have reached this line.";
2914     }
2915     catch (const std::invalid_argument& e)
2916     {
2917         EXPECT_STREQ(e.what(), "Element contains an empty string");
2918     }
2919 }
2920 
2921 TEST(ConfigFileParserTests, ParseUint8)
2922 {
2923     // Test where works: 0
2924     {
2925         const json element = R"( 0 )"_json;
2926         uint8_t value = parseUint8(element);
2927         EXPECT_EQ(value, 0);
2928     }
2929 
2930     // Test where works: UINT8_MAX
2931     {
2932         const json element = R"( 255 )"_json;
2933         uint8_t value = parseUint8(element);
2934         EXPECT_EQ(value, 255);
2935     }
2936 
2937     // Test where fails: Element is not an integer
2938     try
2939     {
2940         const json element = R"( 1.03 )"_json;
2941         parseUint8(element);
2942         ADD_FAILURE() << "Should not have reached this line.";
2943     }
2944     catch (const std::invalid_argument& e)
2945     {
2946         EXPECT_STREQ(e.what(), "Element is not an integer");
2947     }
2948 
2949     // Test where fails: Value < 0
2950     try
2951     {
2952         const json element = R"( -1 )"_json;
2953         parseUint8(element);
2954         ADD_FAILURE() << "Should not have reached this line.";
2955     }
2956     catch (const std::invalid_argument& e)
2957     {
2958         EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
2959     }
2960 
2961     // Test where fails: Value > UINT8_MAX
2962     try
2963     {
2964         const json element = R"( 256 )"_json;
2965         parseUint8(element);
2966         ADD_FAILURE() << "Should not have reached this line.";
2967     }
2968     catch (const std::invalid_argument& e)
2969     {
2970         EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
2971     }
2972 }
2973 
2974 TEST(ConfigFileParserTests, ParseUnsignedInteger)
2975 {
2976     // Test where works: 1
2977     {
2978         const json element = R"( 1 )"_json;
2979         unsigned int value = parseUnsignedInteger(element);
2980         EXPECT_EQ(value, 1);
2981     }
2982 
2983     // Test where fails: Element is not an integer
2984     try
2985     {
2986         const json element = R"( 1.5 )"_json;
2987         parseUnsignedInteger(element);
2988         ADD_FAILURE() << "Should not have reached this line.";
2989     }
2990     catch (const std::invalid_argument& e)
2991     {
2992         EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
2993     }
2994 
2995     // Test where fails: Value < 0
2996     try
2997     {
2998         const json element = R"( -1 )"_json;
2999         parseUnsignedInteger(element);
3000         ADD_FAILURE() << "Should not have reached this line.";
3001     }
3002     catch (const std::invalid_argument& e)
3003     {
3004         EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
3005     }
3006 }
3007 
3008 TEST(ConfigFileParserTests, VerifyIsArray)
3009 {
3010     // Test where element is an array
3011     try
3012     {
3013         const json element = R"( [ "foo", "bar" ] )"_json;
3014         verifyIsArray(element);
3015     }
3016     catch (const std::exception& e)
3017     {
3018         ADD_FAILURE() << "Should not have caught exception.";
3019     }
3020 
3021     // Test where element is not an array
3022     try
3023     {
3024         const json element = R"( { "foo": "bar" } )"_json;
3025         verifyIsArray(element);
3026         ADD_FAILURE() << "Should not have reached this line.";
3027     }
3028     catch (const std::invalid_argument& e)
3029     {
3030         EXPECT_STREQ(e.what(), "Element is not an array");
3031     }
3032 }
3033 
3034 TEST(ConfigFileParserTests, VerifyIsObject)
3035 {
3036     // Test where element is an object
3037     try
3038     {
3039         const json element = R"( { "foo": "bar" } )"_json;
3040         verifyIsObject(element);
3041     }
3042     catch (const std::exception& e)
3043     {
3044         ADD_FAILURE() << "Should not have caught exception.";
3045     }
3046 
3047     // Test where element is not an object
3048     try
3049     {
3050         const json element = R"( [ "foo", "bar" ] )"_json;
3051         verifyIsObject(element);
3052         ADD_FAILURE() << "Should not have reached this line.";
3053     }
3054     catch (const std::invalid_argument& e)
3055     {
3056         EXPECT_STREQ(e.what(), "Element is not an object");
3057     }
3058 }
3059 
3060 TEST(ConfigFileParserTests, VerifyPropertyCount)
3061 {
3062     // Test where element has expected number of properties
3063     try
3064     {
3065         const json element = R"(
3066             {
3067               "comments": [ "Set voltage rule" ],
3068               "id": "set_voltage_rule"
3069             }
3070         )"_json;
3071         verifyPropertyCount(element, 2);
3072     }
3073     catch (const std::exception& e)
3074     {
3075         ADD_FAILURE() << "Should not have caught exception.";
3076     }
3077 
3078     // Test where element has unexpected number of properties
3079     try
3080     {
3081         const json element = R"(
3082             {
3083               "comments": [ "Set voltage rule" ],
3084               "id": "set_voltage_rule",
3085               "foo": 1.3
3086             }
3087         )"_json;
3088         verifyPropertyCount(element, 2);
3089         ADD_FAILURE() << "Should not have reached this line.";
3090     }
3091     catch (const std::invalid_argument& e)
3092     {
3093         EXPECT_STREQ(e.what(), "Element contains an invalid property");
3094     }
3095 }
3096