| #include "absl/strings/str_cat.h" |
| #include "async_resp.hpp" |
| #include "redfish_aggregator.hpp" |
| |
| #include <nlohmann/json.hpp> |
| |
| #include <gtest/gtest.h> // IWYU pragma: keep |
| |
| namespace redfish |
| { |
| namespace |
| { |
| |
| TEST(ParameterRemove, RemoveOnlySkipParams) |
| { |
| auto retOnly = boost::urls::parse_relative_ref("/redfish/v1/Chassis?only"); |
| ASSERT_TRUE(retOnly); |
| |
| std::error_code ec; |
| crow::Request req{ |
| {boost::beast::http::verb::get, retOnly->buffer(), 11}, ec}; |
| parameterRemove(req); |
| EXPECT_EQ(req.url().buffer(), "/redfish/v1/Chassis"); |
| |
| auto retSkip = |
| boost::urls::parse_relative_ref("/redfish/v1/Chassis?$skip=10"); |
| ASSERT_TRUE(retSkip); |
| |
| req.target(retSkip->buffer()); |
| parameterRemove(req); |
| EXPECT_EQ(req.url().buffer(), "/redfish/v1/Chassis"); |
| } |
| |
| TEST(ParameterRemove, IgnoreNonOnlySkipParams) |
| { |
| auto retExpand = |
| boost::urls::parse_relative_ref( |
| "/redfish/v1/Chassis?$expand=.($levels=1)"); |
| ASSERT_TRUE(retExpand); |
| |
| std::error_code ec; |
| crow::Request req{ |
| {boost::beast::http::verb::get, retExpand->buffer(), 11}, ec}; |
| parameterRemove(req); |
| EXPECT_EQ(req.url().buffer(), "/redfish/v1/Chassis?$expand=.($levels=1)"); |
| |
| auto retTop = |
| boost::urls::parse_relative_ref("/redfish/v1/Chassis?$top=4"); |
| ASSERT_TRUE(retTop); |
| |
| req.target(retTop->buffer()); |
| parameterRemove(req); |
| EXPECT_EQ(req.url().buffer(), "/redfish/v1/Chassis?$top=4"); |
| } |
| |
| TEST(ParameterRemove, MultipleParamsRemoveSelectedParams) |
| { |
| auto retSkipTop = |
| boost::urls::parse_relative_ref("/redfish/v1/Chassis?$skip=1&$top=1"); |
| ASSERT_TRUE(retSkipTop); |
| |
| std::error_code ec; |
| crow::Request req{ |
| {boost::beast::http::verb::get, retSkipTop->buffer(), 11}, ec}; |
| parameterRemove(req); |
| EXPECT_EQ(req.url().buffer(), "/redfish/v1/Chassis"); |
| |
| auto retOnlyTop = |
| boost::urls::parse_relative_ref("/redfish/v1/Chassis?only&$top=1"); |
| ASSERT_TRUE(retOnlyTop); |
| |
| req.target(retOnlyTop->buffer()); |
| parameterRemove(req); |
| EXPECT_EQ(req.url().buffer(), "/redfish/v1/Chassis"); |
| |
| auto retSkipTopFilter = |
| boost::urls::parse_relative_ref( |
| "/redfish/v1/Chassis?$skip=1&$top=1&$filter=property%20ge%20value"); |
| ASSERT_TRUE(retSkipTopFilter); |
| |
| req.target(retSkipTopFilter->buffer()); |
| parameterRemove(req); |
| EXPECT_EQ(req.url().buffer(), |
| "/redfish/v1/Chassis?$filter=property%20ge%20value"); |
| } |
| |
| TEST(IsPropertyUri, SupportedPropertyReturnsTrue) |
| { |
| EXPECT_TRUE(isPropertyUri("@Redfish.ActionInfo")); |
| EXPECT_TRUE(isPropertyUri("@odata.id")); |
| EXPECT_TRUE(isPropertyUri("Image")); |
| EXPECT_TRUE(isPropertyUri("MetricProperty")); |
| EXPECT_TRUE(isPropertyUri("TaskMonitor")); |
| EXPECT_TRUE(isPropertyUri("target")); |
| } |
| |
| TEST(IsPropertyUri, CaseInsensitiveURIReturnsTrue) |
| { |
| EXPECT_TRUE(isPropertyUri("AdditionalDataURI")); |
| EXPECT_TRUE(isPropertyUri("DataSourceUri")); |
| EXPECT_TRUE(isPropertyUri("uri")); |
| EXPECT_TRUE(isPropertyUri("URI")); |
| } |
| |
| TEST(IsPropertyUri, SpeificallyIgnoredPropertyReturnsFalse) |
| { |
| EXPECT_FALSE(isPropertyUri("@odata.context")); |
| EXPECT_FALSE(isPropertyUri("Destination")); |
| EXPECT_FALSE(isPropertyUri("HostName")); |
| EXPECT_FALSE(isPropertyUri("OriginOfCondition")); |
| } |
| |
| TEST(IsPropertyUri, UnsupportedPropertyReturnsFalse) |
| { |
| EXPECT_FALSE(isPropertyUri("Name")); |
| EXPECT_FALSE(isPropertyUri("Health")); |
| EXPECT_FALSE(isPropertyUri("Id")); |
| } |
| |
| TEST(addPrefixToItem, ValidURIs) |
| { |
| nlohmann::json jsonRequest; |
| constexpr std::array validRoots{"Cables", |
| "Chassis", |
| "Fabrics", |
| "PowerEquipment/FloorPDUs", |
| "Systems", |
| "TaskService/Tasks", |
| "TelemetryService/LogService/Entries", |
| "UpdateService/SoftwareInventory"}; |
| |
| // We're only testing prefix fixing so it's alright that some of the |
| // resulting URIs will not actually be possible as defined by the schema |
| constexpr std::array validIDs{"1", |
| "1/", |
| "Test", |
| "Test/", |
| "Extra_Test", |
| "Extra_Test/", |
| "Extra_Test/Sensors", |
| "Extra_Test/Sensors/", |
| "Extra_Test/Sensors/power_sensor", |
| "Extra_Test/Sensors/power_sensor/"}; |
| |
| // Construct URIs which should have prefix fixing applied |
| for (const auto& root : validRoots) |
| { |
| for (const auto& id : validIDs) |
| { |
| std::string initial = absl::StrCat("/redfish/v1/", root, "/"); |
| std::string correct = absl::StrCat(initial, "asdfjkl_", id); |
| initial += id; |
| jsonRequest["@odata.id"] = initial; |
| addPrefixToItem(jsonRequest["@odata.id"], "asdfjkl"); |
| EXPECT_EQ(jsonRequest["@odata.id"], correct); |
| } |
| } |
| } |
| |
| TEST(addPrefixToItem, ValidURIsWithFragmentsPreserved) |
| { |
| nlohmann::json jsonItem; |
| |
| nlohmann::json jsonRequest; |
| constexpr std::array validRoots{"Cables", |
| "Chassis", |
| "Fabrics", |
| "PowerEquipment/FloorPDUs", |
| "Systems", |
| "TaskService/Tasks", |
| "TelemetryService/LogService/Entries", |
| "UpdateService/SoftwareInventory"}; |
| |
| // We're only testing prefix fixing so it's alright that some of the |
| // resulting URIs will not actually be possible as defined by the schema |
| constexpr std::array validIDs{"1#/Fragment/0", |
| "Test#/Fragment/0", |
| "Extra_Test#/Fragment/0", |
| "Extra_Test/Sensors#/Fragment/0", |
| "Extra_Test/Sensors/power_sensor#/Fragment/0"}; |
| |
| // Construct URIs which should have prefix fixing applied |
| for (const auto& root : validRoots) |
| { |
| for (const auto& id : validIDs) |
| { |
| std::string initial = absl::StrCat("/redfish/v1/", root, "/"); |
| std::string correct = absl::StrCat(initial, "asdfjkl_", id); |
| initial += id; |
| jsonRequest["@odata.id"] = initial; |
| addPrefixToItem(jsonRequest["@odata.id"], "asdfjkl"); |
| EXPECT_EQ(jsonRequest["@odata.id"], correct); |
| } |
| } |
| |
| } |
| |
| TEST(addPrefixToItem, UnsupportedURIs) |
| { |
| nlohmann::json jsonRequest; |
| constexpr std::array invalidRoots{ |
| "FakeCollection", "JsonSchemas", |
| "PowerEquipment", "TaskService", |
| "TelemetryService/Entries", "UpdateService"}; |
| |
| constexpr std::array validIDs{"1", |
| "1/", |
| "Test", |
| "Test/", |
| "Extra_Test", |
| "Extra_Test/", |
| "Extra_Test/Sensors", |
| "Extra_Test/Sensors/", |
| "Extra_Test/Sensors/power_sensor", |
| "Extra_Test/Sensors/power_sensor/"}; |
| |
| // Construct URIs which should NOT have prefix fixing applied |
| for (const auto& root : invalidRoots) |
| { |
| for (const auto& id : validIDs) |
| { |
| std::string initial = absl::StrCat("/redfish/v1/", root, "/"); |
| std::string correct = absl::StrCat(initial, "asdfjkl_", id); |
| initial += id; |
| jsonRequest["@odata.id"] = initial; |
| addPrefixToItem(jsonRequest["@odata.id"], "asdfjkl"); |
| EXPECT_EQ(jsonRequest["@odata.id"], initial); |
| } |
| } |
| } |
| |
| TEST(addPrefixToItem, TopLevelCollections) |
| { |
| nlohmann::json jsonRequest; |
| constexpr std::array validRoots{"Cables", |
| "Chassis/", |
| "Fabrics", |
| "JsonSchemas", |
| "PowerEquipment/FloorPDUs", |
| "Systems", |
| "TaskService/Tasks", |
| "TelemetryService/LogService/Entries", |
| "TelemetryService/LogService/Entries/", |
| "UpdateService/SoftwareInventory/"}; |
| |
| // Construct URIs for top level collections. Prefixes should NOT be |
| // applied to any of the URIs |
| for (const auto& root : validRoots) |
| { |
| std::string initial("/redfish/v1/" + std::string(root)); |
| jsonRequest["@odata.id"] = initial; |
| addPrefixToItem(jsonRequest["@odata.id"], "perfix"); |
| EXPECT_EQ(jsonRequest["@odata.id"], initial); |
| } |
| } |
| |
| TEST(addPrefixes, ParseJsonObject) |
| { |
| nlohmann::json parameter; |
| parameter["Name"] = "/redfish/v1/Chassis/fakeName"; |
| parameter["@odata.id"] = "/redfish/v1/Chassis/fakeChassis"; |
| |
| addPrefixes(parameter, "abcd"); |
| EXPECT_EQ(parameter["Name"], "/redfish/v1/Chassis/fakeName"); |
| EXPECT_EQ(parameter["@odata.id"], "/redfish/v1/Chassis/abcd_fakeChassis"); |
| } |
| |
| TEST(addPrefixes, ParseJsonArray) |
| { |
| nlohmann::json array = nlohmann::json::parse(R"( |
| { |
| "Conditions": [ |
| { |
| "Message": "This is a test", |
| "@odata.id": "/redfish/v1/Chassis/TestChassis" |
| }, |
| { |
| "Message": "This is also a test", |
| "@odata.id": "/redfish/v1/Chassis/TestChassis2" |
| } |
| ] |
| } |
| )", |
| nullptr, false); |
| |
| addPrefixes(array, "5B42"); |
| EXPECT_EQ(array["Conditions"][0]["@odata.id"], |
| "/redfish/v1/Chassis/5B42_TestChassis"); |
| EXPECT_EQ(array["Conditions"][1]["@odata.id"], |
| "/redfish/v1/Chassis/5B42_TestChassis2"); |
| } |
| |
| TEST(addPrefixes, ParseJsonObjectNestedArray) |
| { |
| nlohmann::json objWithArray = nlohmann::json::parse(R"( |
| { |
| "Status": { |
| "Conditions": [ |
| { |
| "Message": "This is a test", |
| "MessageId": "Test", |
| "OriginOfCondition": { |
| "@odata.id": "/redfish/v1/Chassis/TestChassis" |
| }, |
| "Severity": "Critical" |
| } |
| ], |
| "Health": "Critical", |
| "State": "Enabled" |
| } |
| } |
| )", |
| nullptr, false); |
| |
| addPrefixes(objWithArray, "5B42"); |
| nlohmann::json& array = objWithArray["Status"]["Conditions"]; |
| EXPECT_EQ(array[0]["OriginOfCondition"]["@odata.id"], |
| "/redfish/v1/Chassis/5B42_TestChassis"); |
| } |
| |
| TEST(addPrefixes, FixHttpHeadersInResponseBody) |
| { |
| nlohmann::json taskResp = nlohmann::json::parse(R"( |
| { |
| "@odata.id": "/redfish/v1/TaskService/Tasks/0", |
| "Name": "Task 0", |
| "Payload": { |
| "HttpHeaders": [ |
| "User-Agent: curl/7.87.0", |
| "Accept: */*", |
| "Host: 127.127.12.7", |
| "Content-Length: 33", |
| "Location: /redfish/v1/Managers/bmc/LogServices/Dump/Entries/0" |
| ] |
| }, |
| "PercentComplete": 100, |
| "TaskMonitor": "/redfish/v1/TaskService/Tasks/0/Monitor", |
| "TaskState": "Completed", |
| "TaskStatus": "OK" |
| } |
| )", |
| nullptr, false); |
| |
| addPrefixes(taskResp, "5B247A"); |
| EXPECT_EQ(taskResp["@odata.id"], "/redfish/v1/TaskService/Tasks/5B247A_0"); |
| EXPECT_EQ(taskResp["TaskMonitor"], |
| "/redfish/v1/TaskService/Tasks/5B247A_0/Monitor"); |
| nlohmann::json& httpHeaders = taskResp["Payload"]["HttpHeaders"]; |
| EXPECT_EQ( |
| httpHeaders[4], |
| "Location: /redfish/v1/Managers/5B247A_bmc/LogServices/Dump/Entries/0"); |
| } |
| |
| // Attempts to perform prefix fixing on a response with response code "result". |
| // Fixing should always occur |
| void assertProcessResponse(unsigned result) |
| { |
| nlohmann::json jsonResp; |
| jsonResp["@odata.id"] = "/redfish/v1/Chassis/TestChassis"; |
| jsonResp["Name"] = "Test"; |
| |
| crow::Response resp; |
| resp.body() = |
| jsonResp.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); |
| resp.addHeader("Content-Type", "application/json"); |
| resp.addHeader("Allow", "GET"); |
| resp.addHeader("Location", "/redfish/v1/Chassis/TestChassis"); |
| resp.addHeader("Link", "</redfish/v1/Test.json>; rel=describedby"); |
| resp.addHeader("Retry-After", "120"); |
| resp.result(result); |
| |
| auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); |
| RedfishAggregator::processResponse("prefix", asyncResp, resp); |
| |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), |
| "application/json"); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Allow"), "GET"); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), |
| "/redfish/v1/Chassis/prefix_TestChassis"); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Link"), ""); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Retry-After"), "120"); |
| |
| EXPECT_EQ(asyncResp->res.jsonValue["Name"], "Test"); |
| EXPECT_EQ(asyncResp->res.jsonValue["@odata.id"], |
| "/redfish/v1/Chassis/prefix_TestChassis"); |
| EXPECT_EQ(asyncResp->res.resultInt(), result); |
| } |
| |
| TEST(processResponse, validResponseCodes) |
| { |
| assertProcessResponse(100); |
| assertProcessResponse(200); |
| assertProcessResponse(204); |
| assertProcessResponse(300); |
| assertProcessResponse(404); |
| assertProcessResponse(405); |
| assertProcessResponse(500); |
| assertProcessResponse(507); |
| } |
| |
| TEST(processResponse, preserveHeaders) |
| { |
| auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); |
| asyncResp->res.addHeader("OData-Version", "4.0"); |
| asyncResp->res.result(boost::beast::http::status::ok); |
| |
| crow::Response resp; |
| resp.addHeader("OData-Version", "3.0"); |
| resp.addHeader(boost::beast::http::field::location, |
| "/redfish/v1/Chassis/Test"); |
| resp.result(boost::beast::http::status::too_many_requests); // 429 |
| |
| RedfishAggregator::processResponse("prefix", asyncResp, resp); |
| EXPECT_EQ(asyncResp->res.resultInt(), 429); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), ""); |
| |
| asyncResp->res.result(boost::beast::http::status::ok); |
| resp.result(boost::beast::http::status::bad_gateway); // 502 |
| |
| RedfishAggregator::processResponse("prefix", asyncResp, resp); |
| EXPECT_EQ(asyncResp->res.resultInt(), 502); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), ""); |
| } |
| |
| // Helper function to correctly populate a ComputerSystem collection response |
| void populateCollectionResponse(crow::Response& resp) |
| { |
| nlohmann::json jsonResp = nlohmann::json::parse(R"( |
| { |
| "@odata.id": "/redfish/v1/Systems", |
| "@odata.type": "#ComputerSystemCollection.ComputerSystemCollection", |
| "Members": [ |
| { |
| "@odata.id": "/redfish/v1/Systems/system" |
| } |
| ], |
| "Members@odata.count": 1, |
| "Name": "Computer System Collection" |
| } |
| )", |
| nullptr, false); |
| |
| resp.clear(); |
| // resp.body() = |
| // jsonResp.dump(2, ' ', true, |
| // nlohmann::json::error_handler_t::replace); |
| resp.jsonValue = std::move(jsonResp); |
| resp.addHeader("OData-Version", "4.0"); |
| resp.addHeader("Content-Type", "application/json"); |
| resp.result(boost::beast::http::status::ok); |
| } |
| |
| // Helper function to correctly populate a ComputerSystem collection response |
| // returned by $select=Name |
| void populateCollectionResponseSelectName(crow::Response& resp) |
| { |
| nlohmann::json jsonResp = nlohmann::json::parse(R"( |
| { |
| "@odata.id": "/redfish/v1/Systems", |
| "@odata.type": "#ComputerSystemCollection.ComputerSystemCollection", |
| "Name": "Computer System Collection" |
| } |
| )", |
| nullptr, false); |
| |
| resp.clear(); |
| resp.jsonValue = std::move(jsonResp); |
| resp.addHeader("OData-Version", "4.0"); |
| resp.addHeader("Content-Type", "application/json"); |
| resp.result(boost::beast::http::status::ok); |
| } |
| |
| void populateCollectionNotFound(crow::Response& resp) |
| { |
| resp.clear(); |
| resp.addHeader("OData-Version", "4.0"); |
| resp.result(boost::beast::http::status::not_found); |
| } |
| |
| // Used with the above functions to convert the response to appear like it's |
| // from a satellite which will not have a json component |
| void convertToSat(crow::Response& resp) |
| { |
| resp.body() = resp.jsonValue.dump(2, ' ', true, |
| nlohmann::json::error_handler_t::replace); |
| resp.jsonValue.clear(); |
| } |
| |
| TEST(processCollectionResponse, localOnly) |
| { |
| auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); |
| crow::Response resp; |
| populateCollectionResponse(asyncResp->res); |
| populateCollectionNotFound(resp); |
| |
| RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); |
| EXPECT_EQ(asyncResp->res.resultInt(), 200); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), |
| "application/json"); |
| EXPECT_EQ(asyncResp->res.jsonValue["Members@odata.count"], 1); |
| for (auto& member : asyncResp->res.jsonValue["Members"]) |
| { |
| // There should only be one member |
| EXPECT_EQ(member["@odata.id"], "/redfish/v1/Systems/system"); |
| } |
| } |
| |
| TEST(processCollectionResponse, satelliteOnly) |
| { |
| auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); |
| crow::Response resp; |
| populateCollectionNotFound(asyncResp->res); |
| populateCollectionResponse(resp); |
| convertToSat(resp); |
| |
| RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); |
| EXPECT_EQ(asyncResp->res.resultInt(), 200); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), |
| "application/json"); |
| EXPECT_EQ(asyncResp->res.jsonValue["Members@odata.count"], 1); |
| for (auto& member : asyncResp->res.jsonValue["Members"]) |
| { |
| // There should only be one member |
| EXPECT_EQ(member["@odata.id"], "/redfish/v1/Systems/prefix_system"); |
| } |
| } |
| |
| TEST(processCollectionResponse, satelliteOnlySelectName) |
| { |
| auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); |
| crow::Response resp; |
| populateCollectionNotFound(asyncResp->res); |
| populateCollectionResponseSelectName(resp); |
| convertToSat(resp); |
| |
| // Local resp was a 404 so it should be completely overwritten by satellite |
| // response even though response does not have a Members array |
| RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); |
| EXPECT_EQ(asyncResp->res.resultInt(), 200); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), |
| "application/json"); |
| EXPECT_EQ(asyncResp->res.jsonValue["Name"], "Computer System Collection"); |
| } |
| |
| TEST(processCollectionResponse, bothExist) |
| { |
| auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); |
| crow::Response resp; |
| populateCollectionResponse(asyncResp->res); |
| populateCollectionResponse(resp); |
| convertToSat(resp); |
| |
| RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); |
| EXPECT_EQ(asyncResp->res.resultInt(), 200); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), |
| "application/json"); |
| EXPECT_EQ(asyncResp->res.jsonValue["Members@odata.count"], 2); |
| |
| bool foundLocal = false; |
| bool foundSat = false; |
| for (const auto& member : asyncResp->res.jsonValue["Members"]) |
| { |
| if (member["@odata.id"] == "/redfish/v1/Systems/system") |
| { |
| foundLocal = true; |
| } |
| else if (member["@odata.id"] == "/redfish/v1/Systems/prefix_system") |
| { |
| foundSat = true; |
| } |
| } |
| EXPECT_TRUE(foundLocal); |
| EXPECT_TRUE(foundSat); |
| } |
| |
| TEST(processCollectionResponse, satelliteWrongContentHeader) |
| { |
| auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); |
| crow::Response resp; |
| populateCollectionResponse(asyncResp->res); |
| populateCollectionResponse(resp); |
| convertToSat(resp); |
| |
| // Ignore the satellite even though otherwise valid |
| resp.addHeader("Content-Type", ""); |
| |
| RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); |
| EXPECT_EQ(asyncResp->res.resultInt(), 200); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), |
| "application/json"); |
| EXPECT_EQ(asyncResp->res.jsonValue["Members@odata.count"], 1); |
| for (auto& member : asyncResp->res.jsonValue["Members"]) |
| { |
| EXPECT_EQ(member["@odata.id"], "/redfish/v1/Systems/system"); |
| } |
| } |
| |
| TEST(processCollectionResponse, neitherExist) |
| { |
| auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); |
| crow::Response resp; |
| populateCollectionNotFound(asyncResp->res); |
| populateCollectionNotFound(resp); |
| convertToSat(resp); |
| |
| RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); |
| EXPECT_EQ(asyncResp->res.resultInt(), 404); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), ""); |
| } |
| |
| TEST(processCollectionResponse, preserveHeaders) |
| { |
| auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); |
| crow::Response resp; |
| populateCollectionNotFound(asyncResp->res); |
| populateCollectionResponse(resp); |
| convertToSat(resp); |
| |
| resp.addHeader("OData-Version", "3.0"); |
| resp.addHeader(boost::beast::http::field::location, |
| "/redfish/v1/Chassis/Test"); |
| |
| // We skip processing collection responses that have a 429 or 502 code |
| resp.result(boost::beast::http::status::too_many_requests); // 429 |
| RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp); |
| EXPECT_EQ(asyncResp->res.resultInt(), 404); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), ""); |
| |
| resp.result(boost::beast::http::status::bad_gateway); // 502 |
| RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp); |
| EXPECT_EQ(asyncResp->res.resultInt(), 404); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), ""); |
| } |
| void assertProcessResponseContentType(std::string_view contentType) |
| { |
| crow::Response resp; |
| resp.body() = "responseBody"; |
| resp.addHeader("Content-Type", contentType); |
| resp.addHeader("Location", "/redfish/v1/Chassis/TestChassis"); |
| resp.addHeader("Link", "metadataLink"); |
| resp.addHeader("Retry-After", "120"); |
| |
| auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); |
| RedfishAggregator::processResponse("prefix", asyncResp, resp); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), contentType); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), |
| "/redfish/v1/Chassis/prefix_TestChassis"); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Link"), ""); |
| EXPECT_EQ(asyncResp->res.getHeaderValue("Retry-After"), "120"); |
| EXPECT_EQ(asyncResp->res.body(), "responseBody"); |
| } |
| |
| TEST(processResponse, DifferentContentType) |
| { |
| assertProcessResponseContentType("application/xml"); |
| assertProcessResponseContentType("application/yaml"); |
| assertProcessResponseContentType("text/event-stream"); |
| assertProcessResponseContentType(";charset=utf-8"); |
| } |
| |
| bool containsSubordinateCollection(const std::string_view uri) |
| { |
| return searchCollectionsArray(uri, SearchType::ContainsSubordinate); |
| } |
| |
| bool containsCollection(const std::string_view uri) |
| { |
| return searchCollectionsArray(uri, SearchType::Collection); |
| } |
| |
| bool isCollOrCon(const std::string_view uri) |
| { |
| return searchCollectionsArray(uri, SearchType::CollOrCon); |
| } |
| |
| TEST(searchCollectionsArray, containsSubordinateValidURIs) |
| { |
| EXPECT_TRUE(containsSubordinateCollection("/redfish/v1")); |
| EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/")); |
| EXPECT_TRUE( |
| containsSubordinateCollection("/redfish/v1/AggregationService")); |
| EXPECT_TRUE( |
| containsSubordinateCollection("/redfish/v1/CompositionService/")); |
| EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/JobService")); |
| EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/JobService/Log")); |
| EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/KeyService")); |
| EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/LicenseService/")); |
| EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/PowerEquipment")); |
| EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/TaskService")); |
| EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/TelemetryService")); |
| EXPECT_TRUE(containsSubordinateCollection( |
| "/redfish/v1/TelemetryService/LogService/")); |
| EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/UpdateService")); |
| |
| EXPECT_TRUE(containsSubordinateCollection( |
| "/redfish/v1/UpdateService?$expand=.($levels=1)")); |
| } |
| |
| TEST(searchCollectionsArray, containsSubordinateInvalidURIs) |
| { |
| EXPECT_FALSE(containsSubordinateCollection("")); |
| EXPECT_FALSE(containsSubordinateCollection("http://")); |
| EXPECT_FALSE(containsSubordinateCollection("/redfish")); |
| EXPECT_FALSE(containsSubordinateCollection("/redfish/")); |
| EXPECT_FALSE(containsSubordinateCollection("/redfish//")); |
| EXPECT_FALSE(containsSubordinateCollection("/redfish/v1//")); |
| EXPECT_FALSE(containsSubordinateCollection("/redfish/v11")); |
| EXPECT_FALSE(containsSubordinateCollection("/redfish/v11/")); |
| EXPECT_FALSE(containsSubordinateCollection("www.test.com/redfish/v1")); |
| EXPECT_FALSE(containsSubordinateCollection("/fail")); |
| EXPECT_FALSE(containsSubordinateCollection( |
| "/redfish/v1/AggregationService/Aggregates")); |
| EXPECT_FALSE(containsSubordinateCollection( |
| "/redfish/v1/AggregationService/AggregationSources/")); |
| EXPECT_FALSE(containsSubordinateCollection("/redfish/v1/Cables/")); |
| EXPECT_FALSE( |
| containsSubordinateCollection("/redfish/v1/Chassis/chassisId")); |
| EXPECT_FALSE(containsSubordinateCollection("/redfish/v1/Fake")); |
| EXPECT_FALSE( |
| containsSubordinateCollection("/redfish/v1/TelemetryService//")); |
| EXPECT_FALSE(containsSubordinateCollection( |
| "/redfish/v1/TelemetryService/LogService/Entries")); |
| EXPECT_FALSE(containsSubordinateCollection( |
| "/redfish/v1/UpdateService/SoftwareInventory/")); |
| EXPECT_FALSE(containsSubordinateCollection( |
| "/redfish/v1/UpdateService/SoftwareInventory/Te")); |
| EXPECT_FALSE(containsSubordinateCollection( |
| "/redfish/v1/UpdateService/SoftwareInventory2")); |
| } |
| |
| TEST(searchCollectionsArray, collectionURIs) |
| { |
| EXPECT_TRUE(containsCollection("/redfish/v1/Chassis")); |
| EXPECT_TRUE(containsCollection("/redfish/v1/Chassis/")); |
| EXPECT_TRUE(containsCollection("/redfish/v1/Managers")); |
| EXPECT_TRUE(containsCollection("/redfish/v1/Systems")); |
| EXPECT_TRUE( |
| containsCollection("/redfish/v1/TelemetryService/LogService/Entries")); |
| EXPECT_TRUE( |
| containsCollection("/redfish/v1/TelemetryService/LogService/Entries/")); |
| EXPECT_TRUE( |
| containsCollection("/redfish/v1/UpdateService/FirmwareInventory")); |
| EXPECT_TRUE( |
| containsCollection("/redfish/v1/UpdateService/FirmwareInventory/")); |
| |
| EXPECT_FALSE(containsCollection("http://")); |
| EXPECT_FALSE(containsCollection("/redfish/v11/Chassis")); |
| EXPECT_FALSE(containsCollection("/redfish/v11/Chassis/")); |
| EXPECT_FALSE(containsCollection("/redfish/v1")); |
| EXPECT_FALSE(containsCollection("/redfish/v1/")); |
| EXPECT_FALSE(containsCollection("/redfish/v1//")); |
| EXPECT_FALSE(containsCollection("/redfish/v1/Chassis//")); |
| EXPECT_FALSE(containsCollection("/redfish/v1/Chassis/Test")); |
| EXPECT_FALSE(containsCollection("/redfish/v1/TelemetryService")); |
| EXPECT_FALSE(containsCollection("/redfish/v1/TelemetryService/")); |
| EXPECT_FALSE(containsCollection("/redfish/v1/UpdateService")); |
| EXPECT_FALSE( |
| containsCollection("/redfish/v1/UpdateService/FirmwareInventory/Test")); |
| EXPECT_FALSE( |
| containsCollection("/redfish/v1/UpdateService/FirmwareInventory/Tes/")); |
| EXPECT_FALSE( |
| containsCollection("/redfish/v1/UpdateService/SoftwareInventory/Te")); |
| EXPECT_FALSE( |
| containsCollection("/redfish/v1/UpdateService/SoftwareInventory2")); |
| EXPECT_FALSE(containsCollection("/redfish/v11")); |
| EXPECT_FALSE(containsCollection("/redfish/v11/")); |
| } |
| |
| TEST(searchCollectionsArray, collectionOrContainsURIs) |
| { |
| // Resources that are a top level collection or are uptree of one |
| EXPECT_TRUE(isCollOrCon("/redfish/v1/")); |
| EXPECT_TRUE(isCollOrCon("/redfish/v1/AggregationService")); |
| EXPECT_TRUE(isCollOrCon("/redfish/v1/CompositionService/")); |
| EXPECT_TRUE(isCollOrCon("/redfish/v1/Chassis")); |
| EXPECT_TRUE(isCollOrCon("/redfish/v1/Cables/")); |
| EXPECT_TRUE(isCollOrCon("/redfish/v1/Fabrics")); |
| EXPECT_TRUE(isCollOrCon("/redfish/v1/Managers")); |
| EXPECT_TRUE(isCollOrCon("/redfish/v1/UpdateService/FirmwareInventory")); |
| EXPECT_TRUE(isCollOrCon("/redfish/v1/UpdateService/FirmwareInventory/")); |
| |
| EXPECT_FALSE(isCollOrCon("http://")); |
| EXPECT_FALSE(isCollOrCon("/redfish/v11")); |
| EXPECT_FALSE(isCollOrCon("/redfish/v11/")); |
| EXPECT_FALSE(isCollOrCon("/redfish/v1/Chassis/Test")); |
| EXPECT_FALSE(isCollOrCon("/redfish/v1/Managers/Test/")); |
| EXPECT_FALSE(isCollOrCon("/redfish/v1/TaskService/Tasks/0")); |
| EXPECT_FALSE(isCollOrCon("/redfish/v1/UpdateService/FirmwareInventory/Te")); |
| EXPECT_FALSE(isCollOrCon("/redfish/v1/UpdateService/SoftwareInventory/Te")); |
| EXPECT_FALSE(isCollOrCon("/redfish/v1/UpdateService/SoftwareInventory2")); |
| } |
| |
| TEST(processContainsSubordinateResponse, addLinks) |
| { |
| crow::Response resp; |
| resp.result(200); |
| nlohmann::json jsonValue; |
| resp.addHeader("Content-Type", "application/json"); |
| jsonValue["@odata.id"] = "/redfish/v1"; |
| jsonValue["Fabrics"]["@odata.id"] = "/redfish/v1/Fabrics"; |
| jsonValue["Test"]["@odata.id"] = "/redfish/v1/Test"; |
| jsonValue["TelemetryService"]["@odata.id"] = "/redfish/v1/TelemetryService"; |
| jsonValue["UpdateService"]["@odata.id"] = "/redfish/v1/UpdateService"; |
| resp.body() = |
| jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); |
| |
| auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); |
| asyncResp->res.result(200); |
| asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1"; |
| asyncResp->res.jsonValue["Chassis"]["@odata.id"] = "/redfish/v1/Chassis"; |
| |
| RedfishAggregator::processContainsSubordinateResponse("prefix", asyncResp, |
| resp); |
| EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"], |
| "/redfish/v1/Chassis"); |
| EXPECT_EQ(asyncResp->res.jsonValue["Fabrics"]["@odata.id"], |
| "/redfish/v1/Fabrics"); |
| EXPECT_EQ(asyncResp->res.jsonValue["TelemetryService"]["@odata.id"], |
| "/redfish/v1/TelemetryService"); |
| EXPECT_EQ(asyncResp->res.jsonValue["UpdateService"]["@odata.id"], |
| "/redfish/v1/UpdateService"); |
| EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test")); |
| } |
| |
| TEST(processContainsSubordinateResponse, localNotOK) |
| { |
| auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); |
| asyncResp->res.addHeader("Content-Type", "application/json"); |
| messages::resourceNotFound(asyncResp->res, "", ""); |
| |
| // This field was added by resourceNotFound() |
| // Sanity test to make sure it gets removed later |
| EXPECT_TRUE(asyncResp->res.jsonValue.contains("error")); |
| |
| crow::Response resp; |
| resp.result(200); |
| nlohmann::json jsonValue; |
| resp.addHeader("Content-Type", "application/json"); |
| jsonValue["@odata.id"] = "/redfish/v1"; |
| jsonValue["@odata.type"] = "#ServiceRoot.v1_11_0.ServiceRoot"; |
| jsonValue["Id"] = "RootService"; |
| jsonValue["Name"] = "Root Service"; |
| jsonValue["Fabrics"]["@odata.id"] = "/redfish/v1/Fabrics"; |
| jsonValue["Test"]["@odata.id"] = "/redfish/v1/Test"; |
| jsonValue["TelemetryService"]["@odata.id"] = "/redfish/v1/TelemetryService"; |
| jsonValue["UpdateService"]["@odata.id"] = "/redfish/v1/UpdateService"; |
| resp.body() = |
| jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); |
| |
| RedfishAggregator::processContainsSubordinateResponse("prefix", asyncResp, |
| resp); |
| |
| // Most of the response should get copied over since asyncResp is a 404 |
| EXPECT_EQ(asyncResp->res.resultInt(), 200); |
| EXPECT_EQ(asyncResp->res.jsonValue["@odata.id"], "/redfish/v1"); |
| EXPECT_EQ(asyncResp->res.jsonValue["@odata.type"], |
| "#ServiceRoot.v1_11_0.ServiceRoot"); |
| EXPECT_EQ(asyncResp->res.jsonValue["Id"], "RootService"); |
| EXPECT_EQ(asyncResp->res.jsonValue["Name"], "Root Service"); |
| |
| EXPECT_EQ(asyncResp->res.jsonValue["Fabrics"]["@odata.id"], |
| "/redfish/v1/Fabrics"); |
| EXPECT_EQ(asyncResp->res.jsonValue["TelemetryService"]["@odata.id"], |
| "/redfish/v1/TelemetryService"); |
| EXPECT_EQ(asyncResp->res.jsonValue["UpdateService"]["@odata.id"], |
| "/redfish/v1/UpdateService"); |
| EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test")); |
| EXPECT_FALSE(asyncResp->res.jsonValue.contains("error")); |
| |
| // Test for local response being partially populated before throwing error |
| asyncResp = std::make_shared<bmcweb::AsyncResp>(); |
| asyncResp->res.addHeader("Content-Type", "application/json"); |
| asyncResp->res.jsonValue["Chassis"]["@odata.id"] = "/redfish/v1/Chassis"; |
| asyncResp->res.jsonValue["Fake"]["@odata.id"] = "/redfish/v1/Fake"; |
| messages::internalError(asyncResp->res); |
| |
| RedfishAggregator::processContainsSubordinateResponse("prefix", asyncResp, |
| resp); |
| |
| // These should also be copied over since asyncResp is a 500 |
| EXPECT_EQ(asyncResp->res.resultInt(), 200); |
| EXPECT_EQ(asyncResp->res.jsonValue["@odata.id"], "/redfish/v1"); |
| EXPECT_EQ(asyncResp->res.jsonValue["@odata.type"], |
| "#ServiceRoot.v1_11_0.ServiceRoot"); |
| EXPECT_EQ(asyncResp->res.jsonValue["Id"], "RootService"); |
| EXPECT_EQ(asyncResp->res.jsonValue["Name"], "Root Service"); |
| |
| EXPECT_EQ(asyncResp->res.jsonValue["Fabrics"]["@odata.id"], |
| "/redfish/v1/Fabrics"); |
| EXPECT_EQ(asyncResp->res.jsonValue["TelemetryService"]["@odata.id"], |
| "/redfish/v1/TelemetryService"); |
| EXPECT_EQ(asyncResp->res.jsonValue["UpdateService"]["@odata.id"], |
| "/redfish/v1/UpdateService"); |
| EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test")); |
| EXPECT_FALSE(asyncResp->res.jsonValue.contains("error")); |
| |
| // These fields should still be present |
| EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"], |
| "/redfish/v1/Chassis"); |
| EXPECT_EQ(asyncResp->res.jsonValue["Fake"]["@odata.id"], |
| "/redfish/v1/Fake"); |
| } |
| |
| TEST(processContainsSubordinateResponse, noValidLinks) |
| { |
| auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); |
| asyncResp->res.result(500); |
| asyncResp->res.jsonValue["Chassis"]["@odata.id"] = "/redfish/v1/Chassis"; |
| |
| crow::Response resp; |
| resp.result(200); |
| nlohmann::json jsonValue; |
| resp.addHeader("Content-Type", "application/json"); |
| jsonValue["@odata.id"] = "/redfish/v1"; |
| resp.body() = |
| jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); |
| |
| RedfishAggregator::processContainsSubordinateResponse("prefix", asyncResp, |
| resp); |
| |
| // We won't add any links from response so asyncResp shouldn't change |
| EXPECT_EQ(asyncResp->res.resultInt(), 500); |
| EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"], |
| "/redfish/v1/Chassis"); |
| EXPECT_FALSE(asyncResp->res.jsonValue.contains("@odata.id")); |
| |
| // Sat response is non-500 so it shouldn't get copied over |
| asyncResp->res.result(200); |
| resp.result(500); |
| jsonValue["Fabrics"]["@odata.id"] = "/redfish/v1/Fabrics"; |
| jsonValue["Test"]["@odata.id"] = "/redfish/v1/Test"; |
| jsonValue["TelemetryService"]["@odata.id"] = "/redfish/v1/TelemetryService"; |
| jsonValue["UpdateService"]["@odata.id"] = "/redfish/v1/UpdateService"; |
| resp.body() = |
| jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); |
| |
| RedfishAggregator::processContainsSubordinateResponse("prefix", asyncResp, |
| resp); |
| |
| EXPECT_EQ(asyncResp->res.resultInt(), 200); |
| EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"], |
| "/redfish/v1/Chassis"); |
| EXPECT_FALSE(asyncResp->res.jsonValue.contains("@odata.id")); |
| EXPECT_FALSE(asyncResp->res.jsonValue.contains("Fabrics")); |
| EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test")); |
| EXPECT_FALSE(asyncResp->res.jsonValue.contains("TelemetryService")); |
| EXPECT_FALSE(asyncResp->res.jsonValue.contains("UpdateService")); |
| } |
| |
| } // namespace |
| } // namespace redfish |