blob: c04fa020ab830285eba881079507d327d6b8c9cc [file] [edit]
#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_deferred_binding_format.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)();
};
void PrintTo(const BejEncoderTestParams& params, std::ostream* os)
{
*os << params.testName;
}
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 RedfishPropertyParent annotationSet;
bejTreeInitSet(&annotationSet, "@Redfish.Settings");
static struct RedfishPropertyLeafString annotationType;
bejTreeAddString(&annotationSet, &annotationType, "@odata.type",
"#Settings.v1_0_0.Settings");
bejTreeLinkChildToParent(&root, &annotationSet);
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, nullptr,
&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,
.schemaDictionarySize = inputsOrErr->schemaDictionarySize,
.annotationDictionary = inputsOrErr->annotationDictionary,
.annotationDictionarySize = inputsOrErr->annotationDictionarySize,
.errorDictionary = inputsOrErr->errorDictionary,
.errorDictionarySize = inputsOrErr->errorDictionarySize,
};
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 = nullptr,
};
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,
.schemaDictionarySize = inputsOrErr->schemaDictionarySize,
.annotationDictionary = inputsOrErr->annotationDictionary,
.annotationDictionarySize = inputsOrErr->annotationDictionarySize,
.errorDictionary = inputsOrErr->errorDictionary,
.errorDictionarySize = inputsOrErr->errorDictionarySize,
};
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());
}
TEST(BejEncoderDeferredBindingTest, DeferredBindingEncodeDecode)
{
auto inputsOrErr = loadInputs(driveOemTestFiles);
ASSERT_TRUE(inputsOrErr);
BejDictionaries dictionaries = {
.schemaDictionary = inputsOrErr->schemaDictionary,
.schemaDictionarySize = inputsOrErr->schemaDictionarySize,
.annotationDictionary = inputsOrErr->annotationDictionary,
.annotationDictionarySize = inputsOrErr->annotationDictionarySize,
.errorDictionary = inputsOrErr->errorDictionary,
.errorDictionarySize = inputsOrErr->errorDictionarySize,
};
// We will use DriveOEM which has `@odata.id`: `/redfish/v1/drives/1`.
// Let's create a reverse mapping config where resourceId = 30 points to
// this URI.
BejDeferredBindingMap bindings;
bindings[bejBinding::resourceLink(30)] = "/redfish/v1/drives/1";
bindings[bejBinding::instanceId(30)] = "Drive1";
// Target action mapping simulation
bindings[bejBinding::action(30, 1)] =
"/redfish/v1/drives/1/Actions/Drive.Reset";
libbej::BejEncoderJson encoder;
struct RedfishPropertyParent* root = createDriveOem();
// Encode with binding map
encoder.encode(&dictionaries, bejMajorSchemaClass, root, bindings);
std::vector<uint8_t> outputBuffer = encoder.getOutput();
// Decode with binding map
libbej::BejDecoderJson decoder;
EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer), bindings),
0);
std::string decoded = decoder.getOutput();
nlohmann::json jsonDecoded = nlohmann::json::parse(decoded);
// After decode, the json should perfectly match the input strings
// because `%L30`, `%I30` should be resolved back to their original strings.
std::string odataId = jsonDecoded["@odata.id"].get<std::string>();
EXPECT_EQ(odataId, "/redfish/v1/drives/1");
std::string id = jsonDecoded["Id"].get<std::string>();
EXPECT_EQ(id, "Drive1");
// Action URI
std::string actionTarget =
jsonDecoded["Actions"]["#Drive.Reset"]["target"].get<std::string>();
EXPECT_EQ(actionTarget, "/redfish/v1/drives/1/Actions/Drive.Reset");
}
TEST(BejEncoderDeferredBindingTest, EncodeDoesNotMutateInputTree)
{
auto inputsOrErr = loadInputs(driveOemTestFiles);
ASSERT_TRUE(inputsOrErr);
BejDictionaries dictionaries = {
.schemaDictionary = inputsOrErr->schemaDictionary,
.schemaDictionarySize = inputsOrErr->schemaDictionarySize,
.annotationDictionary = inputsOrErr->annotationDictionary,
.annotationDictionarySize = inputsOrErr->annotationDictionarySize,
.errorDictionary = inputsOrErr->errorDictionary,
.errorDictionarySize = inputsOrErr->errorDictionarySize,
};
BejDeferredBindingMap bindings;
bindings[bejBinding::resourceLink(30)] = "/redfish/v1/drives/1";
bindings[bejBinding::instanceId(30)] = "Drive1";
bindings[bejBinding::action(30, 1)] =
"/redfish/v1/drives/1/Actions/Drive.Reset";
// encode() temporarily rewrites matching leaf values to placeholders, then
// must restore them (value pointers and deferred-binding flags) before
// returning.
struct RedfishPropertyParent* root = createDriveOem();
libbej::BejEncoderJson boundEncoder;
boundEncoder.encode(&dictionaries, bejMajorSchemaClass, root, bindings);
// Re-encoding the same tree WITHOUT bindings must reproduce a clean encode
// of an untouched tree; any residual placeholder or flag would differ.
libbej::BejEncoderJson reuseEncoder;
reuseEncoder.encode(&dictionaries, bejMajorSchemaClass, root);
std::vector<uint8_t> afterRestore = reuseEncoder.getOutput();
libbej::BejEncoderJson referenceEncoder;
referenceEncoder.encode(&dictionaries, bejMajorSchemaClass,
createDriveOem());
std::vector<uint8_t> reference = referenceEncoder.getOutput();
EXPECT_EQ(afterRestore, reference);
}
TEST(BejEncoderDeferredBindingTest, UnresolvedPlaceholdersBecomeInvalidForm)
{
auto inputsOrErr = loadInputs(driveOemTestFiles);
ASSERT_TRUE(inputsOrErr);
BejDictionaries dictionaries = {
.schemaDictionary = inputsOrErr->schemaDictionary,
.schemaDictionarySize = inputsOrErr->schemaDictionarySize,
.annotationDictionary = inputsOrErr->annotationDictionary,
.annotationDictionarySize = inputsOrErr->annotationDictionarySize,
.errorDictionary = inputsOrErr->errorDictionary,
.errorDictionarySize = inputsOrErr->errorDictionarySize,
};
BejDeferredBindingMap bindings;
bindings[bejBinding::resourceLink(30)] = "/redfish/v1/drives/1";
bindings[bejBinding::instanceId(30)] = "Drive1";
bindings[bejBinding::action(30, 1)] =
"/redfish/v1/drives/1/Actions/Drive.Reset";
libbej::BejEncoderJson encoder;
encoder.encode(&dictionaries, bejMajorSchemaClass, createDriveOem(),
bindings);
std::vector<uint8_t> outputBuffer = encoder.getOutput();
// Decode with a non-empty map that does NOT contain resource 30: resolution
// is enabled, so each unresolved placeholder must become its DSP0218
// Table 42 invalid form rather than being left raw.
BejDeferredBindingMap unrelated;
unrelated[bejBinding::resourceLink(99)] = "/redfish/v1/unrelated";
libbej::BejDecoderJson decoder;
EXPECT_THAT(
decoder.decode(dictionaries, std::span(outputBuffer), unrelated), 0);
nlohmann::json jsonDecoded = nlohmann::json::parse(decoder.getOutput());
EXPECT_EQ(jsonDecoded["@odata.id"].get<std::string>(), "/invalid.PDR30");
EXPECT_EQ(jsonDecoded["Id"].get<std::string>(), "invalid");
EXPECT_EQ(
jsonDecoded["Actions"]["#Drive.Reset"]["target"].get<std::string>(),
"/invalid.30.1");
}
TEST(BejEncoderDeferredBindingTest, DisabledModeLeavesPlaceholdersRaw)
{
auto inputsOrErr = loadInputs(driveOemTestFiles);
ASSERT_TRUE(inputsOrErr);
BejDictionaries dictionaries = {
.schemaDictionary = inputsOrErr->schemaDictionary,
.schemaDictionarySize = inputsOrErr->schemaDictionarySize,
.annotationDictionary = inputsOrErr->annotationDictionary,
.annotationDictionarySize = inputsOrErr->annotationDictionarySize,
.errorDictionary = inputsOrErr->errorDictionary,
.errorDictionarySize = inputsOrErr->errorDictionarySize,
};
BejDeferredBindingMap bindings;
bindings[bejBinding::resourceLink(30)] = "/redfish/v1/drives/1";
bindings[bejBinding::instanceId(30)] = "Drive1";
bindings[bejBinding::action(30, 1)] =
"/redfish/v1/drives/1/Actions/Drive.Reset";
libbej::BejEncoderJson encoder;
encoder.encode(&dictionaries, bejMajorSchemaClass, createDriveOem(),
bindings);
std::vector<uint8_t> outputBuffer = encoder.getOutput();
// Decode without a map (the default): resolution is disabled, so every
// placeholder is preserved verbatim instead of being substituted.
libbej::BejDecoderJson decoder;
EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer)), 0);
nlohmann::json jsonDecoded = nlohmann::json::parse(decoder.getOutput());
EXPECT_EQ(jsonDecoded["@odata.id"].get<std::string>(), "%L30");
EXPECT_EQ(jsonDecoded["Id"].get<std::string>(), "%I30");
EXPECT_EQ(
jsonDecoded["Actions"]["#Drive.Reset"]["target"].get<std::string>(),
"%T30.1");
}
TEST(BejEncoderDeferredBindingTest, IdlessMarkerRoundTrips)
{
auto inputsOrErr = loadInputs(driveOemTestFiles);
ASSERT_TRUE(inputsOrErr);
BejDictionaries dictionaries = {
.schemaDictionary = inputsOrErr->schemaDictionary,
.schemaDictionarySize = inputsOrErr->schemaDictionarySize,
.annotationDictionary = inputsOrErr->annotationDictionary,
.annotationDictionarySize = inputsOrErr->annotationDictionarySize,
.errorDictionary = inputsOrErr->errorDictionary,
.errorDictionarySize = inputsOrErr->errorDictionarySize,
};
// Map only the id-less %C token to the DriveOEM @odata.id value, so the
// encoder rewrites that leaf to "%C". Exercises a marker carrying no
// resource id end to end.
BejDeferredBindingMap bindings;
bindings[bejBinding::chassis()] = "/redfish/v1/drives/1";
libbej::BejEncoderJson encoder;
encoder.encode(&dictionaries, bejMajorSchemaClass, createDriveOem(),
bindings);
std::vector<uint8_t> outputBuffer = encoder.getOutput();
// Enabled: %C resolves back to the bound value.
libbej::BejDecoderJson decoder;
EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer), bindings),
0);
nlohmann::json bound = nlohmann::json::parse(decoder.getOutput());
EXPECT_EQ(bound["@odata.id"].get<std::string>(), "/redfish/v1/drives/1");
// Disabled: the id-less placeholder is preserved raw as "%C".
libbej::BejDecoderJson rawDecoder;
EXPECT_THAT(rawDecoder.decode(dictionaries, std::span(outputBuffer)), 0);
nlohmann::json raw = nlohmann::json::parse(rawDecoder.getOutput());
EXPECT_EQ(raw["@odata.id"].get<std::string>(), "%C");
}
TEST(BejEncoderDeferredBindingTest, SystemMarkerRoundTrips)
{
auto inputsOrErr = loadInputs(driveOemTestFiles);
ASSERT_TRUE(inputsOrErr);
BejDictionaries dictionaries = {
.schemaDictionary = inputsOrErr->schemaDictionary,
.schemaDictionarySize = inputsOrErr->schemaDictionarySize,
.annotationDictionary = inputsOrErr->annotationDictionary,
.annotationDictionarySize = inputsOrErr->annotationDictionarySize,
.errorDictionary = inputsOrErr->errorDictionary,
.errorDictionarySize = inputsOrErr->errorDictionarySize,
};
// Map only the id-less %S token to the DriveOEM @odata.id value, so the
// encoder rewrites that leaf to "%S".
BejDeferredBindingMap bindings;
bindings[bejBinding::system()] = "/redfish/v1/drives/1";
libbej::BejEncoderJson encoder;
encoder.encode(&dictionaries, bejMajorSchemaClass, createDriveOem(),
bindings);
std::vector<uint8_t> outputBuffer = encoder.getOutput();
// Enabled: %S resolves back to the bound value.
libbej::BejDecoderJson decoder;
EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer), bindings),
0);
nlohmann::json bound = nlohmann::json::parse(decoder.getOutput());
EXPECT_EQ(bound["@odata.id"].get<std::string>(), "/redfish/v1/drives/1");
// Disabled: the id-less placeholder is preserved raw as "%S".
libbej::BejDecoderJson rawDecoder;
EXPECT_THAT(rawDecoder.decode(dictionaries, std::span(outputBuffer)), 0);
nlohmann::json raw = nlohmann::json::parse(rawDecoder.getOutput());
EXPECT_EQ(raw["@odata.id"].get<std::string>(), "%S");
}
/**
* 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