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