xref: /openbmc/libbej/test/bej_encoder_test.cpp (revision 2bc745a3)
1 #include "bej_dictionary.h"
2 #include "bej_encoder_core.h"
3 #include "bej_tree.h"
4 
5 #include "bej_common_test.hpp"
6 #include "bej_decoder_json.hpp"
7 #include "bej_encoder_json.hpp"
8 
9 #include <vector>
10 
11 #include <gmock/gmock-matchers.h>
12 #include <gmock/gmock.h>
13 #include <gtest/gtest.h>
14 
15 namespace libbej
16 {
17 
18 struct BejEncoderTestParams
19 {
20     const std::string testName;
21     const BejTestInputFiles inputFiles;
22     std::string expectedJson;
23     struct RedfishPropertyParent* (*createResource)();
24 };
25 
26 using BejEncoderTest = testing::TestWithParam<BejEncoderTestParams>;
27 
28 const BejTestInputFiles dummySimpleTestFiles = {
29     .jsonFile = "../test/json/dummysimple.json",
30     .schemaDictionaryFile = "../test/dictionaries/dummy_simple_dict.bin",
31     .annotationDictionaryFile = "../test/dictionaries/annotation_dict.bin",
32     .errorDictionaryFile = "",
33     .encodedStreamFile = "../test/encoded/dummy_simple_enc.bin",
34 };
35 
36 const BejTestInputFiles driveOemTestFiles = {
37     .jsonFile = "../test/json/drive_oem.json",
38     .schemaDictionaryFile = "../test/dictionaries/drive_oem_dict.bin",
39     .annotationDictionaryFile = "../test/dictionaries/annotation_dict.bin",
40     .errorDictionaryFile = "",
41     .encodedStreamFile = "../test/encoded/drive_oem_enc.bin",
42 };
43 
44 const BejTestInputFiles chassisTestFiles = {
45     .jsonFile = "../test/json/chassis.json",
46     .schemaDictionaryFile = "../test/dictionaries/chassis_dict.bin",
47     .annotationDictionaryFile = "../test/dictionaries/annotation_dict.bin",
48     .errorDictionaryFile = "",
49     .encodedStreamFile = "../test/encoded/chassis_enc.bin",
50 };
51 
52 struct RedfishPropertyParent* createDummyResource()
53 {
54     static struct RedfishPropertyParent root;
55     bejTreeInitSet(&root, "DummySimple");
56 
57     static struct RedfishPropertyLeafString id;
58     bejTreeAddString(&root, &id, "Id", "Dummy ID");
59 
60     static struct RedfishPropertyLeafInt intProp;
61     bejTreeAddInteger(&root, &intProp, "SampleIntegerProperty", -5);
62 
63     static struct RedfishPropertyLeafReal real;
64     bejTreeAddReal(&root, &real, "SampleRealProperty", -5576.90001);
65 
66     static struct RedfishPropertyLeafNull enProp;
67     bejTreeAddNull(&root, &enProp, "SampleEnabledProperty");
68 
69     static struct RedfishPropertyParent chArraySet1;
70     bejTreeInitSet(&chArraySet1, nullptr);
71 
72     static struct RedfishPropertyLeafBool chArraySet1bool;
73     bejTreeAddBool(&chArraySet1, &chArraySet1bool, "AnotherBoolean", true);
74 
75     static struct RedfishPropertyLeafEnum chArraySet1Ls;
76     bejTreeAddEnum(&chArraySet1, &chArraySet1Ls, "LinkStatus", "NoLink");
77 
78     static struct RedfishPropertyParent chArraySet2;
79     bejTreeInitSet(&chArraySet2, nullptr);
80 
81     static struct RedfishPropertyLeafEnum chArraySet2Ls;
82     bejTreeAddEnum(&chArraySet2, &chArraySet2Ls, "LinkStatus", "LinkDown");
83 
84     static struct RedfishPropertyParent chArray;
85     bejTreeInitArray(&chArray, "ChildArrayProperty");
86 
87     bejTreeLinkChildToParent(&chArray, &chArraySet1);
88     bejTreeLinkChildToParent(&chArray, &chArraySet2);
89 
90     bejTreeLinkChildToParent(&root, &chArray);
91     return &root;
92 }
93 
94 const std::string driveOemJson = R"(
95       {
96         "@odata.id": "/redfish/v1/drives/1",
97         "@odata.type": "#Drive.v1_5_0.Drive",
98         "Id": "Drive1",
99         "Actions": {
100             "#Drive.Reset": {
101                 "target": "/redfish/v1/drives/1/Actions/Drive.Reset",
102                 "title": "Reset a Drive",
103                 "ResetType@Redfish.AllowableValues": [
104                         "On",
105                         "ForceOff",
106                         "ForceRestart",
107                         "Nmi",
108                         "ForceOn",
109                         "PushPowerButton"
110                     ]
111             }
112         },
113         "Status@Message.ExtendedInfo": [
114             {
115                 "MessageId": "PredictiveFailure",
116                 "RelatedProperties": ["FailurePredicted", "MediaType"]
117             }
118         ],
119         "Identifiers": [],
120         "Links": {}
121     }
122 )";
123 
124 struct RedfishPropertyParent* createDriveOem()
125 {
126     static struct RedfishPropertyParent root;
127     bejTreeInitSet(&root, "Drive");
128 
129     static struct RedfishPropertyLeafString odataId;
130     bejTreeAddString(&root, &odataId, "@odata.id", "/redfish/v1/drives/1");
131 
132     static struct RedfishPropertyLeafString odataType;
133     bejTreeAddString(&root, &odataType, "@odata.type", "#Drive.v1_5_0.Drive");
134 
135     static struct RedfishPropertyLeafString id;
136     bejTreeAddString(&root, &id, "Id", "Drive1");
137 
138     static struct RedfishPropertyParent actions;
139     bejTreeInitSet(&actions, "Actions");
140 
141     static struct RedfishPropertyParent drRst;
142     bejTreeInitSet(&drRst, "#Drive.Reset");
143 
144     static struct RedfishPropertyLeafString drRstTarget;
145     bejTreeAddString(&drRst, &drRstTarget, "target",
146                      "/redfish/v1/drives/1/Actions/Drive.Reset");
147 
148     static struct RedfishPropertyLeafString drRstTitle;
149     bejTreeAddString(&drRst, &drRstTitle, "title", "Reset a Drive");
150 
151     static struct RedfishPropertyParent drRstType;
152     bejTreeInitPropertyAnnotated(&drRstType, "ResetType");
153 
154     static struct RedfishPropertyParent drRstTypeAllowable;
155     bejTreeInitArray(&drRstTypeAllowable, "@Redfish.AllowableValues");
156 
157     static struct RedfishPropertyLeafString drRstTypeAllowableS1;
158     bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS1, "", "On");
159 
160     static struct RedfishPropertyLeafString drRstTypeAllowableS2;
161     bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS2, "",
162                      "ForceOff");
163 
164     static struct RedfishPropertyLeafString drRstTypeAllowableS3;
165     bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS3, "",
166                      "ForceRestart");
167 
168     static struct RedfishPropertyLeafString drRstTypeAllowableS4;
169     bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS4, "", "Nmi");
170 
171     static struct RedfishPropertyLeafString drRstTypeAllowableS5;
172     bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS5, "", "ForceOn");
173 
174     static struct RedfishPropertyLeafString drRstTypeAllowableS6;
175     bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS6, "",
176                      "PushPowerButton");
177 
178     bejTreeLinkChildToParent(&drRstType, &drRstTypeAllowable);
179     bejTreeLinkChildToParent(&drRst, &drRstType);
180     bejTreeLinkChildToParent(&actions, &drRst);
181     bejTreeLinkChildToParent(&root, &actions);
182 
183     static struct RedfishPropertyParent statusAnt;
184     bejTreeInitPropertyAnnotated(&statusAnt, "Status");
185 
186     static struct RedfishPropertyParent statusAntMsgExtInfo;
187     bejTreeInitArray(&statusAntMsgExtInfo, "@Message.ExtendedInfo");
188 
189     static struct RedfishPropertyParent statusAntMsgExtInfoSet1;
190     bejTreeInitSet(&statusAntMsgExtInfoSet1, nullptr);
191 
192     static struct RedfishPropertyLeafString statusAntMsgExtInfoSet1P1;
193     bejTreeAddString(&statusAntMsgExtInfoSet1, &statusAntMsgExtInfoSet1P1,
194                      "MessageId", "PredictiveFailure");
195 
196     static struct RedfishPropertyParent statusAntMsgExtInfoSet1P2;
197     bejTreeInitArray(&statusAntMsgExtInfoSet1P2, "RelatedProperties");
198     bejTreeLinkChildToParent(&statusAntMsgExtInfoSet1,
199                              &statusAntMsgExtInfoSet1P2);
200 
201     static struct RedfishPropertyLeafString statusAntMsgExtInfoSet1P2Ele1;
202     bejTreeAddString(&statusAntMsgExtInfoSet1P2, &statusAntMsgExtInfoSet1P2Ele1,
203                      "", "FailurePredicted");
204 
205     static struct RedfishPropertyLeafString statusAntMsgExtInfoSet1P2Ele2;
206     bejTreeAddString(&statusAntMsgExtInfoSet1P2, &statusAntMsgExtInfoSet1P2Ele2,
207                      "", "MediaType");
208 
209     bejTreeLinkChildToParent(&statusAntMsgExtInfo, &statusAntMsgExtInfoSet1);
210     bejTreeLinkChildToParent(&statusAnt, &statusAntMsgExtInfo);
211     bejTreeLinkChildToParent(&root, &statusAnt);
212 
213     static struct RedfishPropertyParent identifiers;
214     bejTreeInitArray(&identifiers, "Identifiers");
215     bejTreeLinkChildToParent(&root, &identifiers);
216 
217     static struct RedfishPropertyParent links;
218     bejTreeInitSet(&links, "Links");
219     bejTreeLinkChildToParent(&root, &links);
220 
221     return &root;
222 }
223 
224 /**
225  * @brief Storage for an array of links with an annotated odata.count.
226  *
227  * This doesn't contain storage for the link itself.
228  *
229  * Eg:
230  * "Contains": [],
231  * "Contains@odata.count": 0,
232  */
233 struct RedfishArrayOfLinksJson
234 {
235     struct RedfishPropertyParent array;
236     struct RedfishPropertyParent annotatedProperty;
237     struct RedfishPropertyLeafInt count;
238 };
239 
240 /**
241  * @brief Storage for a single odata.id link inside a JSON "Set" object.
242  *
243  * Eg: FieldName: {
244  *   "@odata.id": "/redfish/v1/Chassis/Something"
245  * }
246  */
247 struct RedfishLinkJson
248 {
249     struct RedfishPropertyParent set;
250     struct RedfishPropertyLeafString odataId;
251 };
252 
253 void addLinkToTree(struct RedfishPropertyParent* parent,
254                    struct RedfishPropertyParent* linkSet,
255                    const char* linkSetLabel,
256                    struct RedfishPropertyLeafString* odataId,
257                    const char* linkValue)
258 {
259     bejTreeInitSet(linkSet, linkSetLabel);
260     bejTreeAddString(linkSet, odataId, "@odata.id", linkValue);
261     bejTreeLinkChildToParent(parent, linkSet);
262 }
263 
264 void redfishCreateArrayOfLinksJson(struct RedfishPropertyParent* parent,
265                                    const char* arrayName, int linkCount,
266                                    const char* const links[],
267                                    struct RedfishArrayOfLinksJson* linksInfo,
268                                    struct RedfishLinkJson* linkJsonArray)
269 {
270     bejTreeInitArray(&linksInfo->array, arrayName);
271     bejTreeLinkChildToParent(parent, &linksInfo->array);
272 
273     bejTreeInitPropertyAnnotated(&linksInfo->annotatedProperty, arrayName);
274     bejTreeAddInteger(&linksInfo->annotatedProperty, &linksInfo->count,
275                       "@odata.count", linkCount);
276     bejTreeLinkChildToParent(parent, &linksInfo->annotatedProperty);
277 
278     for (int i = 0; i < linkCount; ++i)
279     {
280         addLinkToTree(&linksInfo->array, &linkJsonArray[i].set, NULL,
281                       &linkJsonArray[i].odataId, links[i]);
282     }
283 }
284 
285 struct RedfishPropertyParent* createChassisResource()
286 {
287     constexpr int containsLinkCount = 2;
288     const char* contains[containsLinkCount] = {"/redfish/v1/Chassis/Disk_0",
289                                                "/redfish/v1/Chassis/Disk_1"};
290     const char* storage[1] = {"/redfish/v1/Systems/system/Storage/SATA"};
291     const char* drives[1] = {"/redfish/v1/Chassis/SomeChassis/Drives/SATA_0"};
292     static struct RedfishPropertyParent root;
293     static struct RedfishPropertyLeafString odataId;
294     static struct RedfishPropertyParent links;
295     static struct RedfishPropertyParent computerSystemsArray;
296     static struct RedfishPropertyParent computerSystemsLinkSet;
297     static struct RedfishPropertyLeafString computerSystemsLinkOdataId;
298     static struct RedfishPropertyParent containedBySet;
299     static struct RedfishPropertyLeafString containedByOdataId;
300     static struct RedfishArrayOfLinksJson containsArray;
301     static struct RedfishLinkJson containsLinks[containsLinkCount];
302     static struct RedfishArrayOfLinksJson storageArray;
303     static struct RedfishLinkJson storageLink;
304     static struct RedfishArrayOfLinksJson drives_array;
305     static struct RedfishLinkJson drive_link;
306 
307     bejTreeInitSet(&root, "Chassis");
308     bejTreeAddString(&root, &odataId, "@odata.id",
309                      "/redfish/v1/Chassis/SomeChassis");
310 
311     bejTreeInitSet(&links, "Links");
312     bejTreeLinkChildToParent(&root, &links);
313 
314     bejTreeInitArray(&computerSystemsArray, "ComputerSystems");
315     bejTreeLinkChildToParent(&links, &computerSystemsArray);
316 
317     addLinkToTree(&computerSystemsArray, &computerSystemsLinkSet, "",
318                   &computerSystemsLinkOdataId, "/redfish/v1/Systems/system");
319 
320     addLinkToTree(&links, &containedBySet, "ContainedBy", &containedByOdataId,
321                   "/redfish/v1/Chassis/SomeOtherChassis");
322 
323     redfishCreateArrayOfLinksJson(&links, "Contains", containsLinkCount,
324                                   contains, &containsArray, containsLinks);
325 
326     redfishCreateArrayOfLinksJson(&links, "Storage", /*linkCount=*/1, storage,
327                                   &storageArray, &storageLink);
328 
329     redfishCreateArrayOfLinksJson(&links, "Drives", /*linkCount=*/1, drives,
330                                   &drives_array, &drive_link);
331 
332     return &root;
333 }
334 
335 TEST_P(BejEncoderTest, Encode)
336 {
337     const BejEncoderTestParams& test_case = GetParam();
338     auto inputsOrErr = loadInputs(test_case.inputFiles);
339     EXPECT_TRUE(inputsOrErr);
340 
341     BejDictionaries dictionaries = {
342         .schemaDictionary = inputsOrErr->schemaDictionary,
343         .annotationDictionary = inputsOrErr->annotationDictionary,
344         .errorDictionary = inputsOrErr->errorDictionary,
345     };
346 
347     std::vector<uint8_t> outputBuffer;
348     struct BejEncoderOutputHandler output = {
349         .handlerContext = &outputBuffer,
350         .recvOutput = &getBejEncodedBuffer,
351     };
352 
353     std::vector<void*> pointerStack;
354     struct BejPointerStackCallback stackCallbacks = {
355         .stackContext = &pointerStack,
356         .stackEmpty = stackEmpty,
357         .stackPeek = stackPeek,
358         .stackPop = stackPop,
359         .stackPush = stackPush,
360         .deleteStack = NULL,
361     };
362 
363     bejEncode(&dictionaries, BEJ_DICTIONARY_START_AT_HEAD, bejMajorSchemaClass,
364               test_case.createResource(), &output, &stackCallbacks);
365 
366     BejDecoderJson decoder;
367     EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer)), 0);
368     std::string decoded = decoder.getOutput();
369     nlohmann::json jsonDecoded = nlohmann::json::parse(decoded);
370 
371     if (!test_case.expectedJson.empty())
372     {
373         inputsOrErr->expectedJson =
374             nlohmann::json::parse(test_case.expectedJson);
375     }
376     EXPECT_TRUE(jsonDecoded.dump() == inputsOrErr->expectedJson.dump());
377 }
378 
379 TEST_P(BejEncoderTest, EncodeWrapper)
380 {
381     const BejEncoderTestParams& test_case = GetParam();
382     auto inputsOrErr = loadInputs(test_case.inputFiles);
383     EXPECT_TRUE(inputsOrErr);
384 
385     BejDictionaries dictionaries = {
386         .schemaDictionary = inputsOrErr->schemaDictionary,
387         .annotationDictionary = inputsOrErr->annotationDictionary,
388         .errorDictionary = inputsOrErr->errorDictionary,
389     };
390 
391     libbej::BejEncoderJson encoder;
392     encoder.encode(&dictionaries, bejMajorSchemaClass,
393                    test_case.createResource());
394 
395     std::vector<uint8_t> outputBuffer = encoder.getOutput();
396     BejDecoderJson decoder;
397     EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer)), 0);
398     std::string decoded = decoder.getOutput();
399     nlohmann::json jsonDecoded = nlohmann::json::parse(decoded);
400 
401     if (!test_case.expectedJson.empty())
402     {
403         inputsOrErr->expectedJson =
404             nlohmann::json::parse(test_case.expectedJson);
405     }
406     EXPECT_TRUE(jsonDecoded.dump() == inputsOrErr->expectedJson.dump());
407 
408     // Try using the same encoder object again to ensure that the same object
409     // does the encoding correctly
410     encoder.encode(&dictionaries, bejMajorSchemaClass,
411                    test_case.createResource());
412 
413     outputBuffer = encoder.getOutput();
414 
415     EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer)), 0);
416     decoded = decoder.getOutput();
417     jsonDecoded = nlohmann::json::parse(decoded);
418 
419     if (!test_case.expectedJson.empty())
420     {
421         inputsOrErr->expectedJson =
422             nlohmann::json::parse(test_case.expectedJson);
423     }
424     EXPECT_TRUE(jsonDecoded.dump() == inputsOrErr->expectedJson.dump());
425 }
426 
427 /**
428  * TODO: Add more test cases.
429  */
430 
431 INSTANTIATE_TEST_SUITE_P(
432     , BejEncoderTest,
433     testing::ValuesIn<BejEncoderTestParams>({
434         {"DriveOEM", driveOemTestFiles, driveOemJson, &createDriveOem},
435         {"DummySimple", dummySimpleTestFiles, "", &createDummyResource},
436         {"Chassis", chassisTestFiles, "", &createChassisResource},
437     }),
438     [](const testing::TestParamInfo<BejEncoderTest::ParamType>& info) {
439     return info.param.testName;
440 });
441 
442 } // namespace libbej
443