xref: /openbmc/libbej/test/bej_encoder_test.cpp (revision be27f2e9)
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(
265     struct RedfishPropertyParent* parent, const char* arrayName, int linkCount,
266     const char* const links[], struct RedfishArrayOfLinksJson* linksInfo,
267     struct RedfishLinkJson* linkJsonArray)
268 {
269     bejTreeInitArray(&linksInfo->array, arrayName);
270     bejTreeLinkChildToParent(parent, &linksInfo->array);
271 
272     bejTreeInitPropertyAnnotated(&linksInfo->annotatedProperty, arrayName);
273     bejTreeAddInteger(&linksInfo->annotatedProperty, &linksInfo->count,
274                       "@odata.count", linkCount);
275     bejTreeLinkChildToParent(parent, &linksInfo->annotatedProperty);
276 
277     for (int i = 0; i < linkCount; ++i)
278     {
279         addLinkToTree(&linksInfo->array, &linkJsonArray[i].set, NULL,
280                       &linkJsonArray[i].odataId, links[i]);
281     }
282 }
283 
284 struct RedfishPropertyParent* createChassisResource()
285 {
286     constexpr int containsLinkCount = 2;
287     const char* contains[containsLinkCount] = {"/redfish/v1/Chassis/Disk_0",
288                                                "/redfish/v1/Chassis/Disk_1"};
289     const char* storage[1] = {"/redfish/v1/Systems/system/Storage/SATA"};
290     const char* drives[1] = {"/redfish/v1/Chassis/SomeChassis/Drives/SATA_0"};
291     static struct RedfishPropertyParent root;
292     static struct RedfishPropertyLeafString odataId;
293     static struct RedfishPropertyParent links;
294     static struct RedfishPropertyParent computerSystemsArray;
295     static struct RedfishPropertyParent computerSystemsLinkSet;
296     static struct RedfishPropertyLeafString computerSystemsLinkOdataId;
297     static struct RedfishPropertyParent containedBySet;
298     static struct RedfishPropertyLeafString containedByOdataId;
299     static struct RedfishArrayOfLinksJson containsArray;
300     static struct RedfishLinkJson containsLinks[containsLinkCount];
301     static struct RedfishArrayOfLinksJson storageArray;
302     static struct RedfishLinkJson storageLink;
303     static struct RedfishArrayOfLinksJson drives_array;
304     static struct RedfishLinkJson drive_link;
305 
306     bejTreeInitSet(&root, "Chassis");
307     bejTreeAddString(&root, &odataId, "@odata.id",
308                      "/redfish/v1/Chassis/SomeChassis");
309 
310     bejTreeInitSet(&links, "Links");
311     bejTreeLinkChildToParent(&root, &links);
312 
313     bejTreeInitArray(&computerSystemsArray, "ComputerSystems");
314     bejTreeLinkChildToParent(&links, &computerSystemsArray);
315 
316     addLinkToTree(&computerSystemsArray, &computerSystemsLinkSet, "",
317                   &computerSystemsLinkOdataId, "/redfish/v1/Systems/system");
318 
319     addLinkToTree(&links, &containedBySet, "ContainedBy", &containedByOdataId,
320                   "/redfish/v1/Chassis/SomeOtherChassis");
321 
322     redfishCreateArrayOfLinksJson(&links, "Contains", containsLinkCount,
323                                   contains, &containsArray, containsLinks);
324 
325     redfishCreateArrayOfLinksJson(&links, "Storage", /*linkCount=*/1, storage,
326                                   &storageArray, &storageLink);
327 
328     redfishCreateArrayOfLinksJson(&links, "Drives", /*linkCount=*/1, drives,
329                                   &drives_array, &drive_link);
330 
331     return &root;
332 }
333 
334 TEST_P(BejEncoderTest, Encode)
335 {
336     const BejEncoderTestParams& test_case = GetParam();
337     auto inputsOrErr = loadInputs(test_case.inputFiles);
338     EXPECT_TRUE(inputsOrErr);
339 
340     BejDictionaries dictionaries = {
341         .schemaDictionary = inputsOrErr->schemaDictionary,
342         .annotationDictionary = inputsOrErr->annotationDictionary,
343         .errorDictionary = inputsOrErr->errorDictionary,
344     };
345 
346     std::vector<uint8_t> outputBuffer;
347     struct BejEncoderOutputHandler output = {
348         .handlerContext = &outputBuffer,
349         .recvOutput = &getBejEncodedBuffer,
350     };
351 
352     std::vector<void*> pointerStack;
353     struct BejPointerStackCallback stackCallbacks = {
354         .stackContext = &pointerStack,
355         .stackEmpty = stackEmpty,
356         .stackPeek = stackPeek,
357         .stackPop = stackPop,
358         .stackPush = stackPush,
359         .deleteStack = NULL,
360     };
361 
362     bejEncode(&dictionaries, BEJ_DICTIONARY_START_AT_HEAD, bejMajorSchemaClass,
363               test_case.createResource(), &output, &stackCallbacks);
364 
365     BejDecoderJson decoder;
366     EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer)), 0);
367     std::string decoded = decoder.getOutput();
368     nlohmann::json jsonDecoded = nlohmann::json::parse(decoded);
369 
370     if (!test_case.expectedJson.empty())
371     {
372         inputsOrErr->expectedJson =
373             nlohmann::json::parse(test_case.expectedJson);
374     }
375     EXPECT_TRUE(jsonDecoded.dump() == inputsOrErr->expectedJson.dump());
376 }
377 
378 TEST_P(BejEncoderTest, EncodeWrapper)
379 {
380     const BejEncoderTestParams& test_case = GetParam();
381     auto inputsOrErr = loadInputs(test_case.inputFiles);
382     EXPECT_TRUE(inputsOrErr);
383 
384     BejDictionaries dictionaries = {
385         .schemaDictionary = inputsOrErr->schemaDictionary,
386         .annotationDictionary = inputsOrErr->annotationDictionary,
387         .errorDictionary = inputsOrErr->errorDictionary,
388     };
389 
390     libbej::BejEncoderJson encoder;
391     encoder.encode(&dictionaries, bejMajorSchemaClass,
392                    test_case.createResource());
393 
394     std::vector<uint8_t> outputBuffer = encoder.getOutput();
395     BejDecoderJson decoder;
396     EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer)), 0);
397     std::string decoded = decoder.getOutput();
398     nlohmann::json jsonDecoded = nlohmann::json::parse(decoded);
399 
400     if (!test_case.expectedJson.empty())
401     {
402         inputsOrErr->expectedJson =
403             nlohmann::json::parse(test_case.expectedJson);
404     }
405     EXPECT_TRUE(jsonDecoded.dump() == inputsOrErr->expectedJson.dump());
406 
407     // Try using the same encoder object again to ensure that the same object
408     // does the encoding correctly
409     encoder.encode(&dictionaries, bejMajorSchemaClass,
410                    test_case.createResource());
411 
412     outputBuffer = encoder.getOutput();
413 
414     EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer)), 0);
415     decoded = decoder.getOutput();
416     jsonDecoded = nlohmann::json::parse(decoded);
417 
418     if (!test_case.expectedJson.empty())
419     {
420         inputsOrErr->expectedJson =
421             nlohmann::json::parse(test_case.expectedJson);
422     }
423     EXPECT_TRUE(jsonDecoded.dump() == inputsOrErr->expectedJson.dump());
424 }
425 
426 /**
427  * TODO: Add more test cases.
428  */
429 
430 INSTANTIATE_TEST_SUITE_P(
431     , BejEncoderTest,
432     testing::ValuesIn<BejEncoderTestParams>({
433         {"DriveOEM", driveOemTestFiles, driveOemJson, &createDriveOem},
434         {"DummySimple", dummySimpleTestFiles, "", &createDummyResource},
435         {"Chassis", chassisTestFiles, "", &createChassisResource},
436     }),
437     [](const testing::TestParamInfo<BejEncoderTest::ParamType>& info) {
438         return info.param.testName;
439     });
440 
441 } // namespace libbej
442