blob: d258b4b0cee6bbc7660fd20f2478bca6e597c3f8 [file] [log] [blame]
#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 <vector>
#include <gmock/gmock-matchers.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace libbej
{
struct BejEncoderTestParams
{
const std::string testName;
const BejTestInputFiles inputFiles;
std::string expectedJson;
struct RedfishPropertyParent* (*createResource)();
};
using BejEncoderTest = testing::TestWithParam<BejEncoderTestParams>;
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<uint8_t> outputBuffer;
struct BejEncoderOutputHandler output = {
.handlerContext = &outputBuffer,
.recvOutput = &getBejEncodedBuffer,
};
std::vector<void*> 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<uint8_t> 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<BejEncoderTestParams>({
{"DriveOEM", driveOemTestFiles, driveOemJson, &createDriveOem},
{"DummySimple", dummySimpleTestFiles, "", &createDummyResource},
{"Chassis", chassisTestFiles, "", &createChassisResource},
}),
[](const testing::TestParamInfo<BejEncoderTest::ParamType>& info) {
return info.param.testName;
});
} // namespace libbej