#include "bej_dictionary.h" #include "bej_encoder_core.h" #include "bej_tree.h" #include "bej_common_test.hpp" #include "bej_decoder_json.hpp" #include "bej_encoder_json.hpp" #include #include #include #include namespace libbej { struct BejEncoderTestParams { const std::string testName; const BejTestInputFiles inputFiles; std::string expectedJson; struct RedfishPropertyParent* (*createResource)(); }; void PrintTo(const BejEncoderTestParams& params, std::ostream* os) { *os << params.testName; } using BejEncoderTest = testing::TestWithParam; const BejTestInputFiles dummySimpleTestFiles = { .jsonFile = "../test/json/dummysimple.json", .schemaDictionaryFile = "../test/dictionaries/dummy_simple_dict.bin", .annotationDictionaryFile = "../test/dictionaries/annotation_dict.bin", .errorDictionaryFile = "", .encodedStreamFile = "../test/encoded/dummy_simple_enc.bin", }; const BejTestInputFiles driveOemTestFiles = { .jsonFile = "../test/json/drive_oem.json", .schemaDictionaryFile = "../test/dictionaries/drive_oem_dict.bin", .annotationDictionaryFile = "../test/dictionaries/annotation_dict.bin", .errorDictionaryFile = "", .encodedStreamFile = "../test/encoded/drive_oem_enc.bin", }; const BejTestInputFiles chassisTestFiles = { .jsonFile = "../test/json/chassis.json", .schemaDictionaryFile = "../test/dictionaries/chassis_dict.bin", .annotationDictionaryFile = "../test/dictionaries/annotation_dict.bin", .errorDictionaryFile = "", .encodedStreamFile = "../test/encoded/chassis_enc.bin", }; struct RedfishPropertyParent* createDummyResource() { static struct RedfishPropertyParent root; bejTreeInitSet(&root, "DummySimple"); static struct RedfishPropertyLeafString id; bejTreeAddString(&root, &id, "Id", "Dummy ID"); static struct RedfishPropertyLeafInt intProp; bejTreeAddInteger(&root, &intProp, "SampleIntegerProperty", -5); static struct RedfishPropertyLeafReal real; bejTreeAddReal(&root, &real, "SampleRealProperty", -5576.90001); static struct RedfishPropertyLeafNull enProp; bejTreeAddNull(&root, &enProp, "SampleEnabledProperty"); static struct RedfishPropertyParent chArraySet1; bejTreeInitSet(&chArraySet1, nullptr); static struct RedfishPropertyLeafBool chArraySet1bool; bejTreeAddBool(&chArraySet1, &chArraySet1bool, "AnotherBoolean", true); static struct RedfishPropertyLeafEnum chArraySet1Ls; bejTreeAddEnum(&chArraySet1, &chArraySet1Ls, "LinkStatus", "NoLink"); static struct RedfishPropertyParent chArraySet2; bejTreeInitSet(&chArraySet2, nullptr); static struct RedfishPropertyLeafEnum chArraySet2Ls; bejTreeAddEnum(&chArraySet2, &chArraySet2Ls, "LinkStatus", "LinkDown"); static struct RedfishPropertyParent chArray; bejTreeInitArray(&chArray, "ChildArrayProperty"); bejTreeLinkChildToParent(&chArray, &chArraySet1); bejTreeLinkChildToParent(&chArray, &chArraySet2); bejTreeLinkChildToParent(&root, &chArray); return &root; } const std::string driveOemJson = R"( { "@odata.id": "/redfish/v1/drives/1", "@odata.type": "#Drive.v1_5_0.Drive", "Id": "Drive1", "Actions": { "#Drive.Reset": { "target": "/redfish/v1/drives/1/Actions/Drive.Reset", "title": "Reset a Drive", "ResetType@Redfish.AllowableValues": [ "On", "ForceOff", "ForceRestart", "Nmi", "ForceOn", "PushPowerButton" ] } }, "Status@Message.ExtendedInfo": [ { "MessageId": "PredictiveFailure", "RelatedProperties": ["FailurePredicted", "MediaType"] } ], "Identifiers": [], "Links": {} } )"; struct RedfishPropertyParent* createDriveOem() { static struct RedfishPropertyParent root; bejTreeInitSet(&root, "Drive"); static struct RedfishPropertyLeafString odataId; bejTreeAddString(&root, &odataId, "@odata.id", "/redfish/v1/drives/1"); static struct RedfishPropertyLeafString odataType; bejTreeAddString(&root, &odataType, "@odata.type", "#Drive.v1_5_0.Drive"); static struct RedfishPropertyLeafString id; bejTreeAddString(&root, &id, "Id", "Drive1"); static struct RedfishPropertyParent actions; bejTreeInitSet(&actions, "Actions"); static struct RedfishPropertyParent drRst; bejTreeInitSet(&drRst, "#Drive.Reset"); static struct RedfishPropertyLeafString drRstTarget; bejTreeAddString(&drRst, &drRstTarget, "target", "/redfish/v1/drives/1/Actions/Drive.Reset"); static struct RedfishPropertyLeafString drRstTitle; bejTreeAddString(&drRst, &drRstTitle, "title", "Reset a Drive"); static struct RedfishPropertyParent drRstType; bejTreeInitPropertyAnnotated(&drRstType, "ResetType"); static struct RedfishPropertyParent drRstTypeAllowable; bejTreeInitArray(&drRstTypeAllowable, "@Redfish.AllowableValues"); static struct RedfishPropertyLeafString drRstTypeAllowableS1; bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS1, "", "On"); static struct RedfishPropertyLeafString drRstTypeAllowableS2; bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS2, "", "ForceOff"); static struct RedfishPropertyLeafString drRstTypeAllowableS3; bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS3, "", "ForceRestart"); static struct RedfishPropertyLeafString drRstTypeAllowableS4; bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS4, "", "Nmi"); static struct RedfishPropertyLeafString drRstTypeAllowableS5; bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS5, "", "ForceOn"); static struct RedfishPropertyLeafString drRstTypeAllowableS6; bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS6, "", "PushPowerButton"); bejTreeLinkChildToParent(&drRstType, &drRstTypeAllowable); bejTreeLinkChildToParent(&drRst, &drRstType); bejTreeLinkChildToParent(&actions, &drRst); bejTreeLinkChildToParent(&root, &actions); static struct RedfishPropertyParent statusAnt; bejTreeInitPropertyAnnotated(&statusAnt, "Status"); static struct RedfishPropertyParent statusAntMsgExtInfo; bejTreeInitArray(&statusAntMsgExtInfo, "@Message.ExtendedInfo"); static struct RedfishPropertyParent statusAntMsgExtInfoSet1; bejTreeInitSet(&statusAntMsgExtInfoSet1, nullptr); static struct RedfishPropertyLeafString statusAntMsgExtInfoSet1P1; bejTreeAddString(&statusAntMsgExtInfoSet1, &statusAntMsgExtInfoSet1P1, "MessageId", "PredictiveFailure"); static struct RedfishPropertyParent statusAntMsgExtInfoSet1P2; bejTreeInitArray(&statusAntMsgExtInfoSet1P2, "RelatedProperties"); bejTreeLinkChildToParent(&statusAntMsgExtInfoSet1, &statusAntMsgExtInfoSet1P2); static struct RedfishPropertyLeafString statusAntMsgExtInfoSet1P2Ele1; bejTreeAddString(&statusAntMsgExtInfoSet1P2, &statusAntMsgExtInfoSet1P2Ele1, "", "FailurePredicted"); static struct RedfishPropertyLeafString statusAntMsgExtInfoSet1P2Ele2; bejTreeAddString(&statusAntMsgExtInfoSet1P2, &statusAntMsgExtInfoSet1P2Ele2, "", "MediaType"); bejTreeLinkChildToParent(&statusAntMsgExtInfo, &statusAntMsgExtInfoSet1); bejTreeLinkChildToParent(&statusAnt, &statusAntMsgExtInfo); bejTreeLinkChildToParent(&root, &statusAnt); static struct RedfishPropertyParent identifiers; bejTreeInitArray(&identifiers, "Identifiers"); bejTreeLinkChildToParent(&root, &identifiers); static struct RedfishPropertyParent links; bejTreeInitSet(&links, "Links"); bejTreeLinkChildToParent(&root, &links); return &root; } /** * @brief Storage for an array of links with an annotated odata.count. * * This doesn't contain storage for the link itself. * * Eg: * "Contains": [], * "Contains@odata.count": 0, */ struct RedfishArrayOfLinksJson { struct RedfishPropertyParent array; struct RedfishPropertyParent annotatedProperty; struct RedfishPropertyLeafInt count; }; /** * @brief Storage for a single odata.id link inside a JSON "Set" object. * * Eg: FieldName: { * "@odata.id": "/redfish/v1/Chassis/Something" * } */ struct RedfishLinkJson { struct RedfishPropertyParent set; struct RedfishPropertyLeafString odataId; }; void addLinkToTree(struct RedfishPropertyParent* parent, struct RedfishPropertyParent* linkSet, const char* linkSetLabel, struct RedfishPropertyLeafString* odataId, const char* linkValue) { bejTreeInitSet(linkSet, linkSetLabel); bejTreeAddString(linkSet, odataId, "@odata.id", linkValue); bejTreeLinkChildToParent(parent, linkSet); } void redfishCreateArrayOfLinksJson( struct RedfishPropertyParent* parent, const char* arrayName, int linkCount, const char* const links[], struct RedfishArrayOfLinksJson* linksInfo, struct RedfishLinkJson* linkJsonArray) { bejTreeInitArray(&linksInfo->array, arrayName); bejTreeLinkChildToParent(parent, &linksInfo->array); bejTreeInitPropertyAnnotated(&linksInfo->annotatedProperty, arrayName); bejTreeAddInteger(&linksInfo->annotatedProperty, &linksInfo->count, "@odata.count", linkCount); bejTreeLinkChildToParent(parent, &linksInfo->annotatedProperty); for (int i = 0; i < linkCount; ++i) { addLinkToTree(&linksInfo->array, &linkJsonArray[i].set, NULL, &linkJsonArray[i].odataId, links[i]); } } struct RedfishPropertyParent* createChassisResource() { constexpr int containsLinkCount = 2; const char* contains[containsLinkCount] = {"/redfish/v1/Chassis/Disk_0", "/redfish/v1/Chassis/Disk_1"}; const char* storage[1] = {"/redfish/v1/Systems/system/Storage/SATA"}; const char* drives[1] = {"/redfish/v1/Chassis/SomeChassis/Drives/SATA_0"}; static struct RedfishPropertyParent root; static struct RedfishPropertyLeafString odataId; static struct RedfishPropertyParent links; static struct RedfishPropertyParent computerSystemsArray; static struct RedfishPropertyParent computerSystemsLinkSet; static struct RedfishPropertyLeafString computerSystemsLinkOdataId; static struct RedfishPropertyParent containedBySet; static struct RedfishPropertyLeafString containedByOdataId; static struct RedfishArrayOfLinksJson containsArray; static struct RedfishLinkJson containsLinks[containsLinkCount]; static struct RedfishArrayOfLinksJson storageArray; static struct RedfishLinkJson storageLink; static struct RedfishArrayOfLinksJson drives_array; static struct RedfishLinkJson drive_link; bejTreeInitSet(&root, "Chassis"); bejTreeAddString(&root, &odataId, "@odata.id", "/redfish/v1/Chassis/SomeChassis"); bejTreeInitSet(&links, "Links"); bejTreeLinkChildToParent(&root, &links); bejTreeInitArray(&computerSystemsArray, "ComputerSystems"); bejTreeLinkChildToParent(&links, &computerSystemsArray); addLinkToTree(&computerSystemsArray, &computerSystemsLinkSet, "", &computerSystemsLinkOdataId, "/redfish/v1/Systems/system"); addLinkToTree(&links, &containedBySet, "ContainedBy", &containedByOdataId, "/redfish/v1/Chassis/SomeOtherChassis"); redfishCreateArrayOfLinksJson(&links, "Contains", containsLinkCount, contains, &containsArray, containsLinks); redfishCreateArrayOfLinksJson(&links, "Storage", /*linkCount=*/1, storage, &storageArray, &storageLink); redfishCreateArrayOfLinksJson(&links, "Drives", /*linkCount=*/1, drives, &drives_array, &drive_link); return &root; } TEST_P(BejEncoderTest, Encode) { const BejEncoderTestParams& test_case = GetParam(); auto inputsOrErr = loadInputs(test_case.inputFiles); EXPECT_TRUE(inputsOrErr); BejDictionaries dictionaries = { .schemaDictionary = inputsOrErr->schemaDictionary, .annotationDictionary = inputsOrErr->annotationDictionary, .errorDictionary = inputsOrErr->errorDictionary, }; std::vector outputBuffer; struct BejEncoderOutputHandler output = { .handlerContext = &outputBuffer, .recvOutput = &getBejEncodedBuffer, }; std::vector pointerStack; struct BejPointerStackCallback stackCallbacks = { .stackContext = &pointerStack, .stackEmpty = stackEmpty, .stackPeek = stackPeek, .stackPop = stackPop, .stackPush = stackPush, .deleteStack = NULL, }; bejEncode(&dictionaries, BEJ_DICTIONARY_START_AT_HEAD, bejMajorSchemaClass, test_case.createResource(), &output, &stackCallbacks); BejDecoderJson decoder; EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer)), 0); std::string decoded = decoder.getOutput(); nlohmann::json jsonDecoded = nlohmann::json::parse(decoded); if (!test_case.expectedJson.empty()) { inputsOrErr->expectedJson = nlohmann::json::parse(test_case.expectedJson); } EXPECT_TRUE(jsonDecoded.dump() == inputsOrErr->expectedJson.dump()); } TEST_P(BejEncoderTest, EncodeWrapper) { const BejEncoderTestParams& test_case = GetParam(); auto inputsOrErr = loadInputs(test_case.inputFiles); EXPECT_TRUE(inputsOrErr); BejDictionaries dictionaries = { .schemaDictionary = inputsOrErr->schemaDictionary, .annotationDictionary = inputsOrErr->annotationDictionary, .errorDictionary = inputsOrErr->errorDictionary, }; libbej::BejEncoderJson encoder; encoder.encode(&dictionaries, bejMajorSchemaClass, test_case.createResource()); std::vector outputBuffer = encoder.getOutput(); BejDecoderJson decoder; EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer)), 0); std::string decoded = decoder.getOutput(); nlohmann::json jsonDecoded = nlohmann::json::parse(decoded); if (!test_case.expectedJson.empty()) { inputsOrErr->expectedJson = nlohmann::json::parse(test_case.expectedJson); } EXPECT_TRUE(jsonDecoded.dump() == inputsOrErr->expectedJson.dump()); // Try using the same encoder object again to ensure that the same object // does the encoding correctly encoder.encode(&dictionaries, bejMajorSchemaClass, test_case.createResource()); outputBuffer = encoder.getOutput(); EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer)), 0); decoded = decoder.getOutput(); jsonDecoded = nlohmann::json::parse(decoded); if (!test_case.expectedJson.empty()) { inputsOrErr->expectedJson = nlohmann::json::parse(test_case.expectedJson); } EXPECT_TRUE(jsonDecoded.dump() == inputsOrErr->expectedJson.dump()); } /** * TODO: Add more test cases. */ INSTANTIATE_TEST_SUITE_P( , BejEncoderTest, testing::ValuesIn({ {"DriveOEM", driveOemTestFiles, driveOemJson, &createDriveOem}, {"DummySimple", dummySimpleTestFiles, "", &createDummyResource}, {"Chassis", chassisTestFiles, "", &createChassisResource}, }), [](const testing::TestParamInfo& info) { return info.param.testName; }); } // namespace libbej