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 "config_file_parser.hpp"
17 #include "config_file_parser_error.hpp"
18 #include "rail.hpp"
19 #include "temporary_file.hpp"
20 #include "temporary_subdirectory.hpp"
21 
22 #include <sys/stat.h> // for chmod()
23 
24 #include <nlohmann/json.hpp>
25 
26 #include <cstdint>
27 #include <exception>
28 #include <filesystem>
29 #include <fstream>
30 #include <memory>
31 #include <optional>
32 #include <stdexcept>
33 #include <string>
34 #include <vector>
35 
36 #include <gtest/gtest.h>
37 
38 using namespace phosphor::power::sequencer;
39 using namespace phosphor::power::sequencer::config_file_parser;
40 using namespace phosphor::power::sequencer::config_file_parser::internal;
41 using namespace phosphor::power::util;
42 using json = nlohmann::json;
43 namespace fs = std::filesystem;
44 
45 void writeConfigFile(const fs::path& pathName, const std::string& contents)
46 {
47     std::ofstream file{pathName};
48     file << contents;
49 }
50 
51 void writeConfigFile(const fs::path& pathName, const json& contents)
52 {
53     std::ofstream file{pathName};
54     file << contents;
55 }
56 
57 TEST(ConfigFileParserTests, Find)
58 {
59     std::vector<std::string> compatibleSystemTypes{
60         "com.acme.Hardware.Chassis.Model.MegaServer4CPU",
61         "com.acme.Hardware.Chassis.Model.MegaServer",
62         "com.acme.Hardware.Chassis.Model.Server"};
63 
64     // Test where works: Fully qualified system type: First in list
65     {
66         TemporarySubDirectory configFileDir;
67         fs::path configFileDirPath = configFileDir.getPath();
68 
69         fs::path configFilePath = configFileDirPath;
70         configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer4CPU.json";
71         writeConfigFile(configFilePath, std::string{""});
72 
73         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
74         EXPECT_EQ(pathFound, configFilePath);
75     }
76 
77     // Test where works: Fully qualified system type: Second in list
78     {
79         TemporarySubDirectory configFileDir;
80         fs::path configFileDirPath = configFileDir.getPath();
81 
82         fs::path configFilePath = configFileDirPath;
83         configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer.json";
84         writeConfigFile(configFilePath, std::string{""});
85 
86         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
87         EXPECT_EQ(pathFound, configFilePath);
88     }
89 
90     // Test where works: Last node in system type: Second in list
91     {
92         TemporarySubDirectory configFileDir;
93         fs::path configFileDirPath = configFileDir.getPath();
94 
95         fs::path configFilePath = configFileDirPath;
96         configFilePath /= "MegaServer.json";
97         writeConfigFile(configFilePath, std::string{""});
98 
99         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
100         EXPECT_EQ(pathFound, configFilePath);
101     }
102 
103     // Test where works: Last node in system type: Last in list
104     {
105         TemporarySubDirectory configFileDir;
106         fs::path configFileDirPath = configFileDir.getPath();
107 
108         fs::path configFilePath = configFileDirPath;
109         configFilePath /= "Server.json";
110         writeConfigFile(configFilePath, std::string{""});
111 
112         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
113         EXPECT_EQ(pathFound, configFilePath);
114     }
115 
116     // Test where works: System type has no '.'
117     {
118         TemporarySubDirectory configFileDir;
119         fs::path configFileDirPath = configFileDir.getPath();
120 
121         fs::path configFilePath = configFileDirPath;
122         configFilePath /= "Server.json";
123         writeConfigFile(configFilePath, std::string{""});
124 
125         std::vector<std::string> noDotSystemTypes{"MegaServer4CPU",
126                                                   "MegaServer", "Server"};
127         fs::path pathFound = find(noDotSystemTypes, configFileDirPath);
128         EXPECT_EQ(pathFound, configFilePath);
129     }
130 
131     // Test where fails: System type list is empty
132     {
133         TemporarySubDirectory configFileDir;
134         fs::path configFileDirPath = configFileDir.getPath();
135 
136         fs::path configFilePath = configFileDirPath;
137         configFilePath /= "Server.json";
138         writeConfigFile(configFilePath, std::string{""});
139 
140         std::vector<std::string> emptySystemTypes{};
141         fs::path pathFound = find(emptySystemTypes, configFileDirPath);
142         EXPECT_TRUE(pathFound.empty());
143     }
144 
145     // Test where fails: Configuration file directory is empty
146     {
147         TemporarySubDirectory configFileDir;
148         fs::path configFileDirPath = configFileDir.getPath();
149 
150         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
151         EXPECT_TRUE(pathFound.empty());
152     }
153 
154     // Test where fails: Configuration file directory does not exist
155     {
156         fs::path configFileDirPath{"/tmp/does_not_exist_XYZ"};
157 
158         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
159         EXPECT_TRUE(pathFound.empty());
160     }
161 
162     // Test where fails: Configuration file directory is not readable
163     {
164         TemporarySubDirectory configFileDir;
165         fs::path configFileDirPath = configFileDir.getPath();
166         fs::permissions(configFileDirPath, fs::perms::none);
167 
168         EXPECT_THROW(find(compatibleSystemTypes, configFileDirPath),
169                      std::exception);
170 
171         fs::permissions(configFileDirPath, fs::perms::owner_all);
172     }
173 
174     // Test where fails: No matching file name found
175     {
176         TemporarySubDirectory configFileDir;
177         fs::path configFileDirPath = configFileDir.getPath();
178 
179         fs::path configFilePath = configFileDirPath;
180         configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer";
181         writeConfigFile(configFilePath, std::string{""});
182 
183         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
184         EXPECT_TRUE(pathFound.empty());
185     }
186 
187     // Test where fails: Matching file name is a directory: Fully qualified
188     {
189         TemporarySubDirectory configFileDir;
190         fs::path configFileDirPath = configFileDir.getPath();
191 
192         fs::path configFilePath = configFileDirPath;
193         configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer4CPU.json";
194         fs::create_directory(configFilePath);
195 
196         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
197         EXPECT_TRUE(pathFound.empty());
198     }
199 
200     // Test where fails: Matching file name is a directory: Last node
201     {
202         TemporarySubDirectory configFileDir;
203         fs::path configFileDirPath = configFileDir.getPath();
204 
205         fs::path configFilePath = configFileDirPath;
206         configFilePath /= "MegaServer.json";
207         fs::create_directory(configFilePath);
208 
209         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
210         EXPECT_TRUE(pathFound.empty());
211     }
212 
213     // Test where fails: System type has no '.'
214     {
215         TemporarySubDirectory configFileDir;
216         fs::path configFileDirPath = configFileDir.getPath();
217 
218         fs::path configFilePath = configFileDirPath;
219         configFilePath /= "MegaServer2CPU.json";
220         writeConfigFile(configFilePath, std::string{""});
221 
222         std::vector<std::string> noDotSystemTypes{"MegaServer4CPU",
223                                                   "MegaServer", "Server", ""};
224         fs::path pathFound = find(noDotSystemTypes, configFileDirPath);
225         EXPECT_TRUE(pathFound.empty());
226     }
227 
228     // Test where fails: System type ends with '.'
229     {
230         TemporarySubDirectory configFileDir;
231         fs::path configFileDirPath = configFileDir.getPath();
232 
233         fs::path configFilePath = configFileDirPath;
234         configFilePath /= "MegaServer4CPU.json";
235         writeConfigFile(configFilePath, std::string{""});
236 
237         std::vector<std::string> dotAtEndSystemTypes{
238             "com.acme.Hardware.Chassis.Model.MegaServer4CPU.", "a.", "."};
239         fs::path pathFound = find(dotAtEndSystemTypes, configFileDirPath);
240         EXPECT_TRUE(pathFound.empty());
241     }
242 }
243 
244 TEST(ConfigFileParserTests, Parse)
245 {
246     // Test where works
247     {
248         const json configFileContents = R"(
249             {
250                 "rails": [
251                     {
252                         "name": "VDD_CPU0",
253                         "page": 11,
254                         "check_status_vout": true
255                     },
256                     {
257                         "name": "VCS_CPU1",
258                         "presence": "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1",
259                         "gpio": { "line": 60 }
260                     }
261                 ]
262             }
263         )"_json;
264 
265         TemporaryFile configFile;
266         fs::path pathName{configFile.getPath()};
267         writeConfigFile(pathName, configFileContents);
268 
269         std::vector<std::unique_ptr<Rail>> rails = parse(pathName);
270 
271         EXPECT_EQ(rails.size(), 2);
272         EXPECT_EQ(rails[0]->getName(), "VDD_CPU0");
273         EXPECT_EQ(rails[1]->getName(), "VCS_CPU1");
274     }
275 
276     // Test where fails: File does not exist
277     {
278         fs::path pathName{"/tmp/non_existent_file"};
279         EXPECT_THROW(parse(pathName), ConfigFileParserError);
280     }
281 
282     // Test where fails: File is not readable
283     {
284         const json configFileContents = R"(
285             {
286                 "rails": [
287                     {
288                         "name": "VDD_CPU0"
289                     }
290                 ]
291             }
292         )"_json;
293 
294         TemporaryFile configFile;
295         fs::path pathName{configFile.getPath()};
296         writeConfigFile(pathName, configFileContents);
297 
298         chmod(pathName.c_str(), 0222);
299         EXPECT_THROW(parse(pathName), ConfigFileParserError);
300     }
301 
302     // Test where fails: File is not valid JSON
303     {
304         const std::string configFileContents = "] foo [";
305 
306         TemporaryFile configFile;
307         fs::path pathName{configFile.getPath()};
308         writeConfigFile(pathName, configFileContents);
309 
310         EXPECT_THROW(parse(pathName), ConfigFileParserError);
311     }
312 
313     // Test where fails: JSON does not conform to config file format
314     {
315         const json configFileContents = R"( [ "foo", "bar" ] )"_json;
316 
317         TemporaryFile configFile;
318         fs::path pathName{configFile.getPath()};
319         writeConfigFile(pathName, configFileContents);
320 
321         EXPECT_THROW(parse(pathName), ConfigFileParserError);
322     }
323 }
324 
325 TEST(ConfigFileParserTests, GetRequiredProperty)
326 {
327     // Test where property exists
328     {
329         const json element = R"( { "name": "VDD_CPU0" } )"_json;
330         const json& propertyElement = getRequiredProperty(element, "name");
331         EXPECT_EQ(propertyElement.get<std::string>(), "VDD_CPU0");
332     }
333 
334     // Test where property does not exist
335     try
336     {
337         const json element = R"( { "foo": 23 } )"_json;
338         getRequiredProperty(element, "name");
339         ADD_FAILURE() << "Should not have reached this line.";
340     }
341     catch (const std::invalid_argument& e)
342     {
343         EXPECT_STREQ(e.what(), "Required property missing: name");
344     }
345 }
346 
347 TEST(ConfigFileParserTests, ParseBoolean)
348 {
349     // Test where works: true
350     {
351         const json element = R"( true )"_json;
352         bool value = parseBoolean(element);
353         EXPECT_EQ(value, true);
354     }
355 
356     // Test where works: false
357     {
358         const json element = R"( false )"_json;
359         bool value = parseBoolean(element);
360         EXPECT_EQ(value, false);
361     }
362 
363     // Test where fails: Element is not a boolean
364     try
365     {
366         const json element = R"( 1 )"_json;
367         parseBoolean(element);
368         ADD_FAILURE() << "Should not have reached this line.";
369     }
370     catch (const std::invalid_argument& e)
371     {
372         EXPECT_STREQ(e.what(), "Element is not a boolean");
373     }
374 }
375 
376 TEST(ConfigFileParserTests, ParseGPIO)
377 {
378     // Test where works: Only required properties specified
379     {
380         const json element = R"(
381             {
382                 "line": 60
383             }
384         )"_json;
385         GPIO gpio = parseGPIO(element);
386         EXPECT_EQ(gpio.line, 60);
387         EXPECT_FALSE(gpio.activeLow);
388     }
389 
390     // Test where works: All properties specified
391     {
392         const json element = R"(
393             {
394                 "line": 131,
395                 "active_low": true
396             }
397         )"_json;
398         GPIO gpio = parseGPIO(element);
399         EXPECT_EQ(gpio.line, 131);
400         EXPECT_TRUE(gpio.activeLow);
401     }
402 
403     // Test where fails: Element is not an object
404     try
405     {
406         const json element = R"( [ "vdda", "vddb" ] )"_json;
407         parseGPIO(element);
408         ADD_FAILURE() << "Should not have reached this line.";
409     }
410     catch (const std::invalid_argument& e)
411     {
412         EXPECT_STREQ(e.what(), "Element is not an object");
413     }
414 
415     // Test where fails: Required line property not specified
416     try
417     {
418         const json element = R"(
419             {
420                 "active_low": true
421             }
422         )"_json;
423         parseGPIO(element);
424         ADD_FAILURE() << "Should not have reached this line.";
425     }
426     catch (const std::invalid_argument& e)
427     {
428         EXPECT_STREQ(e.what(), "Required property missing: line");
429     }
430 
431     // Test where fails: line value is invalid
432     try
433     {
434         const json element = R"(
435             {
436                 "line": -131,
437                 "active_low": true
438             }
439         )"_json;
440         parseGPIO(element);
441         ADD_FAILURE() << "Should not have reached this line.";
442     }
443     catch (const std::invalid_argument& e)
444     {
445         EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
446     }
447 
448     // Test where fails: active_low value is invalid
449     try
450     {
451         const json element = R"(
452             {
453                 "line": 131,
454                 "active_low": "true"
455             }
456         )"_json;
457         parseGPIO(element);
458         ADD_FAILURE() << "Should not have reached this line.";
459     }
460     catch (const std::invalid_argument& e)
461     {
462         EXPECT_STREQ(e.what(), "Element is not a boolean");
463     }
464 
465     // Test where fails: Invalid property specified
466     try
467     {
468         const json element = R"(
469             {
470                 "line": 131,
471                 "foo": "bar"
472             }
473         )"_json;
474         parseGPIO(element);
475         ADD_FAILURE() << "Should not have reached this line.";
476     }
477     catch (const std::invalid_argument& e)
478     {
479         EXPECT_STREQ(e.what(), "Element contains an invalid property");
480     }
481 }
482 
483 TEST(ConfigFileParserTests, ParseRail)
484 {
485     // Test where works: Only required properties specified
486     {
487         const json element = R"(
488             {
489                 "name": "VDD_CPU0"
490             }
491         )"_json;
492         std::unique_ptr<Rail> rail = parseRail(element);
493         EXPECT_EQ(rail->getName(), "VDD_CPU0");
494         EXPECT_FALSE(rail->getPresence().has_value());
495         EXPECT_FALSE(rail->getPage().has_value());
496         EXPECT_FALSE(rail->isPowerSupplyRail());
497         EXPECT_FALSE(rail->getCheckStatusVout());
498         EXPECT_FALSE(rail->getCompareVoltageToLimit());
499         EXPECT_FALSE(rail->getGPIO().has_value());
500     }
501 
502     // Test where works: All properties specified
503     {
504         const json element = R"(
505             {
506                 "name": "12.0VB",
507                 "presence": "/xyz/openbmc_project/inventory/system/chassis/powersupply1",
508                 "page": 11,
509                 "is_power_supply_rail": true,
510                 "check_status_vout": true,
511                 "compare_voltage_to_limit": true,
512                 "gpio": { "line": 60, "active_low": true }
513             }
514         )"_json;
515         std::unique_ptr<Rail> rail = parseRail(element);
516         EXPECT_EQ(rail->getName(), "12.0VB");
517         EXPECT_TRUE(rail->getPresence().has_value());
518         EXPECT_EQ(rail->getPresence().value(),
519                   "/xyz/openbmc_project/inventory/system/chassis/powersupply1");
520         EXPECT_TRUE(rail->getPage().has_value());
521         EXPECT_EQ(rail->getPage().value(), 11);
522         EXPECT_TRUE(rail->isPowerSupplyRail());
523         EXPECT_TRUE(rail->getCheckStatusVout());
524         EXPECT_TRUE(rail->getCompareVoltageToLimit());
525         EXPECT_TRUE(rail->getGPIO().has_value());
526         EXPECT_EQ(rail->getGPIO().value().line, 60);
527         EXPECT_TRUE(rail->getGPIO().value().activeLow);
528     }
529 
530     // Test where fails: Element is not an object
531     try
532     {
533         const json element = R"( [ "vdda", "vddb" ] )"_json;
534         parseRail(element);
535         ADD_FAILURE() << "Should not have reached this line.";
536     }
537     catch (const std::invalid_argument& e)
538     {
539         EXPECT_STREQ(e.what(), "Element is not an object");
540     }
541 
542     // Test where fails: Required name property not specified
543     try
544     {
545         const json element = R"(
546             {
547                 "page": 11
548             }
549         )"_json;
550         parseRail(element);
551         ADD_FAILURE() << "Should not have reached this line.";
552     }
553     catch (const std::invalid_argument& e)
554     {
555         EXPECT_STREQ(e.what(), "Required property missing: name");
556     }
557 
558     // Test where fails: name value is invalid
559     try
560     {
561         const json element = R"(
562             {
563                 "name": 31,
564                 "page": 11
565             }
566         )"_json;
567         parseRail(element);
568         ADD_FAILURE() << "Should not have reached this line.";
569     }
570     catch (const std::invalid_argument& e)
571     {
572         EXPECT_STREQ(e.what(), "Element is not a string");
573     }
574 
575     // Test where fails: presence value is invalid
576     try
577     {
578         const json element = R"(
579             {
580                 "name": "VCS_CPU1",
581                 "presence": false
582             }
583         )"_json;
584         parseRail(element);
585         ADD_FAILURE() << "Should not have reached this line.";
586     }
587     catch (const std::invalid_argument& e)
588     {
589         EXPECT_STREQ(e.what(), "Element is not a string");
590     }
591 
592     // Test where fails: page value is invalid
593     try
594     {
595         const json element = R"(
596             {
597                 "name": "VCS_CPU1",
598                 "page": 256
599             }
600         )"_json;
601         parseRail(element);
602         ADD_FAILURE() << "Should not have reached this line.";
603     }
604     catch (const std::invalid_argument& e)
605     {
606         EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
607     }
608 
609     // Test where fails: is_power_supply_rail value is invalid
610     try
611     {
612         const json element = R"(
613             {
614                 "name": "12.0VA",
615                 "is_power_supply_rail": "true"
616             }
617         )"_json;
618         parseRail(element);
619         ADD_FAILURE() << "Should not have reached this line.";
620     }
621     catch (const std::invalid_argument& e)
622     {
623         EXPECT_STREQ(e.what(), "Element is not a boolean");
624     }
625 
626     // Test where fails: check_status_vout value is invalid
627     try
628     {
629         const json element = R"(
630             {
631                 "name": "VCS_CPU1",
632                 "check_status_vout": "false"
633             }
634         )"_json;
635         parseRail(element);
636         ADD_FAILURE() << "Should not have reached this line.";
637     }
638     catch (const std::invalid_argument& e)
639     {
640         EXPECT_STREQ(e.what(), "Element is not a boolean");
641     }
642 
643     // Test where fails: compare_voltage_to_limit value is invalid
644     try
645     {
646         const json element = R"(
647             {
648                 "name": "VCS_CPU1",
649                 "compare_voltage_to_limit": 23
650             }
651         )"_json;
652         parseRail(element);
653         ADD_FAILURE() << "Should not have reached this line.";
654     }
655     catch (const std::invalid_argument& e)
656     {
657         EXPECT_STREQ(e.what(), "Element is not a boolean");
658     }
659 
660     // Test where fails: gpio value is invalid
661     try
662     {
663         const json element = R"(
664             {
665                 "name": "VCS_CPU1",
666                 "gpio": 131
667             }
668         )"_json;
669         parseRail(element);
670         ADD_FAILURE() << "Should not have reached this line.";
671     }
672     catch (const std::invalid_argument& e)
673     {
674         EXPECT_STREQ(e.what(), "Element is not an object");
675     }
676 
677     // Test where fails: check_status_vout is true and page not specified
678     try
679     {
680         const json element = R"(
681             {
682                 "name": "VCS_CPU1",
683                 "check_status_vout": true
684             }
685         )"_json;
686         parseRail(element);
687         ADD_FAILURE() << "Should not have reached this line.";
688     }
689     catch (const std::invalid_argument& e)
690     {
691         EXPECT_STREQ(e.what(), "Required property missing: page");
692     }
693 
694     // Test where fails: compare_voltage_to_limit is true and page not
695     // specified
696     try
697     {
698         const json element = R"(
699             {
700                 "name": "VCS_CPU1",
701                 "compare_voltage_to_limit": true
702             }
703         )"_json;
704         parseRail(element);
705         ADD_FAILURE() << "Should not have reached this line.";
706     }
707     catch (const std::invalid_argument& e)
708     {
709         EXPECT_STREQ(e.what(), "Required property missing: page");
710     }
711 
712     // Test where fails: Invalid property specified
713     try
714     {
715         const json element = R"(
716             {
717                 "name": "VCS_CPU1",
718                 "foo": "bar"
719             }
720         )"_json;
721         parseRail(element);
722         ADD_FAILURE() << "Should not have reached this line.";
723     }
724     catch (const std::invalid_argument& e)
725     {
726         EXPECT_STREQ(e.what(), "Element contains an invalid property");
727     }
728 }
729 
730 TEST(ConfigFileParserTests, ParseRailArray)
731 {
732     // Test where works: Array is empty
733     {
734         const json element = R"(
735             [
736             ]
737         )"_json;
738         std::vector<std::unique_ptr<Rail>> rails = parseRailArray(element);
739         EXPECT_EQ(rails.size(), 0);
740     }
741 
742     // Test where works: Array is not empty
743     {
744         const json element = R"(
745             [
746                 { "name": "VDD_CPU0" },
747                 { "name": "VCS_CPU1" }
748             ]
749         )"_json;
750         std::vector<std::unique_ptr<Rail>> rails = parseRailArray(element);
751         EXPECT_EQ(rails.size(), 2);
752         EXPECT_EQ(rails[0]->getName(), "VDD_CPU0");
753         EXPECT_EQ(rails[1]->getName(), "VCS_CPU1");
754     }
755 
756     // Test where fails: Element is not an array
757     try
758     {
759         const json element = R"(
760             {
761                 "foo": "bar"
762             }
763         )"_json;
764         parseRailArray(element);
765         ADD_FAILURE() << "Should not have reached this line.";
766     }
767     catch (const std::invalid_argument& e)
768     {
769         EXPECT_STREQ(e.what(), "Element is not an array");
770     }
771 
772     // Test where fails: Element within array is invalid
773     try
774     {
775         const json element = R"(
776             [
777                 { "name": "VDD_CPU0" },
778                 23
779             ]
780         )"_json;
781         parseRailArray(element);
782         ADD_FAILURE() << "Should not have reached this line.";
783     }
784     catch (const std::invalid_argument& e)
785     {
786         EXPECT_STREQ(e.what(), "Element is not an object");
787     }
788 }
789 
790 TEST(ConfigFileParserTests, ParseRoot)
791 {
792     // Test where works
793     {
794         const json element = R"(
795             {
796                 "rails": [
797                     {
798                         "name": "VDD_CPU0",
799                         "page": 11,
800                         "check_status_vout": true
801                     },
802                     {
803                         "name": "VCS_CPU1",
804                         "presence": "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1",
805                         "gpio": { "line": 60 }
806                     }
807                 ]
808             }
809         )"_json;
810         std::vector<std::unique_ptr<Rail>> rails = parseRoot(element);
811         EXPECT_EQ(rails.size(), 2);
812         EXPECT_EQ(rails[0]->getName(), "VDD_CPU0");
813         EXPECT_EQ(rails[1]->getName(), "VCS_CPU1");
814     }
815 
816     // Test where fails: Element is not an object
817     try
818     {
819         const json element = R"( [ "VDD_CPU0", "VCS_CPU1" ] )"_json;
820         parseRoot(element);
821         ADD_FAILURE() << "Should not have reached this line.";
822     }
823     catch (const std::invalid_argument& e)
824     {
825         EXPECT_STREQ(e.what(), "Element is not an object");
826     }
827 
828     // Test where fails: Required rails property not specified
829     try
830     {
831         const json element = R"(
832             {
833             }
834         )"_json;
835         parseRoot(element);
836         ADD_FAILURE() << "Should not have reached this line.";
837     }
838     catch (const std::invalid_argument& e)
839     {
840         EXPECT_STREQ(e.what(), "Required property missing: rails");
841     }
842 
843     // Test where fails: rails value is invalid
844     try
845     {
846         const json element = R"(
847             {
848                 "rails": 31
849             }
850         )"_json;
851         parseRoot(element);
852         ADD_FAILURE() << "Should not have reached this line.";
853     }
854     catch (const std::invalid_argument& e)
855     {
856         EXPECT_STREQ(e.what(), "Element is not an array");
857     }
858 
859     // Test where fails: Invalid property specified
860     try
861     {
862         const json element = R"(
863             {
864                 "rails": [
865                     {
866                         "name": "VDD_CPU0",
867                         "page": 11,
868                         "check_status_vout": true
869                     }
870                 ],
871                 "foo": true
872             }
873         )"_json;
874         parseRoot(element);
875         ADD_FAILURE() << "Should not have reached this line.";
876     }
877     catch (const std::invalid_argument& e)
878     {
879         EXPECT_STREQ(e.what(), "Element contains an invalid property");
880     }
881 }
882 
883 TEST(ConfigFileParserTests, ParseString)
884 {
885     // Test where works: Empty string
886     {
887         const json element = "";
888         std::string value = parseString(element, true);
889         EXPECT_EQ(value, "");
890     }
891 
892     // Test where works: Non-empty string
893     {
894         const json element = "vdd_cpu1";
895         std::string value = parseString(element, false);
896         EXPECT_EQ(value, "vdd_cpu1");
897     }
898 
899     // Test where fails: Element is not a string
900     try
901     {
902         const json element = R"( { "foo": "bar" } )"_json;
903         parseString(element);
904         ADD_FAILURE() << "Should not have reached this line.";
905     }
906     catch (const std::invalid_argument& e)
907     {
908         EXPECT_STREQ(e.what(), "Element is not a string");
909     }
910 
911     // Test where fails: Empty string
912     try
913     {
914         const json element = "";
915         parseString(element);
916         ADD_FAILURE() << "Should not have reached this line.";
917     }
918     catch (const std::invalid_argument& e)
919     {
920         EXPECT_STREQ(e.what(), "Element contains an empty string");
921     }
922 }
923 
924 TEST(ConfigFileParserTests, ParseUint8)
925 {
926     // Test where works: 0
927     {
928         const json element = R"( 0 )"_json;
929         uint8_t value = parseUint8(element);
930         EXPECT_EQ(value, 0);
931     }
932 
933     // Test where works: UINT8_MAX
934     {
935         const json element = R"( 255 )"_json;
936         uint8_t value = parseUint8(element);
937         EXPECT_EQ(value, 255);
938     }
939 
940     // Test where fails: Element is not an integer
941     try
942     {
943         const json element = R"( 1.03 )"_json;
944         parseUint8(element);
945         ADD_FAILURE() << "Should not have reached this line.";
946     }
947     catch (const std::invalid_argument& e)
948     {
949         EXPECT_STREQ(e.what(), "Element is not an integer");
950     }
951 
952     // Test where fails: Value < 0
953     try
954     {
955         const json element = R"( -1 )"_json;
956         parseUint8(element);
957         ADD_FAILURE() << "Should not have reached this line.";
958     }
959     catch (const std::invalid_argument& e)
960     {
961         EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
962     }
963 
964     // Test where fails: Value > UINT8_MAX
965     try
966     {
967         const json element = R"( 256 )"_json;
968         parseUint8(element);
969         ADD_FAILURE() << "Should not have reached this line.";
970     }
971     catch (const std::invalid_argument& e)
972     {
973         EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
974     }
975 }
976 
977 TEST(ConfigFileParserTests, ParseUnsignedInteger)
978 {
979     // Test where works: 1
980     {
981         const json element = R"( 1 )"_json;
982         unsigned int value = parseUnsignedInteger(element);
983         EXPECT_EQ(value, 1);
984     }
985 
986     // Test where fails: Element is not an integer
987     try
988     {
989         const json element = R"( 1.5 )"_json;
990         parseUnsignedInteger(element);
991         ADD_FAILURE() << "Should not have reached this line.";
992     }
993     catch (const std::invalid_argument& e)
994     {
995         EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
996     }
997 
998     // Test where fails: Value < 0
999     try
1000     {
1001         const json element = R"( -1 )"_json;
1002         parseUnsignedInteger(element);
1003         ADD_FAILURE() << "Should not have reached this line.";
1004     }
1005     catch (const std::invalid_argument& e)
1006     {
1007         EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
1008     }
1009 }
1010 
1011 TEST(ConfigFileParserTests, VerifyIsArray)
1012 {
1013     // Test where element is an array
1014     {
1015         const json element = R"( [ "foo", "bar" ] )"_json;
1016         verifyIsArray(element);
1017     }
1018 
1019     // Test where element is not an array
1020     try
1021     {
1022         const json element = R"( { "foo": "bar" } )"_json;
1023         verifyIsArray(element);
1024         ADD_FAILURE() << "Should not have reached this line.";
1025     }
1026     catch (const std::invalid_argument& e)
1027     {
1028         EXPECT_STREQ(e.what(), "Element is not an array");
1029     }
1030 }
1031 
1032 TEST(ConfigFileParserTests, VerifyIsObject)
1033 {
1034     // Test where element is an object
1035     {
1036         const json element = R"( { "foo": "bar" } )"_json;
1037         verifyIsObject(element);
1038     }
1039 
1040     // Test where element is not an object
1041     try
1042     {
1043         const json element = R"( [ "foo", "bar" ] )"_json;
1044         verifyIsObject(element);
1045         ADD_FAILURE() << "Should not have reached this line.";
1046     }
1047     catch (const std::invalid_argument& e)
1048     {
1049         EXPECT_STREQ(e.what(), "Element is not an object");
1050     }
1051 }
1052 
1053 TEST(ConfigFileParserTests, VerifyPropertyCount)
1054 {
1055     // Test where element has expected number of properties
1056     {
1057         const json element = R"(
1058             {
1059                 "line": 131,
1060                 "active_low": true
1061             }
1062         )"_json;
1063         verifyPropertyCount(element, 2);
1064     }
1065 
1066     // Test where element has unexpected number of properties
1067     try
1068     {
1069         const json element = R"(
1070             {
1071                 "line": 131,
1072                 "active_low": true,
1073                 "foo": 1.3
1074             }
1075         )"_json;
1076         verifyPropertyCount(element, 2);
1077         ADD_FAILURE() << "Should not have reached this line.";
1078     }
1079     catch (const std::invalid_argument& e)
1080     {
1081         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1082     }
1083 }
1084