xref: /openbmc/phosphor-power/phosphor-power-sequencer/test/config_file_parser_tests.cpp (revision d62367d6b4018179fb53c596860e511979447d2e)
1 /**
2  * Copyright © 2024 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 "chassis.hpp"
17 #include "config_file_parser.hpp"
18 #include "config_file_parser_error.hpp"
19 #include "mock_services.hpp"
20 #include "power_sequencer_device.hpp"
21 #include "rail.hpp"
22 #include "temporary_file.hpp"
23 #include "temporary_subdirectory.hpp"
24 
25 #include <sys/stat.h> // for chmod()
26 
27 #include <nlohmann/json.hpp>
28 
29 #include <cstdint>
30 #include <exception>
31 #include <filesystem>
32 #include <fstream>
33 #include <map>
34 #include <memory>
35 #include <optional>
36 #include <stdexcept>
37 #include <string>
38 #include <tuple>
39 #include <vector>
40 
41 #include <gtest/gtest.h>
42 
43 using namespace phosphor::power::sequencer;
44 using namespace phosphor::power::sequencer::config_file_parser;
45 using namespace phosphor::power::sequencer::config_file_parser::internal;
46 using namespace phosphor::power::util;
47 using json = nlohmann::json;
48 namespace fs = std::filesystem;
49 
50 void writeConfigFile(const fs::path& pathName, const std::string& contents)
51 {
52     std::ofstream file{pathName};
53     file << contents;
54 }
55 
56 void writeConfigFile(const fs::path& pathName, const json& contents)
57 {
58     std::ofstream file{pathName};
59     file << contents;
60 }
61 
62 TEST(ConfigFileParserTests, Find)
63 {
64     std::vector<std::string> compatibleSystemTypes{
65         "com.acme.Hardware.Chassis.Model.MegaServer4CPU",
66         "com.acme.Hardware.Chassis.Model.MegaServer",
67         "com.acme.Hardware.Chassis.Model.Server"};
68 
69     // Test where works: Fully qualified system type: First in list
70     {
71         TemporarySubDirectory configFileDir;
72         fs::path configFileDirPath = configFileDir.getPath();
73 
74         fs::path configFilePath = configFileDirPath;
75         configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer4CPU.json";
76         writeConfigFile(configFilePath, std::string{""});
77 
78         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
79         EXPECT_EQ(pathFound, configFilePath);
80     }
81 
82     // Test where works: Fully qualified system type: Second in list
83     {
84         TemporarySubDirectory configFileDir;
85         fs::path configFileDirPath = configFileDir.getPath();
86 
87         fs::path configFilePath = configFileDirPath;
88         configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer.json";
89         writeConfigFile(configFilePath, std::string{""});
90 
91         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
92         EXPECT_EQ(pathFound, configFilePath);
93     }
94 
95     // Test where works: Last node in system type: Second in list
96     {
97         TemporarySubDirectory configFileDir;
98         fs::path configFileDirPath = configFileDir.getPath();
99 
100         fs::path configFilePath = configFileDirPath;
101         configFilePath /= "MegaServer.json";
102         writeConfigFile(configFilePath, std::string{""});
103 
104         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
105         EXPECT_EQ(pathFound, configFilePath);
106     }
107 
108     // Test where works: Last node in system type: Last in list
109     {
110         TemporarySubDirectory configFileDir;
111         fs::path configFileDirPath = configFileDir.getPath();
112 
113         fs::path configFilePath = configFileDirPath;
114         configFilePath /= "Server.json";
115         writeConfigFile(configFilePath, std::string{""});
116 
117         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
118         EXPECT_EQ(pathFound, configFilePath);
119     }
120 
121     // Test where works: System type has no '.'
122     {
123         TemporarySubDirectory configFileDir;
124         fs::path configFileDirPath = configFileDir.getPath();
125 
126         fs::path configFilePath = configFileDirPath;
127         configFilePath /= "Server.json";
128         writeConfigFile(configFilePath, std::string{""});
129 
130         std::vector<std::string> noDotSystemTypes{"MegaServer4CPU",
131                                                   "MegaServer", "Server"};
132         fs::path pathFound = find(noDotSystemTypes, configFileDirPath);
133         EXPECT_EQ(pathFound, configFilePath);
134     }
135 
136     // Test where fails: System type list is empty
137     {
138         TemporarySubDirectory configFileDir;
139         fs::path configFileDirPath = configFileDir.getPath();
140 
141         fs::path configFilePath = configFileDirPath;
142         configFilePath /= "Server.json";
143         writeConfigFile(configFilePath, std::string{""});
144 
145         std::vector<std::string> emptySystemTypes{};
146         fs::path pathFound = find(emptySystemTypes, configFileDirPath);
147         EXPECT_TRUE(pathFound.empty());
148     }
149 
150     // Test where fails: Configuration file directory is empty
151     {
152         TemporarySubDirectory configFileDir;
153         fs::path configFileDirPath = configFileDir.getPath();
154 
155         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
156         EXPECT_TRUE(pathFound.empty());
157     }
158 
159     // Test where fails: Configuration file directory does not exist
160     {
161         fs::path configFileDirPath{"/tmp/does_not_exist_XYZ"};
162 
163         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
164         EXPECT_TRUE(pathFound.empty());
165     }
166 
167     // Test where fails: Configuration file directory is not readable
168     {
169         TemporarySubDirectory configFileDir;
170         fs::path configFileDirPath = configFileDir.getPath();
171         fs::permissions(configFileDirPath, fs::perms::none);
172 
173         EXPECT_THROW(find(compatibleSystemTypes, configFileDirPath),
174                      std::exception);
175 
176         fs::permissions(configFileDirPath, fs::perms::owner_all);
177     }
178 
179     // Test where fails: No matching file name found
180     {
181         TemporarySubDirectory configFileDir;
182         fs::path configFileDirPath = configFileDir.getPath();
183 
184         fs::path configFilePath = configFileDirPath;
185         configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer";
186         writeConfigFile(configFilePath, std::string{""});
187 
188         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
189         EXPECT_TRUE(pathFound.empty());
190     }
191 
192     // Test where fails: Matching file name is a directory: Fully qualified
193     {
194         TemporarySubDirectory configFileDir;
195         fs::path configFileDirPath = configFileDir.getPath();
196 
197         fs::path configFilePath = configFileDirPath;
198         configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer4CPU.json";
199         fs::create_directory(configFilePath);
200 
201         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
202         EXPECT_TRUE(pathFound.empty());
203     }
204 
205     // Test where fails: Matching file name is a directory: Last node
206     {
207         TemporarySubDirectory configFileDir;
208         fs::path configFileDirPath = configFileDir.getPath();
209 
210         fs::path configFilePath = configFileDirPath;
211         configFilePath /= "MegaServer.json";
212         fs::create_directory(configFilePath);
213 
214         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
215         EXPECT_TRUE(pathFound.empty());
216     }
217 
218     // Test where fails: System type has no '.'
219     {
220         TemporarySubDirectory configFileDir;
221         fs::path configFileDirPath = configFileDir.getPath();
222 
223         fs::path configFilePath = configFileDirPath;
224         configFilePath /= "MegaServer2CPU.json";
225         writeConfigFile(configFilePath, std::string{""});
226 
227         std::vector<std::string> noDotSystemTypes{"MegaServer4CPU",
228                                                   "MegaServer", "Server", ""};
229         fs::path pathFound = find(noDotSystemTypes, configFileDirPath);
230         EXPECT_TRUE(pathFound.empty());
231     }
232 
233     // Test where fails: System type ends with '.'
234     {
235         TemporarySubDirectory configFileDir;
236         fs::path configFileDirPath = configFileDir.getPath();
237 
238         fs::path configFilePath = configFileDirPath;
239         configFilePath /= "MegaServer4CPU.json";
240         writeConfigFile(configFilePath, std::string{""});
241 
242         std::vector<std::string> dotAtEndSystemTypes{
243             "com.acme.Hardware.Chassis.Model.MegaServer4CPU.", "a.", "."};
244         fs::path pathFound = find(dotAtEndSystemTypes, configFileDirPath);
245         EXPECT_TRUE(pathFound.empty());
246     }
247 }
248 
249 TEST(ConfigFileParserTests, Parse)
250 {
251     // Test where works
252     {
253         const json configFileContents = R"(
254             {
255               "chassis": [
256                 {
257                   "number": 1,
258                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
259                   "power_sequencers": []
260                 },
261                 {
262                   "number": 2,
263                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis2",
264                   "power_sequencers": []
265                 }
266               ]
267             }
268         )"_json;
269 
270         TemporaryFile configFile;
271         fs::path pathName{configFile.getPath()};
272         writeConfigFile(pathName, configFileContents);
273 
274         MockServices services{};
275         auto chassis = parse(pathName, services);
276 
277         EXPECT_EQ(chassis.size(), 2);
278         EXPECT_EQ(chassis[0]->getNumber(), 1);
279         EXPECT_EQ(chassis[0]->getInventoryPath(),
280                   "/xyz/openbmc_project/inventory/system/chassis1");
281         EXPECT_EQ(chassis[1]->getNumber(), 2);
282         EXPECT_EQ(chassis[1]->getInventoryPath(),
283                   "/xyz/openbmc_project/inventory/system/chassis2");
284     }
285 
286     // Test where fails: File does not exist
287     {
288         fs::path pathName{"/tmp/non_existent_file"};
289         MockServices services{};
290         EXPECT_THROW(parse(pathName, services), ConfigFileParserError);
291     }
292 
293     // Test where fails: File is not readable
294     {
295         const json configFileContents = R"(
296             {
297               "chassis": [
298                 {
299                   "number": 1,
300                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
301                   "power_sequencers": []
302                 }
303               ]
304             }
305         )"_json;
306 
307         TemporaryFile configFile;
308         fs::path pathName{configFile.getPath()};
309         writeConfigFile(pathName, configFileContents);
310 
311         chmod(pathName.c_str(), 0222);
312         MockServices services{};
313         EXPECT_THROW(parse(pathName, services), ConfigFileParserError);
314     }
315 
316     // Test where fails: File is not valid JSON
317     {
318         const std::string configFileContents = "] foo [";
319 
320         TemporaryFile configFile;
321         fs::path pathName{configFile.getPath()};
322         writeConfigFile(pathName, configFileContents);
323 
324         MockServices services{};
325         EXPECT_THROW(parse(pathName, services), ConfigFileParserError);
326     }
327 
328     // Test where fails: JSON does not conform to config file format
329     {
330         const json configFileContents = R"( [ "foo", "bar" ] )"_json;
331 
332         TemporaryFile configFile;
333         fs::path pathName{configFile.getPath()};
334         writeConfigFile(pathName, configFileContents);
335 
336         MockServices services{};
337         EXPECT_THROW(parse(pathName, services), ConfigFileParserError);
338     }
339 }
340 
341 TEST(ConfigFileParserTests, ParseChassis)
342 {
343     // Constants used by multiple tests
344     const json templateElement = R"(
345         {
346           "id": "foo_chassis",
347           "number": "${chassis_number}",
348           "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
349           "power_sequencers": [
350             {
351               "type": "UCD90320",
352               "i2c_interface": { "bus": "${bus}", "address": "${address}" },
353               "power_control_gpio_name": "power-chassis${chassis_number}-control",
354               "power_good_gpio_name": "power-chassis${chassis_number}-good",
355               "rails": []
356             }
357           ]
358         }
359     )"_json;
360     const std::map<std::string, JSONRefWrapper> chassisTemplates{
361         {"foo_chassis", templateElement}};
362 
363     // Test where works: Does not use a template
364     {
365         const json element = R"(
366             {
367               "number": 1,
368               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
369               "power_sequencers": [
370                 {
371                   "type": "UCD90320",
372                   "i2c_interface": { "bus": 3, "address": "0x11" },
373                   "power_control_gpio_name": "power-chassis-control",
374                   "power_good_gpio_name": "power-chassis-good",
375                   "rails": []
376                 }
377               ]
378             }
379         )"_json;
380         MockServices services{};
381         auto chassis = parseChassis(element, chassisTemplates, services);
382         EXPECT_EQ(chassis->getNumber(), 1);
383         EXPECT_EQ(chassis->getInventoryPath(),
384                   "/xyz/openbmc_project/inventory/system/chassis");
385         EXPECT_EQ(chassis->getPowerSequencers().size(), 1);
386         EXPECT_EQ(chassis->getPowerSequencers()[0]->getName(), "UCD90320");
387         EXPECT_EQ(chassis->getPowerSequencers()[0]->getBus(), 3);
388         EXPECT_EQ(chassis->getPowerSequencers()[0]->getAddress(), 0x11);
389     }
390 
391     // Test where works: Uses template: No comments specified
392     {
393         const json element = R"(
394             {
395               "template_id": "foo_chassis",
396               "template_variable_values": {
397                 "chassis_number": "2",
398                 "bus": "13",
399                 "address": "0x70"
400               }
401             }
402         )"_json;
403         MockServices services{};
404         auto chassis = parseChassis(element, chassisTemplates, services);
405         EXPECT_EQ(chassis->getNumber(), 2);
406         EXPECT_EQ(chassis->getInventoryPath(),
407                   "/xyz/openbmc_project/inventory/system/chassis2");
408         EXPECT_EQ(chassis->getPowerSequencers().size(), 1);
409         EXPECT_EQ(chassis->getPowerSequencers()[0]->getName(), "UCD90320");
410         EXPECT_EQ(chassis->getPowerSequencers()[0]->getBus(), 13);
411         EXPECT_EQ(chassis->getPowerSequencers()[0]->getAddress(), 0x70);
412     }
413 
414     // Test where works: Uses template: Comments specified
415     {
416         const json element = R"(
417             {
418               "comments": ["Chassis 3: Standard hardware layout"],
419               "template_id": "foo_chassis",
420               "template_variable_values": {
421                 "chassis_number": "3",
422                 "bus": "23",
423                 "address": "0x54"
424               }
425             }
426         )"_json;
427         MockServices services{};
428         auto chassis = parseChassis(element, chassisTemplates, services);
429         EXPECT_EQ(chassis->getNumber(), 3);
430         EXPECT_EQ(chassis->getInventoryPath(),
431                   "/xyz/openbmc_project/inventory/system/chassis3");
432         EXPECT_EQ(chassis->getPowerSequencers().size(), 1);
433         EXPECT_EQ(chassis->getPowerSequencers()[0]->getName(), "UCD90320");
434         EXPECT_EQ(chassis->getPowerSequencers()[0]->getBus(), 23);
435         EXPECT_EQ(chassis->getPowerSequencers()[0]->getAddress(), 0x54);
436     }
437 
438     // Test where fails: Element is not an object
439     try
440     {
441         const json element = R"( [ "vdda", "vddb" ] )"_json;
442         MockServices services{};
443         parseChassis(element, chassisTemplates, services);
444         ADD_FAILURE() << "Should not have reached this line.";
445     }
446     catch (const std::invalid_argument& e)
447     {
448         EXPECT_STREQ(e.what(), "Element is not an object");
449     }
450 
451     // Test where fails: Does not use a template: Cannot parse properties
452     try
453     {
454         const json element = R"(
455             {
456               "number": "one",
457               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
458               "power_sequencers": []
459             }
460         )"_json;
461         MockServices services{};
462         parseChassis(element, chassisTemplates, services);
463         ADD_FAILURE() << "Should not have reached this line.";
464     }
465     catch (const std::invalid_argument& e)
466     {
467         EXPECT_STREQ(e.what(), "Element is not an integer");
468     }
469 
470     // Test where fails: Uses template: Required template_variable_values
471     // property not specified
472     try
473     {
474         const json element = R"(
475             {
476               "template_id": "foo_chassis"
477             }
478         )"_json;
479         MockServices services{};
480         parseChassis(element, chassisTemplates, services);
481         ADD_FAILURE() << "Should not have reached this line.";
482     }
483     catch (const std::invalid_argument& e)
484     {
485         EXPECT_STREQ(e.what(),
486                      "Required property missing: template_variable_values");
487     }
488 
489     // Test where fails: Uses template: template_id value is invalid: Not a
490     // string
491     try
492     {
493         const json element = R"(
494             {
495               "template_id": 23,
496               "template_variable_values": { "chassis_number": "2" }
497             }
498         )"_json;
499         MockServices services{};
500         parseChassis(element, chassisTemplates, services);
501         ADD_FAILURE() << "Should not have reached this line.";
502     }
503     catch (const std::invalid_argument& e)
504     {
505         EXPECT_STREQ(e.what(), "Element is not a string");
506     }
507 
508     // Test where fails: Uses template: template_id value is invalid: No
509     // matching template
510     try
511     {
512         const json element = R"(
513             {
514               "template_id": "does_not_exist",
515               "template_variable_values": { "chassis_number": "2" }
516             }
517         )"_json;
518         MockServices services{};
519         parseChassis(element, chassisTemplates, services);
520         ADD_FAILURE() << "Should not have reached this line.";
521     }
522     catch (const std::invalid_argument& e)
523     {
524         EXPECT_STREQ(e.what(), "Invalid chassis template id: does_not_exist");
525     }
526 
527     // Test where fails: Uses template: template_variable_values value is
528     // invalid
529     try
530     {
531         const json element = R"(
532             {
533               "template_id": "foo_chassis",
534               "template_variable_values": { "chassis_number": 2 }
535             }
536         )"_json;
537         MockServices services{};
538         parseChassis(element, chassisTemplates, services);
539         ADD_FAILURE() << "Should not have reached this line.";
540     }
541     catch (const std::invalid_argument& e)
542     {
543         EXPECT_STREQ(e.what(), "Element is not a string");
544     }
545 
546     // Test where fails: Uses template: Invalid property specified
547     try
548     {
549         const json element = R"(
550             {
551               "template_id": "foo_chassis",
552               "template_variable_values": { "chassis_number": "2" },
553               "foo": "bar"
554             }
555         )"_json;
556         MockServices services{};
557         parseChassis(element, chassisTemplates, services);
558         ADD_FAILURE() << "Should not have reached this line.";
559     }
560     catch (const std::invalid_argument& e)
561     {
562         EXPECT_STREQ(e.what(), "Element contains an invalid property");
563     }
564 
565     // Test where fails: Uses template: Cannot parse properties in template
566     try
567     {
568         const json element = R"(
569             {
570               "template_id": "foo_chassis",
571               "template_variable_values": { "chassis_number": "0" }
572             }
573         )"_json;
574         MockServices services{};
575         parseChassis(element, chassisTemplates, services);
576         ADD_FAILURE() << "Should not have reached this line.";
577     }
578     catch (const std::invalid_argument& e)
579     {
580         EXPECT_STREQ(e.what(), "Invalid chassis number: Must be > 0");
581     }
582 }
583 
584 TEST(ConfigFileParserTests, ParseChassisArray)
585 {
586     // Constants used by multiple tests
587     const json fooTemplateElement = R"(
588         {
589           "id": "foo_chassis",
590           "number": "${chassis_number}",
591           "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
592           "power_sequencers": []
593         }
594     )"_json;
595     const json barTemplateElement = R"(
596         {
597           "id": "bar_chassis",
598           "number": "${chassis_number}",
599           "inventory_path": "/xyz/openbmc_project/inventory/system/bar_chassis${chassis_number}",
600           "power_sequencers": []
601         }
602     )"_json;
603     const std::map<std::string, JSONRefWrapper> chassisTemplates{
604         {"foo_chassis", fooTemplateElement},
605         {"bar_chassis", barTemplateElement}};
606 
607     // Test where works: Array is empty
608     {
609         const json element = R"(
610             [
611             ]
612         )"_json;
613         MockServices services{};
614         auto chassis = parseChassisArray(element, chassisTemplates, services);
615         EXPECT_EQ(chassis.size(), 0);
616     }
617 
618     // Test where works: Template not used
619     {
620         const json element = R"(
621             [
622               {
623                 "number": 1,
624                 "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
625                 "power_sequencers": []
626               },
627               {
628                 "number": 2,
629                 "inventory_path": "/xyz/openbmc_project/inventory/system/chassis2",
630                 "power_sequencers": []
631               }
632             ]
633         )"_json;
634         MockServices services{};
635         auto chassis = parseChassisArray(element, chassisTemplates, services);
636         EXPECT_EQ(chassis.size(), 2);
637         EXPECT_EQ(chassis[0]->getNumber(), 1);
638         EXPECT_EQ(chassis[0]->getInventoryPath(),
639                   "/xyz/openbmc_project/inventory/system/chassis1");
640         EXPECT_EQ(chassis[1]->getNumber(), 2);
641         EXPECT_EQ(chassis[1]->getInventoryPath(),
642                   "/xyz/openbmc_project/inventory/system/chassis2");
643     }
644 
645     // Test where works: Template used
646     {
647         const json element = R"(
648             [
649               {
650                 "template_id": "foo_chassis",
651                 "template_variable_values": { "chassis_number": "2" }
652               },
653               {
654                 "template_id": "bar_chassis",
655                 "template_variable_values": { "chassis_number": "3" }
656               }
657             ]
658         )"_json;
659         MockServices services{};
660         auto chassis = parseChassisArray(element, chassisTemplates, services);
661         EXPECT_EQ(chassis.size(), 2);
662         EXPECT_EQ(chassis[0]->getNumber(), 2);
663         EXPECT_EQ(chassis[0]->getInventoryPath(),
664                   "/xyz/openbmc_project/inventory/system/chassis2");
665         EXPECT_EQ(chassis[1]->getNumber(), 3);
666         EXPECT_EQ(chassis[1]->getInventoryPath(),
667                   "/xyz/openbmc_project/inventory/system/bar_chassis3");
668     }
669 
670     // Test where fails: Element is not an array
671     try
672     {
673         const json element = R"(
674             {
675                 "foo": "bar"
676             }
677         )"_json;
678         MockServices services{};
679         parseChassisArray(element, chassisTemplates, services);
680         ADD_FAILURE() << "Should not have reached this line.";
681     }
682     catch (const std::invalid_argument& e)
683     {
684         EXPECT_STREQ(e.what(), "Element is not an array");
685     }
686 
687     // Test where fails: Element within array is invalid
688     try
689     {
690         const json element = R"(
691             [
692               {
693                 "number": true,
694                 "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
695                 "power_sequencers": []
696               }
697             ]
698         )"_json;
699         MockServices services{};
700         parseChassisArray(element, chassisTemplates, services);
701         ADD_FAILURE() << "Should not have reached this line.";
702     }
703     catch (const std::invalid_argument& e)
704     {
705         EXPECT_STREQ(e.what(), "Element is not an integer");
706     }
707 
708     // Test where fails: Invalid variable value specified
709     try
710     {
711         const json element = R"(
712             [
713                 {
714                   "template_id": "foo_chassis",
715                   "template_variable_values": { "chassis_number": "two" }
716                 }
717             ]
718         )"_json;
719         MockServices services{};
720         parseChassisArray(element, chassisTemplates, services);
721         ADD_FAILURE() << "Should not have reached this line.";
722     }
723     catch (const std::invalid_argument& e)
724     {
725         EXPECT_STREQ(e.what(), "Element is not an integer");
726     }
727 }
728 
729 TEST(ConfigFileParserTests, ParseChassisProperties)
730 {
731     // Test where works: Parse chassis object without template/variables: Has
732     // comments property
733     {
734         const json element = R"(
735             {
736               "comments": [ "Chassis 1: Has all CPUs, fans, and PSUs" ],
737               "number": 1,
738               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
739               "power_sequencers": [
740                 {
741                   "type": "UCD90160",
742                   "i2c_interface": { "bus": 3, "address": "0x11" },
743                   "power_control_gpio_name": "power-chassis-control",
744                   "power_good_gpio_name": "power-chassis-good",
745                   "rails": [ { "name": "VDD_CPU0" }, { "name": "VCS_CPU1" } ]
746                 }
747               ]
748             }
749         )"_json;
750         bool isChassisTemplate{false};
751         std::map<std::string, std::string> variables{};
752         MockServices services{};
753         auto chassis = parseChassisProperties(element, isChassisTemplate,
754                                               variables, services);
755         EXPECT_EQ(chassis->getNumber(), 1);
756         EXPECT_EQ(chassis->getInventoryPath(),
757                   "/xyz/openbmc_project/inventory/system/chassis");
758         EXPECT_EQ(chassis->getPowerSequencers().size(), 1);
759         EXPECT_EQ(chassis->getPowerSequencers()[0]->getName(), "UCD90160");
760         EXPECT_EQ(chassis->getPowerSequencers()[0]->getBus(), 3);
761         EXPECT_EQ(chassis->getPowerSequencers()[0]->getAddress(), 0x11);
762         EXPECT_EQ(chassis->getPowerSequencers()[0]->getPowerControlGPIOName(),
763                   "power-chassis-control");
764         EXPECT_EQ(chassis->getPowerSequencers()[0]->getPowerGoodGPIOName(),
765                   "power-chassis-good");
766         EXPECT_EQ(chassis->getPowerSequencers()[0]->getRails().size(), 2);
767         EXPECT_EQ(chassis->getPowerSequencers()[0]->getRails()[0]->getName(),
768                   "VDD_CPU0");
769         EXPECT_EQ(chassis->getPowerSequencers()[0]->getRails()[1]->getName(),
770                   "VCS_CPU1");
771     }
772 
773     // Test where works: Parse chassis_template object with variables: No
774     // comments property
775     {
776         const json element = R"(
777             {
778               "id": "foo_chassis",
779               "number": "${chassis_number}",
780               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
781               "power_sequencers": [
782                 {
783                   "type": "UCD90320",
784                   "i2c_interface": { "bus": "${bus}", "address": "${address}" },
785                   "power_control_gpio_name": "power-chassis${chassis_number}-control",
786                   "power_good_gpio_name": "power-chassis${chassis_number}-good",
787                   "rails": [ { "name": "vio${chassis_number}" } ]
788                 }
789               ]
790             }
791         )"_json;
792         bool isChassisTemplate{true};
793         std::map<std::string, std::string> variables{
794             {"chassis_number", "2"}, {"bus", "12"}, {"address", "0x71"}};
795         MockServices services{};
796         auto chassis = parseChassisProperties(element, isChassisTemplate,
797                                               variables, services);
798         EXPECT_EQ(chassis->getNumber(), 2);
799         EXPECT_EQ(chassis->getInventoryPath(),
800                   "/xyz/openbmc_project/inventory/system/chassis2");
801         EXPECT_EQ(chassis->getPowerSequencers().size(), 1);
802         EXPECT_EQ(chassis->getPowerSequencers()[0]->getName(), "UCD90320");
803         EXPECT_EQ(chassis->getPowerSequencers()[0]->getBus(), 12);
804         EXPECT_EQ(chassis->getPowerSequencers()[0]->getAddress(), 0x71);
805         EXPECT_EQ(chassis->getPowerSequencers()[0]->getPowerControlGPIOName(),
806                   "power-chassis2-control");
807         EXPECT_EQ(chassis->getPowerSequencers()[0]->getPowerGoodGPIOName(),
808                   "power-chassis2-good");
809         EXPECT_EQ(chassis->getPowerSequencers()[0]->getRails().size(), 1);
810         EXPECT_EQ(chassis->getPowerSequencers()[0]->getRails()[0]->getName(),
811                   "vio2");
812     }
813 
814     // Test where fails: Element is not an object
815     try
816     {
817         const json element = R"( true )"_json;
818         bool isChassisTemplate{false};
819         std::map<std::string, std::string> variables{};
820         MockServices services{};
821         parseChassisProperties(element, isChassisTemplate, variables, services);
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 an object");
827     }
828 
829     // Test where fails: Required id property not specified in chassis template
830     try
831     {
832         const json element = R"(
833             {
834               "number": "${chassis_number}",
835               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
836               "power_sequencers": []
837             }
838         )"_json;
839         bool isChassisTemplate{true};
840         std::map<std::string, std::string> variables{{"chassis_number", "2"}};
841         MockServices services{};
842         parseChassisProperties(element, isChassisTemplate, variables, services);
843         ADD_FAILURE() << "Should not have reached this line.";
844     }
845     catch (const std::invalid_argument& e)
846     {
847         EXPECT_STREQ(e.what(), "Required property missing: id");
848     }
849 
850     // Test where fails: Required number property not specified
851     try
852     {
853         const json element = R"(
854             {
855               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
856               "power_sequencers": []
857             }
858         )"_json;
859         bool isChassisTemplate{false};
860         std::map<std::string, std::string> variables{};
861         MockServices services{};
862         parseChassisProperties(element, isChassisTemplate, variables, services);
863         ADD_FAILURE() << "Should not have reached this line.";
864     }
865     catch (const std::invalid_argument& e)
866     {
867         EXPECT_STREQ(e.what(), "Required property missing: number");
868     }
869 
870     // Test where fails: Required inventory_path property not specified
871     try
872     {
873         const json element = R"(
874             {
875               "id": "foo_chassis",
876               "number": "${chassis_number}",
877               "power_sequencers": []
878             }
879         )"_json;
880         bool isChassisTemplate{true};
881         std::map<std::string, std::string> variables{{"chassis_number", "2"}};
882         MockServices services{};
883         parseChassisProperties(element, isChassisTemplate, variables, services);
884         ADD_FAILURE() << "Should not have reached this line.";
885     }
886     catch (const std::invalid_argument& e)
887     {
888         EXPECT_STREQ(e.what(), "Required property missing: inventory_path");
889     }
890 
891     // Test where fails: Required power_sequencers property not specified
892     try
893     {
894         const json element = R"(
895             {
896               "number": 1,
897               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis"
898             }
899         )"_json;
900         bool isChassisTemplate{false};
901         std::map<std::string, std::string> variables{};
902         MockServices services{};
903         parseChassisProperties(element, isChassisTemplate, variables, services);
904         ADD_FAILURE() << "Should not have reached this line.";
905     }
906     catch (const std::invalid_argument& e)
907     {
908         EXPECT_STREQ(e.what(), "Required property missing: power_sequencers");
909     }
910 
911     // Test where fails: number value is invalid: Not an integer
912     try
913     {
914         const json element = R"(
915             {
916               "number": "two",
917               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
918               "power_sequencers": []
919             }
920         )"_json;
921         bool isChassisTemplate{false};
922         std::map<std::string, std::string> variables{};
923         MockServices services{};
924         parseChassisProperties(element, isChassisTemplate, variables, services);
925         ADD_FAILURE() << "Should not have reached this line.";
926     }
927     catch (const std::invalid_argument& e)
928     {
929         EXPECT_STREQ(e.what(), "Element is not an integer");
930     }
931 
932     // Test where fails: number value is invalid: Equal to 0
933     try
934     {
935         const json element = R"(
936             {
937               "number": 0,
938               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
939               "power_sequencers": []
940             }
941         )"_json;
942         bool isChassisTemplate{false};
943         std::map<std::string, std::string> variables{};
944         MockServices services{};
945         parseChassisProperties(element, isChassisTemplate, variables, services);
946         ADD_FAILURE() << "Should not have reached this line.";
947     }
948     catch (const std::invalid_argument& e)
949     {
950         EXPECT_STREQ(e.what(), "Invalid chassis number: Must be > 0");
951     }
952 
953     // Test where fails: inventory_path value is invalid
954     try
955     {
956         const json element = R"(
957             {
958               "number": 1,
959               "inventory_path": "",
960               "power_sequencers": []
961             }
962         )"_json;
963         bool isChassisTemplate{false};
964         std::map<std::string, std::string> variables{};
965         MockServices services{};
966         parseChassisProperties(element, isChassisTemplate, variables, services);
967         ADD_FAILURE() << "Should not have reached this line.";
968     }
969     catch (const std::invalid_argument& e)
970     {
971         EXPECT_STREQ(e.what(), "Element contains an empty string");
972     }
973 
974     // Test where fails: power_sequencers value is invalid
975     try
976     {
977         const json element = R"(
978             {
979               "number": 1,
980               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
981               "power_sequencers": { "name": "foo" }
982             }
983         )"_json;
984         bool isChassisTemplate{false};
985         std::map<std::string, std::string> variables{};
986         MockServices services{};
987         parseChassisProperties(element, isChassisTemplate, variables, services);
988         ADD_FAILURE() << "Should not have reached this line.";
989     }
990     catch (const std::invalid_argument& e)
991     {
992         EXPECT_STREQ(e.what(), "Element is not an array");
993     }
994 
995     // Test where fails: Invalid property specified
996     try
997     {
998         const json element = R"(
999             {
1000               "foo": "bar",
1001               "number": 1,
1002               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
1003               "power_sequencers": []
1004             }
1005         )"_json;
1006         bool isChassisTemplate{false};
1007         std::map<std::string, std::string> variables{};
1008         MockServices services{};
1009         parseChassisProperties(element, isChassisTemplate, variables, services);
1010         ADD_FAILURE() << "Should not have reached this line.";
1011     }
1012     catch (const std::invalid_argument& e)
1013     {
1014         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1015     }
1016 
1017     // Test where fails: Invalid variable value specified
1018     try
1019     {
1020         const json element = R"(
1021             {
1022               "id": "foo_chassis",
1023               "number": "${chassis_number}",
1024               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1025               "power_sequencers": []
1026             }
1027         )"_json;
1028         bool isChassisTemplate{true};
1029         std::map<std::string, std::string> variables{{"chassis_number", "two"}};
1030         MockServices services{};
1031         parseChassisProperties(element, isChassisTemplate, variables, services);
1032         ADD_FAILURE() << "Should not have reached this line.";
1033     }
1034     catch (const std::invalid_argument& e)
1035     {
1036         EXPECT_STREQ(e.what(), "Element is not an integer");
1037     }
1038 }
1039 
1040 TEST(ConfigFileParserTests, ParseChassisTemplate)
1041 {
1042     // Test where works: comments specified
1043     {
1044         const json element = R"(
1045             {
1046               "comments": [ "This is a template for the foo chassis type",
1047                             "Chassis contains a UCD90320 power sequencer" ],
1048               "id": "foo_chassis",
1049               "number": "${chassis_number}",
1050               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1051               "power_sequencers": [
1052                 {
1053                   "type": "UCD90320",
1054                   "i2c_interface": { "bus": "${bus}", "address": "0x11" },
1055                   "power_control_gpio_name": "power-chassis${chassis_number}-control",
1056                   "power_good_gpio_name": "power-chassis${chassis_number}-good",
1057                   "rails": [ { "name": "VDD_CPU0" }, { "name": "VCS_CPU1" } ]
1058                 }
1059               ]
1060             }
1061         )"_json;
1062         auto [id, jsonRef] = parseChassisTemplate(element);
1063         EXPECT_EQ(id, "foo_chassis");
1064         EXPECT_EQ(jsonRef.get()["number"], "${chassis_number}");
1065         EXPECT_EQ(jsonRef.get()["power_sequencers"].size(), 1);
1066         EXPECT_EQ(jsonRef.get()["power_sequencers"][0]["type"], "UCD90320");
1067     }
1068 
1069     // Test where works: comments not specified
1070     {
1071         const json element = R"(
1072             {
1073               "id": "foo_chassis",
1074               "number": "${chassis_number}",
1075               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1076               "power_sequencers": []
1077             }
1078         )"_json;
1079         auto [id, jsonRef] = parseChassisTemplate(element);
1080         EXPECT_EQ(id, "foo_chassis");
1081         EXPECT_EQ(jsonRef.get()["number"], "${chassis_number}");
1082         EXPECT_EQ(
1083             jsonRef.get()["inventory_path"],
1084             "/xyz/openbmc_project/inventory/system/chassis${chassis_number}");
1085         EXPECT_EQ(jsonRef.get()["power_sequencers"].size(), 0);
1086     }
1087 
1088     // Test where fails: Element is not an object
1089     try
1090     {
1091         const json element = R"( [ "vdda", "vddb" ] )"_json;
1092         parseChassisTemplate(element);
1093         ADD_FAILURE() << "Should not have reached this line.";
1094     }
1095     catch (const std::invalid_argument& e)
1096     {
1097         EXPECT_STREQ(e.what(), "Element is not an object");
1098     }
1099 
1100     // Test where fails: Required id property not specified
1101     try
1102     {
1103         const json element = R"(
1104             {
1105               "number": "${chassis_number}",
1106               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1107               "power_sequencers": []
1108             }
1109         )"_json;
1110         parseChassisTemplate(element);
1111         ADD_FAILURE() << "Should not have reached this line.";
1112     }
1113     catch (const std::invalid_argument& e)
1114     {
1115         EXPECT_STREQ(e.what(), "Required property missing: id");
1116     }
1117 
1118     // Test where fails: Required number property not specified
1119     try
1120     {
1121         const json element = R"(
1122             {
1123               "id": "foo_chassis",
1124               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1125               "power_sequencers": []
1126             }
1127         )"_json;
1128         parseChassisTemplate(element);
1129         ADD_FAILURE() << "Should not have reached this line.";
1130     }
1131     catch (const std::invalid_argument& e)
1132     {
1133         EXPECT_STREQ(e.what(), "Required property missing: number");
1134     }
1135 
1136     // Test where fails: Required inventory_path property not specified
1137     try
1138     {
1139         const json element = R"(
1140             {
1141               "id": "foo_chassis",
1142               "number": "${chassis_number}",
1143               "power_sequencers": []
1144             }
1145         )"_json;
1146         parseChassisTemplate(element);
1147         ADD_FAILURE() << "Should not have reached this line.";
1148     }
1149     catch (const std::invalid_argument& e)
1150     {
1151         EXPECT_STREQ(e.what(), "Required property missing: inventory_path");
1152     }
1153 
1154     // Test where fails: Required power_sequencers property not specified
1155     try
1156     {
1157         const json element = R"(
1158             {
1159               "id": "foo_chassis",
1160               "number": "${chassis_number}",
1161               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}"
1162             }
1163         )"_json;
1164         parseChassisTemplate(element);
1165         ADD_FAILURE() << "Should not have reached this line.";
1166     }
1167     catch (const std::invalid_argument& e)
1168     {
1169         EXPECT_STREQ(e.what(), "Required property missing: power_sequencers");
1170     }
1171 
1172     // Test where fails: id value is invalid
1173     try
1174     {
1175         const json element = R"(
1176             {
1177               "id": 13,
1178               "number": "${chassis_number}",
1179               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1180               "power_sequencers": []
1181             }
1182         )"_json;
1183         parseChassisTemplate(element);
1184         ADD_FAILURE() << "Should not have reached this line.";
1185     }
1186     catch (const std::invalid_argument& e)
1187     {
1188         EXPECT_STREQ(e.what(), "Element is not a string");
1189     }
1190 
1191     // Test where fails: Invalid property specified
1192     try
1193     {
1194         const json element = R"(
1195             {
1196               "id": "foo_chassis",
1197               "number": "${chassis_number}",
1198               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1199               "power_sequencers": [],
1200               "foo": "bar"
1201             }
1202         )"_json;
1203         parseChassisTemplate(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 contains an invalid property");
1209     }
1210 }
1211 
1212 TEST(ConfigFileParserTests, ParseChassisTemplateArray)
1213 {
1214     // Test where works: Array is empty
1215     {
1216         const json element = R"(
1217             [
1218             ]
1219         )"_json;
1220         auto chassisTemplates = parseChassisTemplateArray(element);
1221         EXPECT_EQ(chassisTemplates.size(), 0);
1222     }
1223 
1224     // Test where works: Array is not empty
1225     {
1226         const json element = R"(
1227             [
1228               {
1229                 "id": "foo_chassis",
1230                 "number": "${chassis_number}",
1231                 "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1232                 "power_sequencers": []
1233               },
1234               {
1235                 "id": "bar_chassis",
1236                 "number": "${number}",
1237                 "inventory_path": "/xyz/openbmc_project/inventory/system/bar_chassis${number}",
1238                 "power_sequencers": []
1239               }
1240             ]
1241         )"_json;
1242         auto chassisTemplates = parseChassisTemplateArray(element);
1243         EXPECT_EQ(chassisTemplates.size(), 2);
1244         EXPECT_EQ(chassisTemplates.at("foo_chassis").get()["number"],
1245                   "${chassis_number}");
1246         EXPECT_EQ(
1247             chassisTemplates.at("foo_chassis").get()["inventory_path"],
1248             "/xyz/openbmc_project/inventory/system/chassis${chassis_number}");
1249         EXPECT_EQ(chassisTemplates.at("bar_chassis").get()["number"],
1250                   "${number}");
1251         EXPECT_EQ(chassisTemplates.at("bar_chassis").get()["inventory_path"],
1252                   "/xyz/openbmc_project/inventory/system/bar_chassis${number}");
1253     }
1254 
1255     // Test where fails: Element is not an array
1256     try
1257     {
1258         const json element = R"(
1259             {
1260                 "foo": "bar"
1261             }
1262         )"_json;
1263         parseChassisTemplateArray(element);
1264         ADD_FAILURE() << "Should not have reached this line.";
1265     }
1266     catch (const std::invalid_argument& e)
1267     {
1268         EXPECT_STREQ(e.what(), "Element is not an array");
1269     }
1270 
1271     // Test where fails: Element within array is invalid
1272     try
1273     {
1274         const json element = R"(
1275             [
1276               {
1277                 "id": "foo_chassis",
1278                 "number": "${chassis_number}",
1279                 "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1280                 "power_sequencers": []
1281               },
1282               {
1283                 "id": "bar_chassis"
1284               }
1285             ]
1286         )"_json;
1287         parseChassisTemplateArray(element);
1288         ADD_FAILURE() << "Should not have reached this line.";
1289     }
1290     catch (const std::invalid_argument& e)
1291     {
1292         EXPECT_STREQ(e.what(), "Required property missing: number");
1293     }
1294 }
1295 
1296 TEST(ConfigFileParserTests, ParseGPIO)
1297 {
1298     // Test where works: Only required properties specified
1299     {
1300         const json element = R"(
1301             {
1302                 "line": 60
1303             }
1304         )"_json;
1305         std::map<std::string, std::string> variables{};
1306         auto gpio = parseGPIO(element, variables);
1307         EXPECT_EQ(gpio.line, 60);
1308         EXPECT_FALSE(gpio.activeLow);
1309     }
1310 
1311     // Test where works: All properties specified
1312     {
1313         const json element = R"(
1314             {
1315                 "line": 131,
1316                 "active_low": true
1317             }
1318         )"_json;
1319         std::map<std::string, std::string> variables{};
1320         auto gpio = parseGPIO(element, variables);
1321         EXPECT_EQ(gpio.line, 131);
1322         EXPECT_TRUE(gpio.activeLow);
1323     }
1324 
1325     // Test where works: Variables specified
1326     {
1327         const json element = R"(
1328             {
1329                 "line": "${line}",
1330                 "active_low": "${active_low}"
1331             }
1332         )"_json;
1333         std::map<std::string, std::string> variables{{"line", "54"},
1334                                                      {"active_low", "false"}};
1335         auto gpio = parseGPIO(element, variables);
1336         EXPECT_EQ(gpio.line, 54);
1337         EXPECT_FALSE(gpio.activeLow);
1338     }
1339 
1340     // Test where fails: Element is not an object
1341     try
1342     {
1343         const json element = R"( [ "vdda", "vddb" ] )"_json;
1344         std::map<std::string, std::string> variables{};
1345         parseGPIO(element, variables);
1346         ADD_FAILURE() << "Should not have reached this line.";
1347     }
1348     catch (const std::invalid_argument& e)
1349     {
1350         EXPECT_STREQ(e.what(), "Element is not an object");
1351     }
1352 
1353     // Test where fails: Required line property not specified
1354     try
1355     {
1356         const json element = R"(
1357             {
1358                 "active_low": true
1359             }
1360         )"_json;
1361         std::map<std::string, std::string> variables{};
1362         parseGPIO(element, variables);
1363         ADD_FAILURE() << "Should not have reached this line.";
1364     }
1365     catch (const std::invalid_argument& e)
1366     {
1367         EXPECT_STREQ(e.what(), "Required property missing: line");
1368     }
1369 
1370     // Test where fails: line value is invalid
1371     try
1372     {
1373         const json element = R"(
1374             {
1375                 "line": -131,
1376                 "active_low": true
1377             }
1378         )"_json;
1379         std::map<std::string, std::string> variables{};
1380         parseGPIO(element, variables);
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 an unsigned integer");
1386     }
1387 
1388     // Test where fails: active_low value is invalid
1389     try
1390     {
1391         const json element = R"(
1392             {
1393                 "line": 131,
1394                 "active_low": "true"
1395             }
1396         )"_json;
1397         std::map<std::string, std::string> variables{};
1398         parseGPIO(element, variables);
1399         ADD_FAILURE() << "Should not have reached this line.";
1400     }
1401     catch (const std::invalid_argument& e)
1402     {
1403         EXPECT_STREQ(e.what(), "Element is not a boolean");
1404     }
1405 
1406     // Test where fails: Invalid property specified
1407     try
1408     {
1409         const json element = R"(
1410             {
1411                 "line": 131,
1412                 "foo": "bar"
1413             }
1414         )"_json;
1415         std::map<std::string, std::string> variables{};
1416         parseGPIO(element, variables);
1417         ADD_FAILURE() << "Should not have reached this line.";
1418     }
1419     catch (const std::invalid_argument& e)
1420     {
1421         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1422     }
1423 
1424     // Test where fails: Invalid variable value specified
1425     try
1426     {
1427         const json element = R"(
1428             {
1429                 "line": "${line}",
1430                 "active_low": "${active_low}"
1431             }
1432         )"_json;
1433         std::map<std::string, std::string> variables{{"line", "-1"},
1434                                                      {"active_low", "false"}};
1435         parseGPIO(element, variables);
1436         ADD_FAILURE() << "Should not have reached this line.";
1437     }
1438     catch (const std::invalid_argument& e)
1439     {
1440         EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
1441     }
1442 }
1443 
1444 TEST(ConfigFileParserTests, ParseI2CInterface)
1445 {
1446     // Test where works: No variables
1447     {
1448         const json element = R"(
1449             {
1450                 "bus": 2,
1451                 "address": "0x70"
1452             }
1453         )"_json;
1454         std::map<std::string, std::string> variables{};
1455         auto [bus, address] = parseI2CInterface(element, variables);
1456         EXPECT_EQ(bus, 2);
1457         EXPECT_EQ(address, 0x70);
1458     }
1459 
1460     // Test where works: Variables specified
1461     {
1462         const json element = R"(
1463             {
1464                 "bus": "${bus}",
1465                 "address": "${address}"
1466             }
1467         )"_json;
1468         std::map<std::string, std::string> variables{{"bus", "3"},
1469                                                      {"address", "0x23"}};
1470         auto [bus, address] = parseI2CInterface(element, variables);
1471         EXPECT_EQ(bus, 3);
1472         EXPECT_EQ(address, 0x23);
1473     }
1474 
1475     // Test where fails: Element is not an object
1476     try
1477     {
1478         const json element = R"( [ 1, "0x70" ] )"_json;
1479         std::map<std::string, std::string> variables{};
1480         parseI2CInterface(element, variables);
1481         ADD_FAILURE() << "Should not have reached this line.";
1482     }
1483     catch (const std::invalid_argument& e)
1484     {
1485         EXPECT_STREQ(e.what(), "Element is not an object");
1486     }
1487 
1488     // Test where fails: Required bus property not specified
1489     try
1490     {
1491         const json element = R"(
1492             {
1493                 "address": "0x70"
1494             }
1495         )"_json;
1496         std::map<std::string, std::string> variables{};
1497         parseI2CInterface(element, variables);
1498         ADD_FAILURE() << "Should not have reached this line.";
1499     }
1500     catch (const std::invalid_argument& e)
1501     {
1502         EXPECT_STREQ(e.what(), "Required property missing: bus");
1503     }
1504 
1505     // Test where fails: Required address property not specified
1506     try
1507     {
1508         const json element = R"(
1509             {
1510                 "bus": 2
1511             }
1512         )"_json;
1513         std::map<std::string, std::string> variables{};
1514         parseI2CInterface(element, variables);
1515         ADD_FAILURE() << "Should not have reached this line.";
1516     }
1517     catch (const std::invalid_argument& e)
1518     {
1519         EXPECT_STREQ(e.what(), "Required property missing: address");
1520     }
1521 
1522     // Test where fails: bus value is invalid
1523     try
1524     {
1525         const json element = R"(
1526             {
1527                 "bus": 1.1,
1528                 "address": "0x70"
1529             }
1530         )"_json;
1531         std::map<std::string, std::string> variables{};
1532         parseI2CInterface(element, variables);
1533         ADD_FAILURE() << "Should not have reached this line.";
1534     }
1535     catch (const std::invalid_argument& e)
1536     {
1537         EXPECT_STREQ(e.what(), "Element is not an integer");
1538     }
1539 
1540     // Test where fails: address value is invalid
1541     try
1542     {
1543         const json element = R"(
1544             {
1545                 "bus": 2,
1546                 "address": 70
1547             }
1548         )"_json;
1549         std::map<std::string, std::string> variables{};
1550         parseI2CInterface(element, variables);
1551         ADD_FAILURE() << "Should not have reached this line.";
1552     }
1553     catch (const std::invalid_argument& e)
1554     {
1555         EXPECT_STREQ(e.what(), "Element is not a string");
1556     }
1557 
1558     // Test where fails: Invalid property specified
1559     try
1560     {
1561         const json element = R"(
1562             {
1563                 "bus": 2,
1564                 "address": "0x70",
1565                 "foo": "bar"
1566             }
1567         )"_json;
1568         std::map<std::string, std::string> variables{};
1569         parseI2CInterface(element, variables);
1570         ADD_FAILURE() << "Should not have reached this line.";
1571     }
1572     catch (const std::invalid_argument& e)
1573     {
1574         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1575     }
1576 
1577     // Test where fails: Invalid variable value specified
1578     try
1579     {
1580         const json element = R"(
1581             {
1582                 "bus": "${bus}",
1583                 "address": "${address}"
1584             }
1585         )"_json;
1586         std::map<std::string, std::string> variables{{"bus", "foo"},
1587                                                      {"address", "0x23"}};
1588         parseI2CInterface(element, variables);
1589         ADD_FAILURE() << "Should not have reached this line.";
1590     }
1591     catch (const std::invalid_argument& e)
1592     {
1593         EXPECT_STREQ(e.what(), "Element is not an integer");
1594     }
1595 }
1596 
1597 TEST(ConfigFileParserTests, ParsePowerSequencer)
1598 {
1599     // Test where works: Has comments property: Type is "UCD90160"
1600     {
1601         const json element = R"(
1602             {
1603               "comments": [ "Power sequencer in chassis 1",
1604                             "Controls VDD rails" ],
1605               "type": "UCD90160",
1606               "i2c_interface": { "bus": 3, "address": "0x11" },
1607               "power_control_gpio_name": "power-chassis-control",
1608               "power_good_gpio_name": "power-chassis-good",
1609               "rails": [ { "name": "VDD_CPU0" }, { "name": "VCS_CPU1" } ]
1610             }
1611         )"_json;
1612         std::map<std::string, std::string> variables{};
1613         MockServices services{};
1614         auto powerSequencer = parsePowerSequencer(element, variables, services);
1615         EXPECT_EQ(powerSequencer->getName(), "UCD90160");
1616         EXPECT_EQ(powerSequencer->getBus(), 3);
1617         EXPECT_EQ(powerSequencer->getAddress(), 0x11);
1618         EXPECT_EQ(powerSequencer->getPowerControlGPIOName(),
1619                   "power-chassis-control");
1620         EXPECT_EQ(powerSequencer->getPowerGoodGPIOName(), "power-chassis-good");
1621         EXPECT_EQ(powerSequencer->getRails().size(), 2);
1622         EXPECT_EQ(powerSequencer->getRails()[0]->getName(), "VDD_CPU0");
1623         EXPECT_EQ(powerSequencer->getRails()[1]->getName(), "VCS_CPU1");
1624     }
1625 
1626     // Test where works: No comments property: Variables specified: Type is
1627     // "UCD90320"
1628     {
1629         const json element = R"(
1630             {
1631               "type": "${type}",
1632               "i2c_interface": { "bus": "${bus}", "address": "${address}" },
1633               "power_control_gpio_name": "${power_control_gpio_name}",
1634               "power_good_gpio_name": "${power_good_gpio_name}",
1635               "rails": [ { "name": "${rail1}" }, { "name": "${rail2}" } ]
1636             }
1637         )"_json;
1638         std::map<std::string, std::string> variables{
1639             {"type", "UCD90320"},
1640             {"bus", "4"},
1641             {"address", "0x24"},
1642             {"power_control_gpio_name", "power_on"},
1643             {"power_good_gpio_name", "pgood"},
1644             {"rail1", "cpu1"},
1645             {"rail2", "cpu2"}};
1646         MockServices services{};
1647         auto powerSequencer = parsePowerSequencer(element, variables, services);
1648         EXPECT_EQ(powerSequencer->getName(), "UCD90320");
1649         EXPECT_EQ(powerSequencer->getBus(), 4);
1650         EXPECT_EQ(powerSequencer->getAddress(), 0x24);
1651         EXPECT_EQ(powerSequencer->getPowerControlGPIOName(), "power_on");
1652         EXPECT_EQ(powerSequencer->getPowerGoodGPIOName(), "pgood");
1653         EXPECT_EQ(powerSequencer->getRails().size(), 2);
1654         EXPECT_EQ(powerSequencer->getRails()[0]->getName(), "cpu1");
1655         EXPECT_EQ(powerSequencer->getRails()[1]->getName(), "cpu2");
1656     }
1657 
1658     // Test where fails: Element is not an object
1659     try
1660     {
1661         const json element = R"( [ "vdda", "vddb" ] )"_json;
1662         std::map<std::string, std::string> variables{};
1663         MockServices services{};
1664         parsePowerSequencer(element, variables, services);
1665         ADD_FAILURE() << "Should not have reached this line.";
1666     }
1667     catch (const std::invalid_argument& e)
1668     {
1669         EXPECT_STREQ(e.what(), "Element is not an object");
1670     }
1671 
1672     // Test where fails: Required type property not specified
1673     try
1674     {
1675         const json element = R"(
1676             {
1677               "i2c_interface": { "bus": 3, "address": "0x11" },
1678               "power_control_gpio_name": "power-chassis-control",
1679               "power_good_gpio_name": "power-chassis-good",
1680               "rails": []
1681             }
1682         )"_json;
1683         std::map<std::string, std::string> variables{};
1684         MockServices services{};
1685         parsePowerSequencer(element, variables, services);
1686         ADD_FAILURE() << "Should not have reached this line.";
1687     }
1688     catch (const std::invalid_argument& e)
1689     {
1690         EXPECT_STREQ(e.what(), "Required property missing: type");
1691     }
1692 
1693     // Test where fails: Required i2c_interface property not specified
1694     try
1695     {
1696         const json element = R"(
1697             {
1698               "type": "UCD90320",
1699               "power_control_gpio_name": "power-chassis-control",
1700               "power_good_gpio_name": "power-chassis-good",
1701               "rails": []
1702             }
1703         )"_json;
1704         std::map<std::string, std::string> variables{};
1705         MockServices services{};
1706         parsePowerSequencer(element, variables, services);
1707         ADD_FAILURE() << "Should not have reached this line.";
1708     }
1709     catch (const std::invalid_argument& e)
1710     {
1711         EXPECT_STREQ(e.what(), "Required property missing: i2c_interface");
1712     }
1713 
1714     // Test where fails: Required power_control_gpio_name property not specified
1715     try
1716     {
1717         const json element = R"(
1718             {
1719               "type": "UCD90320",
1720               "i2c_interface": { "bus": 3, "address": "0x11" },
1721               "power_good_gpio_name": "power-chassis-good",
1722               "rails": []
1723             }
1724         )"_json;
1725         std::map<std::string, std::string> variables{};
1726         MockServices services{};
1727         parsePowerSequencer(element, variables, services);
1728         ADD_FAILURE() << "Should not have reached this line.";
1729     }
1730     catch (const std::invalid_argument& e)
1731     {
1732         EXPECT_STREQ(e.what(),
1733                      "Required property missing: power_control_gpio_name");
1734     }
1735 
1736     // Test where fails: Required power_good_gpio_name property not specified
1737     try
1738     {
1739         const json element = R"(
1740             {
1741               "type": "UCD90320",
1742               "i2c_interface": { "bus": 3, "address": "0x11" },
1743               "power_control_gpio_name": "power-chassis-control",
1744               "rails": []
1745             }
1746         )"_json;
1747         std::map<std::string, std::string> variables{};
1748         MockServices services{};
1749         parsePowerSequencer(element, variables, services);
1750         ADD_FAILURE() << "Should not have reached this line.";
1751     }
1752     catch (const std::invalid_argument& e)
1753     {
1754         EXPECT_STREQ(e.what(),
1755                      "Required property missing: power_good_gpio_name");
1756     }
1757 
1758     // Test where fails: Required rails property not specified
1759     try
1760     {
1761         const json element = R"(
1762             {
1763               "type": "UCD90320",
1764               "i2c_interface": { "bus": 3, "address": "0x11" },
1765               "power_control_gpio_name": "power-chassis-control",
1766               "power_good_gpio_name": "power-chassis-good"
1767             }
1768         )"_json;
1769         std::map<std::string, std::string> variables{};
1770         MockServices services{};
1771         parsePowerSequencer(element, variables, services);
1772         ADD_FAILURE() << "Should not have reached this line.";
1773     }
1774     catch (const std::invalid_argument& e)
1775     {
1776         EXPECT_STREQ(e.what(), "Required property missing: rails");
1777     }
1778 
1779     // Test where fails: type value is invalid: Not a string
1780     try
1781     {
1782         const json element = R"(
1783             {
1784               "type": true,
1785               "i2c_interface": { "bus": 3, "address": "0x11" },
1786               "power_control_gpio_name": "power-chassis-control",
1787               "power_good_gpio_name": "power-chassis-good",
1788               "rails": []
1789             }
1790         )"_json;
1791         std::map<std::string, std::string> variables{};
1792         MockServices services{};
1793         parsePowerSequencer(element, variables, services);
1794         ADD_FAILURE() << "Should not have reached this line.";
1795     }
1796     catch (const std::invalid_argument& e)
1797     {
1798         EXPECT_STREQ(e.what(), "Element is not a string");
1799     }
1800 
1801     // Test where fails: type value is invalid: Not a supported type
1802     try
1803     {
1804         const json element = R"(
1805             {
1806               "type": "foo_bar",
1807               "i2c_interface": { "bus": 3, "address": "0x11" },
1808               "power_control_gpio_name": "power-chassis-control",
1809               "power_good_gpio_name": "power-chassis-good",
1810               "rails": []
1811             }
1812         )"_json;
1813         std::map<std::string, std::string> variables{};
1814         MockServices services{};
1815         parsePowerSequencer(element, variables, services);
1816         ADD_FAILURE() << "Should not have reached this line.";
1817     }
1818     catch (const std::invalid_argument& e)
1819     {
1820         EXPECT_STREQ(e.what(), "Invalid power sequencer type: foo_bar");
1821     }
1822 
1823     // Test where fails: i2c_interface value is invalid
1824     try
1825     {
1826         const json element = R"(
1827             {
1828               "type": "UCD90320",
1829               "i2c_interface": 3,
1830               "power_control_gpio_name": "power-chassis-control",
1831               "power_good_gpio_name": "power-chassis-good",
1832               "rails": []
1833             }
1834         )"_json;
1835         std::map<std::string, std::string> variables{};
1836         MockServices services{};
1837         parsePowerSequencer(element, variables, services);
1838         ADD_FAILURE() << "Should not have reached this line.";
1839     }
1840     catch (const std::invalid_argument& e)
1841     {
1842         EXPECT_STREQ(e.what(), "Element is not an object");
1843     }
1844 
1845     // Test where fails: power_control_gpio_name value is invalid
1846     try
1847     {
1848         const json element = R"(
1849             {
1850               "type": "UCD90320",
1851               "i2c_interface": { "bus": 3, "address": "0x11" },
1852               "power_control_gpio_name": [],
1853               "power_good_gpio_name": "power-chassis-good",
1854               "rails": []
1855             }
1856         )"_json;
1857         std::map<std::string, std::string> variables{};
1858         MockServices services{};
1859         parsePowerSequencer(element, variables, services);
1860         ADD_FAILURE() << "Should not have reached this line.";
1861     }
1862     catch (const std::invalid_argument& e)
1863     {
1864         EXPECT_STREQ(e.what(), "Element is not a string");
1865     }
1866 
1867     // Test where fails: power_good_gpio_name value is invalid
1868     try
1869     {
1870         const json element = R"(
1871             {
1872               "type": "UCD90320",
1873               "i2c_interface": { "bus": 3, "address": "0x11" },
1874               "power_control_gpio_name": "power-chassis-control",
1875               "power_good_gpio_name": 12,
1876               "rails": []
1877             }
1878         )"_json;
1879         std::map<std::string, std::string> variables{};
1880         MockServices services{};
1881         parsePowerSequencer(element, variables, services);
1882         ADD_FAILURE() << "Should not have reached this line.";
1883     }
1884     catch (const std::invalid_argument& e)
1885     {
1886         EXPECT_STREQ(e.what(), "Element is not a string");
1887     }
1888 
1889     // Test where fails: rails value is invalid
1890     try
1891     {
1892         const json element = R"(
1893             {
1894               "type": "UCD90320",
1895               "i2c_interface": { "bus": 3, "address": "0x11" },
1896               "power_control_gpio_name": "power-chassis-control",
1897               "power_good_gpio_name": "power-chassis-good",
1898               "rails": [ { "name": 33 } ]
1899             }
1900         )"_json;
1901         std::map<std::string, std::string> variables{};
1902         MockServices services{};
1903         parsePowerSequencer(element, variables, services);
1904         ADD_FAILURE() << "Should not have reached this line.";
1905     }
1906     catch (const std::invalid_argument& e)
1907     {
1908         EXPECT_STREQ(e.what(), "Element is not a string");
1909     }
1910 
1911     // Test where fails: Invalid property specified
1912     try
1913     {
1914         const json element = R"(
1915             {
1916               "type": "UCD90320",
1917               "i2c_interface": { "bus": 3, "address": "0x11" },
1918               "power_control_gpio_name": "power-chassis-control",
1919               "power_good_gpio_name": "power-chassis-good",
1920               "driver_name": "foo",
1921               "rails": []
1922             }
1923         )"_json;
1924         std::map<std::string, std::string> variables{};
1925         MockServices services{};
1926         parsePowerSequencer(element, variables, services);
1927         ADD_FAILURE() << "Should not have reached this line.";
1928     }
1929     catch (const std::invalid_argument& e)
1930     {
1931         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1932     }
1933 
1934     // Test where fails: Invalid variable value specified
1935     try
1936     {
1937         const json element = R"(
1938             {
1939               "type": "UCD90320",
1940               "i2c_interface": { "bus": "${bus}", "address": "0x11" },
1941               "power_control_gpio_name": "power-chassis-control",
1942               "power_good_gpio_name": "power-chassis-good",
1943               "rails": []
1944             }
1945         )"_json;
1946         std::map<std::string, std::string> variables{{"bus", "two"}};
1947         MockServices services{};
1948         parsePowerSequencer(element, variables, services);
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 integer");
1954     }
1955 }
1956 
1957 TEST(ConfigFileParserTests, ParsePowerSequencerArray)
1958 {
1959     // Test where works: Array is empty
1960     {
1961         const json element = R"(
1962             [
1963             ]
1964         )"_json;
1965         std::map<std::string, std::string> variables{};
1966         MockServices services{};
1967         auto powerSequencers =
1968             parsePowerSequencerArray(element, variables, services);
1969         EXPECT_EQ(powerSequencers.size(), 0);
1970     }
1971 
1972     // Test where works: Array is not empty
1973     {
1974         const json element = R"(
1975             [
1976               {
1977                 "type": "UCD90160",
1978                 "i2c_interface": { "bus": 3, "address": "0x11" },
1979                 "power_control_gpio_name": "power-chassis-control1",
1980                 "power_good_gpio_name": "power-chassis-good1",
1981                 "rails": []
1982               },
1983               {
1984                 "type": "UCD90320",
1985                 "i2c_interface": { "bus": 4, "address": "0x70" },
1986                 "power_control_gpio_name": "power-chassis-control2",
1987                 "power_good_gpio_name": "power-chassis-good2",
1988                 "rails": []
1989               }
1990             ]
1991         )"_json;
1992         std::map<std::string, std::string> variables{};
1993         MockServices services{};
1994         auto powerSequencers =
1995             parsePowerSequencerArray(element, variables, services);
1996         EXPECT_EQ(powerSequencers.size(), 2);
1997         EXPECT_EQ(powerSequencers[0]->getName(), "UCD90160");
1998         EXPECT_EQ(powerSequencers[0]->getBus(), 3);
1999         EXPECT_EQ(powerSequencers[0]->getAddress(), 0x11);
2000         EXPECT_EQ(powerSequencers[1]->getName(), "UCD90320");
2001         EXPECT_EQ(powerSequencers[1]->getBus(), 4);
2002         EXPECT_EQ(powerSequencers[1]->getAddress(), 0x70);
2003     }
2004 
2005     // Test where works: Variables specified
2006     {
2007         const json element = R"(
2008             [
2009               {
2010                 "type": "UCD90160",
2011                 "i2c_interface": { "bus": "${bus1}", "address": "${address1}" },
2012                 "power_control_gpio_name": "power-chassis-control1",
2013                 "power_good_gpio_name": "power-chassis-good1",
2014                 "rails": []
2015               },
2016               {
2017                 "type": "UCD90320",
2018                 "i2c_interface": { "bus": "${bus2}", "address": "${address2}" },
2019                 "power_control_gpio_name": "power-chassis-control2",
2020                 "power_good_gpio_name": "power-chassis-good2",
2021                 "rails": []
2022               }
2023             ]
2024         )"_json;
2025         std::map<std::string, std::string> variables{
2026             {"bus1", "5"},
2027             {"address1", "0x22"},
2028             {"bus2", "7"},
2029             {"address2", "0x49"}};
2030         MockServices services{};
2031         auto powerSequencers =
2032             parsePowerSequencerArray(element, variables, services);
2033         EXPECT_EQ(powerSequencers.size(), 2);
2034         EXPECT_EQ(powerSequencers[0]->getName(), "UCD90160");
2035         EXPECT_EQ(powerSequencers[0]->getBus(), 5);
2036         EXPECT_EQ(powerSequencers[0]->getAddress(), 0x22);
2037         EXPECT_EQ(powerSequencers[1]->getName(), "UCD90320");
2038         EXPECT_EQ(powerSequencers[1]->getBus(), 7);
2039         EXPECT_EQ(powerSequencers[1]->getAddress(), 0x49);
2040     }
2041 
2042     // Test where fails: Element is not an array
2043     try
2044     {
2045         const json element = R"(
2046             {
2047                 "foo": "bar"
2048             }
2049         )"_json;
2050         std::map<std::string, std::string> variables{};
2051         MockServices services{};
2052         parsePowerSequencerArray(element, variables, services);
2053         ADD_FAILURE() << "Should not have reached this line.";
2054     }
2055     catch (const std::invalid_argument& e)
2056     {
2057         EXPECT_STREQ(e.what(), "Element is not an array");
2058     }
2059 
2060     // Test where fails: Element within array is invalid
2061     try
2062     {
2063         const json element = R"(
2064             [
2065               {
2066                 "type": "UCD90160",
2067                 "i2c_interface": { "bus": 3, "address": "0x11" },
2068                 "power_control_gpio_name": "power-chassis-control1",
2069                 "power_good_gpio_name": "power-chassis-good1",
2070                 "rails": []
2071               },
2072               true
2073             ]
2074         )"_json;
2075         std::map<std::string, std::string> variables{};
2076         MockServices services{};
2077         parsePowerSequencerArray(element, variables, services);
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 object");
2083     }
2084 
2085     // Test where fails: Invalid variable value specified
2086     try
2087     {
2088         const json element = R"(
2089             [
2090               {
2091                 "type": "UCD90320",
2092                 "i2c_interface": { "bus": "${bus}", "address": "${address}" },
2093                 "power_control_gpio_name": "power-chassis-control",
2094                 "power_good_gpio_name": "power-chassis-good",
2095                 "rails": []
2096               }
2097             ]
2098         )"_json;
2099         std::map<std::string, std::string> variables{{"bus", "7"},
2100                                                      {"address", "70"}};
2101         MockServices services{};
2102         parsePowerSequencerArray(element, variables, services);
2103         ADD_FAILURE() << "Should not have reached this line.";
2104     }
2105     catch (const std::invalid_argument& e)
2106     {
2107         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2108     }
2109 }
2110 
2111 TEST(ConfigFileParserTests, ParseRail)
2112 {
2113     // Test where works: Only required properties specified
2114     {
2115         const json element = R"(
2116             {
2117                 "name": "VDD_CPU0"
2118             }
2119         )"_json;
2120         std::map<std::string, std::string> variables{};
2121         auto rail = parseRail(element, variables);
2122         EXPECT_EQ(rail->getName(), "VDD_CPU0");
2123         EXPECT_FALSE(rail->getPresence().has_value());
2124         EXPECT_FALSE(rail->getPage().has_value());
2125         EXPECT_FALSE(rail->isPowerSupplyRail());
2126         EXPECT_FALSE(rail->getCheckStatusVout());
2127         EXPECT_FALSE(rail->getCompareVoltageToLimit());
2128         EXPECT_FALSE(rail->getGPIO().has_value());
2129     }
2130 
2131     // Test where works: All properties specified
2132     {
2133         const json element = R"(
2134             {
2135                 "name": "12.0VB",
2136                 "presence": "/xyz/openbmc_project/inventory/system/chassis/powersupply1",
2137                 "page": 11,
2138                 "is_power_supply_rail": true,
2139                 "check_status_vout": true,
2140                 "compare_voltage_to_limit": true,
2141                 "gpio": { "line": 60, "active_low": true }
2142             }
2143         )"_json;
2144         std::map<std::string, std::string> variables{};
2145         auto rail = parseRail(element, variables);
2146         EXPECT_EQ(rail->getName(), "12.0VB");
2147         EXPECT_TRUE(rail->getPresence().has_value());
2148         EXPECT_EQ(rail->getPresence().value(),
2149                   "/xyz/openbmc_project/inventory/system/chassis/powersupply1");
2150         EXPECT_TRUE(rail->getPage().has_value());
2151         EXPECT_EQ(rail->getPage().value(), 11);
2152         EXPECT_TRUE(rail->isPowerSupplyRail());
2153         EXPECT_TRUE(rail->getCheckStatusVout());
2154         EXPECT_TRUE(rail->getCompareVoltageToLimit());
2155         EXPECT_TRUE(rail->getGPIO().has_value());
2156         EXPECT_EQ(rail->getGPIO().value().line, 60);
2157         EXPECT_TRUE(rail->getGPIO().value().activeLow);
2158     }
2159 
2160     // Test where works: Variables specified
2161     {
2162         const json element = R"(
2163             {
2164                 "name": "${name}",
2165                 "presence": "${presence}",
2166                 "page": "${page}",
2167                 "is_power_supply_rail": "${is_power_supply_rail}",
2168                 "check_status_vout": "${check_status_vout}",
2169                 "compare_voltage_to_limit": "${compare_voltage_to_limit}",
2170                 "gpio": { "line": "${line}", "active_low": true }
2171             }
2172         )"_json;
2173         std::map<std::string, std::string> variables{
2174             {"name", "vdd"},
2175             {"presence",
2176              "/xyz/openbmc_project/inventory/system/chassis/powersupply2"},
2177             {"page", "9"},
2178             {"is_power_supply_rail", "true"},
2179             {"check_status_vout", "false"},
2180             {"compare_voltage_to_limit", "true"},
2181             {"line", "72"}};
2182         auto rail = parseRail(element, variables);
2183         EXPECT_EQ(rail->getName(), "vdd");
2184         EXPECT_TRUE(rail->getPresence().has_value());
2185         EXPECT_EQ(rail->getPresence().value(),
2186                   "/xyz/openbmc_project/inventory/system/chassis/powersupply2");
2187         EXPECT_TRUE(rail->getPage().has_value());
2188         EXPECT_EQ(rail->getPage().value(), 9);
2189         EXPECT_TRUE(rail->isPowerSupplyRail());
2190         EXPECT_FALSE(rail->getCheckStatusVout());
2191         EXPECT_TRUE(rail->getCompareVoltageToLimit());
2192         EXPECT_TRUE(rail->getGPIO().has_value());
2193         EXPECT_EQ(rail->getGPIO().value().line, 72);
2194         EXPECT_TRUE(rail->getGPIO().value().activeLow);
2195     }
2196 
2197     // Test where fails: Element is not an object
2198     try
2199     {
2200         const json element = R"( [ "vdda", "vddb" ] )"_json;
2201         std::map<std::string, std::string> variables{};
2202         parseRail(element, variables);
2203         ADD_FAILURE() << "Should not have reached this line.";
2204     }
2205     catch (const std::invalid_argument& e)
2206     {
2207         EXPECT_STREQ(e.what(), "Element is not an object");
2208     }
2209 
2210     // Test where fails: Required name property not specified
2211     try
2212     {
2213         const json element = R"(
2214             {
2215                 "page": 11
2216             }
2217         )"_json;
2218         std::map<std::string, std::string> variables{};
2219         parseRail(element, variables);
2220         ADD_FAILURE() << "Should not have reached this line.";
2221     }
2222     catch (const std::invalid_argument& e)
2223     {
2224         EXPECT_STREQ(e.what(), "Required property missing: name");
2225     }
2226 
2227     // Test where fails: name value is invalid
2228     try
2229     {
2230         const json element = R"(
2231             {
2232                 "name": 31,
2233                 "page": 11
2234             }
2235         )"_json;
2236         std::map<std::string, std::string> variables{};
2237         parseRail(element, variables);
2238         ADD_FAILURE() << "Should not have reached this line.";
2239     }
2240     catch (const std::invalid_argument& e)
2241     {
2242         EXPECT_STREQ(e.what(), "Element is not a string");
2243     }
2244 
2245     // Test where fails: presence value is invalid
2246     try
2247     {
2248         const json element = R"(
2249             {
2250                 "name": "VCS_CPU1",
2251                 "presence": false
2252             }
2253         )"_json;
2254         std::map<std::string, std::string> variables{};
2255         parseRail(element, variables);
2256         ADD_FAILURE() << "Should not have reached this line.";
2257     }
2258     catch (const std::invalid_argument& e)
2259     {
2260         EXPECT_STREQ(e.what(), "Element is not a string");
2261     }
2262 
2263     // Test where fails: page value is invalid
2264     try
2265     {
2266         const json element = R"(
2267             {
2268                 "name": "VCS_CPU1",
2269                 "page": 256
2270             }
2271         )"_json;
2272         std::map<std::string, std::string> variables{};
2273         parseRail(element, variables);
2274         ADD_FAILURE() << "Should not have reached this line.";
2275     }
2276     catch (const std::invalid_argument& e)
2277     {
2278         EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
2279     }
2280 
2281     // Test where fails: is_power_supply_rail value is invalid
2282     try
2283     {
2284         const json element = R"(
2285             {
2286                 "name": "12.0VA",
2287                 "is_power_supply_rail": "true"
2288             }
2289         )"_json;
2290         std::map<std::string, std::string> variables{};
2291         parseRail(element, variables);
2292         ADD_FAILURE() << "Should not have reached this line.";
2293     }
2294     catch (const std::invalid_argument& e)
2295     {
2296         EXPECT_STREQ(e.what(), "Element is not a boolean");
2297     }
2298 
2299     // Test where fails: check_status_vout value is invalid
2300     try
2301     {
2302         const json element = R"(
2303             {
2304                 "name": "VCS_CPU1",
2305                 "check_status_vout": "false"
2306             }
2307         )"_json;
2308         std::map<std::string, std::string> variables{};
2309         parseRail(element, variables);
2310         ADD_FAILURE() << "Should not have reached this line.";
2311     }
2312     catch (const std::invalid_argument& e)
2313     {
2314         EXPECT_STREQ(e.what(), "Element is not a boolean");
2315     }
2316 
2317     // Test where fails: compare_voltage_to_limit value is invalid
2318     try
2319     {
2320         const json element = R"(
2321             {
2322                 "name": "VCS_CPU1",
2323                 "compare_voltage_to_limit": 23
2324             }
2325         )"_json;
2326         std::map<std::string, std::string> variables{};
2327         parseRail(element, variables);
2328         ADD_FAILURE() << "Should not have reached this line.";
2329     }
2330     catch (const std::invalid_argument& e)
2331     {
2332         EXPECT_STREQ(e.what(), "Element is not a boolean");
2333     }
2334 
2335     // Test where fails: gpio value is invalid
2336     try
2337     {
2338         const json element = R"(
2339             {
2340                 "name": "VCS_CPU1",
2341                 "gpio": 131
2342             }
2343         )"_json;
2344         std::map<std::string, std::string> variables{};
2345         parseRail(element, variables);
2346         ADD_FAILURE() << "Should not have reached this line.";
2347     }
2348     catch (const std::invalid_argument& e)
2349     {
2350         EXPECT_STREQ(e.what(), "Element is not an object");
2351     }
2352 
2353     // Test where fails: check_status_vout is true and page not specified
2354     try
2355     {
2356         const json element = R"(
2357             {
2358                 "name": "VCS_CPU1",
2359                 "check_status_vout": true
2360             }
2361         )"_json;
2362         std::map<std::string, std::string> variables{};
2363         parseRail(element, variables);
2364         ADD_FAILURE() << "Should not have reached this line.";
2365     }
2366     catch (const std::invalid_argument& e)
2367     {
2368         EXPECT_STREQ(e.what(), "Required property missing: page");
2369     }
2370 
2371     // Test where fails: compare_voltage_to_limit is true and page not
2372     // specified
2373     try
2374     {
2375         const json element = R"(
2376             {
2377                 "name": "VCS_CPU1",
2378                 "compare_voltage_to_limit": true
2379             }
2380         )"_json;
2381         std::map<std::string, std::string> variables{};
2382         parseRail(element, variables);
2383         ADD_FAILURE() << "Should not have reached this line.";
2384     }
2385     catch (const std::invalid_argument& e)
2386     {
2387         EXPECT_STREQ(e.what(), "Required property missing: page");
2388     }
2389 
2390     // Test where fails: Invalid property specified
2391     try
2392     {
2393         const json element = R"(
2394             {
2395                 "name": "VCS_CPU1",
2396                 "foo": "bar"
2397             }
2398         )"_json;
2399         std::map<std::string, std::string> variables{};
2400         parseRail(element, variables);
2401         ADD_FAILURE() << "Should not have reached this line.";
2402     }
2403     catch (const std::invalid_argument& e)
2404     {
2405         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2406     }
2407 
2408     // Test where fails: Undefined variable specified
2409     try
2410     {
2411         const json element = R"(
2412             {
2413                 "name": "12.0VB",
2414                 "presence": "/xyz/openbmc_project/inventory/system/chassis/powersupply${chassis}"
2415             }
2416         )"_json;
2417         std::map<std::string, std::string> variables{{"foo", "bar"}};
2418         parseRail(element, variables);
2419         ADD_FAILURE() << "Should not have reached this line.";
2420     }
2421     catch (const std::invalid_argument& e)
2422     {
2423         EXPECT_STREQ(e.what(), "Undefined variable: chassis");
2424     }
2425 }
2426 
2427 TEST(ConfigFileParserTests, ParseRailArray)
2428 {
2429     // Test where works: Array is empty
2430     {
2431         const json element = R"(
2432             [
2433             ]
2434         )"_json;
2435         std::map<std::string, std::string> variables{};
2436         auto rails = parseRailArray(element, variables);
2437         EXPECT_EQ(rails.size(), 0);
2438     }
2439 
2440     // Test where works: Array is not empty
2441     {
2442         const json element = R"(
2443             [
2444                 { "name": "VDD_CPU0" },
2445                 { "name": "VCS_CPU1" }
2446             ]
2447         )"_json;
2448         std::map<std::string, std::string> variables{};
2449         auto rails = parseRailArray(element, variables);
2450         EXPECT_EQ(rails.size(), 2);
2451         EXPECT_EQ(rails[0]->getName(), "VDD_CPU0");
2452         EXPECT_EQ(rails[1]->getName(), "VCS_CPU1");
2453     }
2454 
2455     // Test where works: Variables specified
2456     {
2457         const json element = R"(
2458             [
2459                 { "name": "${rail1}" },
2460                 { "name": "${rail2}" },
2461                 { "name": "${rail3}" }
2462             ]
2463         )"_json;
2464         std::map<std::string, std::string> variables{
2465             {"rail1", "foo"}, {"rail2", "bar"}, {"rail3", "baz"}};
2466         auto rails = parseRailArray(element, variables);
2467         EXPECT_EQ(rails.size(), 3);
2468         EXPECT_EQ(rails[0]->getName(), "foo");
2469         EXPECT_EQ(rails[1]->getName(), "bar");
2470         EXPECT_EQ(rails[2]->getName(), "baz");
2471     }
2472 
2473     // Test where fails: Element is not an array
2474     try
2475     {
2476         const json element = R"(
2477             {
2478                 "foo": "bar"
2479             }
2480         )"_json;
2481         std::map<std::string, std::string> variables{};
2482         parseRailArray(element, variables);
2483         ADD_FAILURE() << "Should not have reached this line.";
2484     }
2485     catch (const std::invalid_argument& e)
2486     {
2487         EXPECT_STREQ(e.what(), "Element is not an array");
2488     }
2489 
2490     // Test where fails: Element within array is invalid
2491     try
2492     {
2493         const json element = R"(
2494             [
2495                 { "name": "VDD_CPU0" },
2496                 23
2497             ]
2498         )"_json;
2499         std::map<std::string, std::string> variables{};
2500         parseRailArray(element, variables);
2501         ADD_FAILURE() << "Should not have reached this line.";
2502     }
2503     catch (const std::invalid_argument& e)
2504     {
2505         EXPECT_STREQ(e.what(), "Element is not an object");
2506     }
2507 
2508     // Test where fails: Invalid variable value specified
2509     try
2510     {
2511         const json element = R"(
2512             [
2513                 { "name": "VDD_CPU0", "page": "${page1}" },
2514                 { "name": "VCS_CPU1", "page": "${page2}" }
2515             ]
2516         )"_json;
2517         std::map<std::string, std::string> variables{{"page1", "11"},
2518                                                      {"page2", "-1"}};
2519         parseRailArray(element, variables);
2520         ADD_FAILURE() << "Should not have reached this line.";
2521     }
2522     catch (const std::invalid_argument& e)
2523     {
2524         EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
2525     }
2526 }
2527 
2528 TEST(ConfigFileParserTests, ParseRoot)
2529 {
2530     // Test where works: Only required properties specified
2531     {
2532         const json element = R"(
2533             {
2534               "chassis": [
2535                 {
2536                   "number": 1,
2537                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
2538                   "power_sequencers": []
2539                 },
2540                 {
2541                   "number": 2,
2542                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis2",
2543                   "power_sequencers": []
2544                 }
2545               ]
2546             }
2547         )"_json;
2548         MockServices services{};
2549         auto chassis = parseRoot(element, services);
2550         EXPECT_EQ(chassis.size(), 2);
2551         EXPECT_EQ(chassis[0]->getNumber(), 1);
2552         EXPECT_EQ(chassis[0]->getInventoryPath(),
2553                   "/xyz/openbmc_project/inventory/system/chassis1");
2554         EXPECT_EQ(chassis[1]->getNumber(), 2);
2555         EXPECT_EQ(chassis[1]->getInventoryPath(),
2556                   "/xyz/openbmc_project/inventory/system/chassis2");
2557     }
2558 
2559     // Test where works: All properties specified
2560     {
2561         const json element = R"(
2562             {
2563               "comments": [ "Config file for a FooBar one-chassis system" ],
2564               "chassis_templates": [
2565                 {
2566                   "id": "foo_chassis",
2567                   "number": "${chassis_number}",
2568                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
2569                   "power_sequencers": []
2570                 },
2571                 {
2572                   "id": "bar_chassis",
2573                   "number": "${chassis_number}",
2574                   "inventory_path": "/xyz/openbmc_project/inventory/system/bar_chassis${chassis_number}",
2575                   "power_sequencers": []
2576                 }
2577               ],
2578               "chassis": [
2579                 {
2580                   "template_id": "foo_chassis",
2581                   "template_variable_values": { "chassis_number": "2" }
2582                 },
2583                 {
2584                   "template_id": "bar_chassis",
2585                   "template_variable_values": { "chassis_number": "3" }
2586                 }
2587               ]
2588             }
2589         )"_json;
2590         MockServices services{};
2591         auto chassis = parseRoot(element, services);
2592         EXPECT_EQ(chassis.size(), 2);
2593         EXPECT_EQ(chassis[0]->getNumber(), 2);
2594         EXPECT_EQ(chassis[0]->getInventoryPath(),
2595                   "/xyz/openbmc_project/inventory/system/chassis2");
2596         EXPECT_EQ(chassis[1]->getNumber(), 3);
2597         EXPECT_EQ(chassis[1]->getInventoryPath(),
2598                   "/xyz/openbmc_project/inventory/system/bar_chassis3");
2599     }
2600 
2601     // Test where fails: Element is not an object
2602     try
2603     {
2604         const json element = R"( [ "VDD_CPU0", "VCS_CPU1" ] )"_json;
2605         MockServices services{};
2606         parseRoot(element, services);
2607         ADD_FAILURE() << "Should not have reached this line.";
2608     }
2609     catch (const std::invalid_argument& e)
2610     {
2611         EXPECT_STREQ(e.what(), "Element is not an object");
2612     }
2613 
2614     // Test where fails: Required chassis property not specified
2615     try
2616     {
2617         const json element = R"(
2618             {
2619               "comments": [ "Config file for a FooBar one-chassis system" ],
2620               "chassis_templates": [
2621                 {
2622                   "id": "foo_chassis",
2623                   "number": "${chassis_number}",
2624                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
2625                   "power_sequencers": []
2626                 }
2627               ]
2628             }
2629         )"_json;
2630         MockServices services{};
2631         parseRoot(element, services);
2632         ADD_FAILURE() << "Should not have reached this line.";
2633     }
2634     catch (const std::invalid_argument& e)
2635     {
2636         EXPECT_STREQ(e.what(), "Required property missing: chassis");
2637     }
2638 
2639     // Test where fails: chassis_templates value is invalid
2640     try
2641     {
2642         const json element = R"(
2643             {
2644               "chassis_templates": true,
2645               "chassis": [
2646                 {
2647                   "number": 1,
2648                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
2649                   "power_sequencers": []
2650                 }
2651               ]
2652             }
2653         )"_json;
2654         MockServices services{};
2655         parseRoot(element, services);
2656         ADD_FAILURE() << "Should not have reached this line.";
2657     }
2658     catch (const std::invalid_argument& e)
2659     {
2660         EXPECT_STREQ(e.what(), "Element is not an array");
2661     }
2662 
2663     // Test where fails: chassis value is invalid
2664     try
2665     {
2666         const json element = R"(
2667             {
2668               "chassis": [
2669                 {
2670                   "number": "one",
2671                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
2672                   "power_sequencers": []
2673                 }
2674               ]
2675             }
2676         )"_json;
2677         MockServices services{};
2678         parseRoot(element, services);
2679         ADD_FAILURE() << "Should not have reached this line.";
2680     }
2681     catch (const std::invalid_argument& e)
2682     {
2683         EXPECT_STREQ(e.what(), "Element is not an integer");
2684     }
2685 
2686     // Test where fails: Invalid property specified
2687     try
2688     {
2689         const json element = R"(
2690             {
2691               "chassis": [
2692                 {
2693                   "number": 1,
2694                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
2695                   "power_sequencers": []
2696                 }
2697               ],
2698               "foo": true
2699             }
2700         )"_json;
2701         MockServices services{};
2702         parseRoot(element, services);
2703         ADD_FAILURE() << "Should not have reached this line.";
2704     }
2705     catch (const std::invalid_argument& e)
2706     {
2707         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2708     }
2709 }
2710 
2711 TEST(ConfigFileParserTests, ParseVariables)
2712 {
2713     // Test where works: No variables specified
2714     {
2715         const json element = R"(
2716             {
2717             }
2718         )"_json;
2719         auto variables = parseVariables(element);
2720         EXPECT_EQ(variables.size(), 0);
2721     }
2722 
2723     // Test where works: Variables specified
2724     {
2725         const json element = R"(
2726             {
2727               "chassis_number": "2",
2728               "bus_number": "13"
2729             }
2730         )"_json;
2731         auto variables = parseVariables(element);
2732         EXPECT_EQ(variables.size(), 2);
2733         EXPECT_EQ(variables["chassis_number"], "2");
2734         EXPECT_EQ(variables["bus_number"], "13");
2735     }
2736 
2737     // Test where fails: Element is not an object
2738     try
2739     {
2740         const json element = R"(
2741             [
2742               "chassis_number", "2",
2743               "bus_number", "13"
2744             ]
2745         )"_json;
2746         parseVariables(element);
2747         ADD_FAILURE() << "Should not have reached this line.";
2748     }
2749     catch (const std::invalid_argument& e)
2750     {
2751         EXPECT_STREQ(e.what(), "Element is not an object");
2752     }
2753 
2754     // Test where fails: Key is not a string
2755     try
2756     {
2757         const json element = R"(
2758             {
2759               chassis_number: "2",
2760               "bus_number": "13"
2761             }
2762         )"_json;
2763         parseVariables(element);
2764         ADD_FAILURE() << "Should not have reached this line.";
2765     }
2766     catch (const json::parse_error& e)
2767     {}
2768 
2769     // Test where fails: Value is not a string
2770     try
2771     {
2772         const json element = R"(
2773             {
2774               "chassis_number": "2",
2775               "bus_number": 13
2776             }
2777         )"_json;
2778         parseVariables(element);
2779         ADD_FAILURE() << "Should not have reached this line.";
2780     }
2781     catch (const std::invalid_argument& e)
2782     {
2783         EXPECT_STREQ(e.what(), "Element is not a string");
2784     }
2785 
2786     // Test where fails: Key is an empty string
2787     try
2788     {
2789         const json element = R"(
2790             {
2791               "chassis_number": "2",
2792               "": "13"
2793             }
2794         )"_json;
2795         parseVariables(element);
2796         ADD_FAILURE() << "Should not have reached this line.";
2797     }
2798     catch (const std::invalid_argument& e)
2799     {
2800         EXPECT_STREQ(e.what(), "Element contains an empty string");
2801     }
2802 
2803     // Test where fails: Value is an empty string
2804     try
2805     {
2806         const json element = R"(
2807             {
2808               "chassis_number": "",
2809               "bus_number": "13"
2810             }
2811         )"_json;
2812         parseVariables(element);
2813         ADD_FAILURE() << "Should not have reached this line.";
2814     }
2815     catch (const std::invalid_argument& e)
2816     {
2817         EXPECT_STREQ(e.what(), "Element contains an empty string");
2818     }
2819 }
2820