1 #include "async_resp.hpp" 2 #include "error_messages.hpp" 3 #include "http_response.hpp" 4 #include "redfish_aggregator.hpp" 5 6 #include <boost/beast/http/field.hpp> 7 #include <boost/beast/http/status.hpp> 8 #include <nlohmann/json.hpp> 9 10 #include <array> 11 #include <memory> 12 #include <string> 13 #include <utility> 14 15 #include <gtest/gtest.h> // IWYU pragma: keep 16 17 namespace redfish 18 { 19 namespace 20 { 21 22 TEST(IsPropertyUri, SupportedPropertyReturnsTrue) 23 { 24 EXPECT_TRUE(isPropertyUri("@Redfish.ActionInfo")); 25 EXPECT_TRUE(isPropertyUri("@odata.id")); 26 EXPECT_TRUE(isPropertyUri("Image")); 27 EXPECT_TRUE(isPropertyUri("MetricProperty")); 28 EXPECT_TRUE(isPropertyUri("TaskMonitor")); 29 EXPECT_TRUE(isPropertyUri("target")); 30 } 31 32 TEST(IsPropertyUri, CaseInsensitiveURIReturnsTrue) 33 { 34 EXPECT_TRUE(isPropertyUri("AdditionalDataURI")); 35 EXPECT_TRUE(isPropertyUri("DataSourceUri")); 36 EXPECT_TRUE(isPropertyUri("uri")); 37 EXPECT_TRUE(isPropertyUri("URI")); 38 } 39 40 TEST(IsPropertyUri, SpeificallyIgnoredPropertyReturnsFalse) 41 { 42 EXPECT_FALSE(isPropertyUri("@odata.context")); 43 EXPECT_FALSE(isPropertyUri("Destination")); 44 EXPECT_FALSE(isPropertyUri("HostName")); 45 EXPECT_FALSE(isPropertyUri("OriginOfCondition")); 46 } 47 48 TEST(IsPropertyUri, UnsupportedPropertyReturnsFalse) 49 { 50 EXPECT_FALSE(isPropertyUri("Name")); 51 EXPECT_FALSE(isPropertyUri("Health")); 52 EXPECT_FALSE(isPropertyUri("Id")); 53 } 54 55 TEST(addPrefixToItem, ValidURIs) 56 { 57 nlohmann::json jsonRequest; 58 constexpr std::array validRoots{"Cables", 59 "Chassis", 60 "Fabrics", 61 "PowerEquipment/FloorPDUs", 62 "Systems", 63 "TaskService/Tasks", 64 "TelemetryService/LogService/Entries", 65 "UpdateService/SoftwareInventory"}; 66 67 // We're only testing prefix fixing so it's alright that some of the 68 // resulting URIs will not actually be possible as defined by the schema 69 constexpr std::array validIDs{"1", 70 "1/", 71 "Test", 72 "Test/", 73 "Extra_Test", 74 "Extra_Test/", 75 "Extra_Test/Sensors", 76 "Extra_Test/Sensors/", 77 "Extra_Test/Sensors/power_sensor", 78 "Extra_Test/Sensors/power_sensor/"}; 79 80 // Construct URIs which should have prefix fixing applied 81 for (const auto& root : validRoots) 82 { 83 for (const auto& id : validIDs) 84 { 85 std::string initial("/redfish/v1/" + std::string(root) + "/"); 86 std::string correct(initial + "asdfjkl_" + std::string(id)); 87 initial += id; 88 jsonRequest["@odata.id"] = initial; 89 addPrefixToItem(jsonRequest["@odata.id"], "asdfjkl"); 90 EXPECT_EQ(jsonRequest["@odata.id"], correct); 91 } 92 } 93 } 94 95 TEST(addPrefixToItem, UnsupportedURIs) 96 { 97 nlohmann::json jsonRequest; 98 constexpr std::array invalidRoots{ 99 "FakeCollection", "JsonSchemas", 100 "PowerEquipment", "TaskService", 101 "TelemetryService/Entries", "UpdateService"}; 102 103 constexpr std::array validIDs{"1", 104 "1/", 105 "Test", 106 "Test/", 107 "Extra_Test", 108 "Extra_Test/", 109 "Extra_Test/Sensors", 110 "Extra_Test/Sensors/", 111 "Extra_Test/Sensors/power_sensor", 112 "Extra_Test/Sensors/power_sensor/"}; 113 114 // Construct URIs which should NOT have prefix fixing applied 115 for (const auto& root : invalidRoots) 116 { 117 for (const auto& id : validIDs) 118 { 119 std::string initial("/redfish/v1/" + std::string(root) + "/"); 120 initial += id; 121 jsonRequest["@odata.id"] = initial; 122 addPrefixToItem(jsonRequest["@odata.id"], "asdfjkl"); 123 EXPECT_EQ(jsonRequest["@odata.id"], initial); 124 } 125 } 126 } 127 128 TEST(addPrefixToItem, TopLevelCollections) 129 { 130 nlohmann::json jsonRequest; 131 constexpr std::array validRoots{"Cables", 132 "Chassis/", 133 "Fabrics", 134 "JsonSchemas", 135 "PowerEquipment/FloorPDUs", 136 "Systems", 137 "TaskService/Tasks", 138 "TelemetryService/LogService/Entries", 139 "TelemetryService/LogService/Entries/", 140 "UpdateService/SoftwareInventory/"}; 141 142 // Construct URIs for top level collections. Prefixes should NOT be 143 // applied to any of the URIs 144 for (const auto& root : validRoots) 145 { 146 std::string initial("/redfish/v1/" + std::string(root)); 147 jsonRequest["@odata.id"] = initial; 148 addPrefixToItem(jsonRequest["@odata.id"], "prefix"); 149 EXPECT_EQ(jsonRequest["@odata.id"], initial); 150 } 151 } 152 153 TEST(addPrefixes, ParseJsonObject) 154 { 155 nlohmann::json parameter; 156 parameter["Name"] = "/redfish/v1/Chassis/fakeName"; 157 parameter["@odata.id"] = "/redfish/v1/Chassis/fakeChassis"; 158 159 addPrefixes(parameter, "abcd"); 160 EXPECT_EQ(parameter["Name"], "/redfish/v1/Chassis/fakeName"); 161 EXPECT_EQ(parameter["@odata.id"], "/redfish/v1/Chassis/abcd_fakeChassis"); 162 } 163 164 TEST(addPrefixes, ParseJsonArray) 165 { 166 nlohmann::json array = nlohmann::json::parse(R"( 167 { 168 "Conditions": [ 169 { 170 "Message": "This is a test", 171 "@odata.id": "/redfish/v1/Chassis/TestChassis" 172 }, 173 { 174 "Message": "This is also a test", 175 "@odata.id": "/redfish/v1/Chassis/TestChassis2" 176 } 177 ] 178 } 179 )", 180 nullptr, false); 181 182 addPrefixes(array, "5B42"); 183 EXPECT_EQ(array["Conditions"][0]["@odata.id"], 184 "/redfish/v1/Chassis/5B42_TestChassis"); 185 EXPECT_EQ(array["Conditions"][1]["@odata.id"], 186 "/redfish/v1/Chassis/5B42_TestChassis2"); 187 } 188 189 TEST(addPrefixes, ParseJsonObjectNestedArray) 190 { 191 nlohmann::json objWithArray = nlohmann::json::parse(R"( 192 { 193 "Status": { 194 "Conditions": [ 195 { 196 "Message": "This is a test", 197 "MessageId": "Test", 198 "OriginOfCondition": { 199 "@odata.id": "/redfish/v1/Chassis/TestChassis" 200 }, 201 "Severity": "Critical" 202 } 203 ], 204 "Health": "Critical", 205 "State": "Enabled" 206 } 207 } 208 )", 209 nullptr, false); 210 211 addPrefixes(objWithArray, "5B42"); 212 nlohmann::json& array = objWithArray["Status"]["Conditions"]; 213 EXPECT_EQ(array[0]["OriginOfCondition"]["@odata.id"], 214 "/redfish/v1/Chassis/5B42_TestChassis"); 215 } 216 217 TEST(addPrefixes, FixHttpHeadersInResponseBody) 218 { 219 nlohmann::json taskResp = nlohmann::json::parse(R"( 220 { 221 "@odata.id": "/redfish/v1/TaskService/Tasks/0", 222 "Name": "Task 0", 223 "Payload": { 224 "HttpHeaders": [ 225 "User-Agent: curl/7.87.0", 226 "Accept: */*", 227 "Host: 127.127.12.7", 228 "Content-Length: 33", 229 "Location: /redfish/v1/Managers/bmc/LogServices/Dump/Entries/0" 230 ] 231 }, 232 "PercentComplete": 100, 233 "TaskMonitor": "/redfish/v1/TaskService/Tasks/0/Monitor", 234 "TaskState": "Completed", 235 "TaskStatus": "OK" 236 } 237 )", 238 nullptr, false); 239 240 addPrefixes(taskResp, "5B247A"); 241 EXPECT_EQ(taskResp["@odata.id"], "/redfish/v1/TaskService/Tasks/5B247A_0"); 242 EXPECT_EQ(taskResp["TaskMonitor"], 243 "/redfish/v1/TaskService/Tasks/5B247A_0/Monitor"); 244 nlohmann::json& httpHeaders = taskResp["Payload"]["HttpHeaders"]; 245 EXPECT_EQ( 246 httpHeaders[4], 247 "Location: /redfish/v1/Managers/5B247A_bmc/LogServices/Dump/Entries/0"); 248 } 249 250 // Attempts to perform prefix fixing on a response with response code "result". 251 // Fixing should always occur 252 void assertProcessResponse(unsigned result) 253 { 254 nlohmann::json jsonResp; 255 jsonResp["@odata.id"] = "/redfish/v1/Chassis/TestChassis"; 256 jsonResp["Name"] = "Test"; 257 258 crow::Response resp; 259 resp.write( 260 jsonResp.dump(2, ' ', true, nlohmann::json::error_handler_t::replace)); 261 resp.addHeader("Content-Type", "application/json"); 262 resp.addHeader("Allow", "GET"); 263 resp.addHeader("Location", "/redfish/v1/Chassis/TestChassis"); 264 resp.addHeader("Link", "</redfish/v1/Test.json>; rel=describedby"); 265 resp.addHeader("Retry-After", "120"); 266 resp.result(result); 267 268 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 269 RedfishAggregator::processResponse("prefix", asyncResp, resp); 270 271 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), 272 "application/json"); 273 EXPECT_EQ(asyncResp->res.getHeaderValue("Allow"), "GET"); 274 EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), 275 "/redfish/v1/Chassis/prefix_TestChassis"); 276 EXPECT_EQ(asyncResp->res.getHeaderValue("Link"), ""); 277 EXPECT_EQ(asyncResp->res.getHeaderValue("Retry-After"), "120"); 278 279 EXPECT_EQ(asyncResp->res.jsonValue["Name"], "Test"); 280 EXPECT_EQ(asyncResp->res.jsonValue["@odata.id"], 281 "/redfish/v1/Chassis/prefix_TestChassis"); 282 EXPECT_EQ(asyncResp->res.resultInt(), result); 283 } 284 285 TEST(processResponse, validResponseCodes) 286 { 287 assertProcessResponse(100); 288 assertProcessResponse(200); 289 assertProcessResponse(204); 290 assertProcessResponse(300); 291 assertProcessResponse(404); 292 assertProcessResponse(405); 293 assertProcessResponse(500); 294 assertProcessResponse(507); 295 } 296 297 TEST(processResponse, preserveHeaders) 298 { 299 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 300 asyncResp->res.addHeader("OData-Version", "4.0"); 301 asyncResp->res.result(boost::beast::http::status::ok); 302 303 crow::Response resp; 304 resp.addHeader("OData-Version", "3.0"); 305 resp.addHeader(boost::beast::http::field::location, 306 "/redfish/v1/Chassis/Test"); 307 resp.result(boost::beast::http::status::too_many_requests); // 429 308 309 RedfishAggregator::processResponse("prefix", asyncResp, resp); 310 EXPECT_EQ(asyncResp->res.resultInt(), 429); 311 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); 312 EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), ""); 313 314 asyncResp->res.result(boost::beast::http::status::ok); 315 resp.result(boost::beast::http::status::bad_gateway); // 502 316 317 RedfishAggregator::processResponse("prefix", asyncResp, resp); 318 EXPECT_EQ(asyncResp->res.resultInt(), 502); 319 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); 320 EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), ""); 321 } 322 323 // Helper function to correctly populate a ComputerSystem collection response 324 void populateCollectionResponse(crow::Response& resp) 325 { 326 nlohmann::json jsonResp = nlohmann::json::parse(R"( 327 { 328 "@odata.id": "/redfish/v1/Systems", 329 "@odata.type": "#ComputerSystemCollection.ComputerSystemCollection", 330 "Members": [ 331 { 332 "@odata.id": "/redfish/v1/Systems/system" 333 } 334 ], 335 "Members@odata.count": 1, 336 "Name": "Computer System Collection" 337 } 338 )", 339 nullptr, false); 340 341 resp.clear(); 342 // resp.body() = 343 // jsonResp.dump(2, ' ', true, 344 // nlohmann::json::error_handler_t::replace); 345 resp.jsonValue = std::move(jsonResp); 346 resp.addHeader("OData-Version", "4.0"); 347 resp.addHeader("Content-Type", "application/json"); 348 resp.result(boost::beast::http::status::ok); 349 } 350 351 void populateCollectionNotFound(crow::Response& resp) 352 { 353 resp.clear(); 354 resp.addHeader("OData-Version", "4.0"); 355 resp.result(boost::beast::http::status::not_found); 356 } 357 358 // Used with the above functions to convert the response to appear like it's 359 // from a satellite which will not have a json component 360 void convertToSat(crow::Response& resp) 361 { 362 resp.write(resp.jsonValue.dump(2, ' ', true, 363 nlohmann::json::error_handler_t::replace)); 364 resp.jsonValue.clear(); 365 } 366 367 TEST(processCollectionResponse, localOnly) 368 { 369 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 370 crow::Response resp; 371 populateCollectionResponse(asyncResp->res); 372 populateCollectionNotFound(resp); 373 374 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp); 375 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); 376 EXPECT_EQ(asyncResp->res.resultInt(), 200); 377 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), 378 "application/json"); 379 EXPECT_EQ(asyncResp->res.jsonValue["Members@odata.count"], 1); 380 for (auto& member : asyncResp->res.jsonValue["Members"]) 381 { 382 // There should only be one member 383 EXPECT_EQ(member["@odata.id"], "/redfish/v1/Systems/system"); 384 } 385 } 386 387 TEST(processCollectionResponse, satelliteOnly) 388 { 389 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 390 crow::Response resp; 391 populateCollectionNotFound(asyncResp->res); 392 populateCollectionResponse(resp); 393 convertToSat(resp); 394 395 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp); 396 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); 397 EXPECT_EQ(asyncResp->res.resultInt(), 200); 398 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), 399 "application/json"); 400 EXPECT_EQ(asyncResp->res.jsonValue["Members@odata.count"], 1); 401 for (auto& member : asyncResp->res.jsonValue["Members"]) 402 { 403 // There should only be one member 404 EXPECT_EQ(member["@odata.id"], "/redfish/v1/Systems/prefix_system"); 405 } 406 } 407 408 TEST(processCollectionResponse, bothExist) 409 { 410 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 411 crow::Response resp; 412 populateCollectionResponse(asyncResp->res); 413 populateCollectionResponse(resp); 414 convertToSat(resp); 415 416 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp); 417 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); 418 EXPECT_EQ(asyncResp->res.resultInt(), 200); 419 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), 420 "application/json"); 421 EXPECT_EQ(asyncResp->res.jsonValue["Members@odata.count"], 2); 422 423 bool foundLocal = false; 424 bool foundSat = false; 425 for (const auto& member : asyncResp->res.jsonValue["Members"]) 426 { 427 if (member["@odata.id"] == "/redfish/v1/Systems/system") 428 { 429 foundLocal = true; 430 } 431 else if (member["@odata.id"] == "/redfish/v1/Systems/prefix_system") 432 { 433 foundSat = true; 434 } 435 } 436 EXPECT_TRUE(foundLocal); 437 EXPECT_TRUE(foundSat); 438 } 439 440 TEST(processCollectionResponse, satelliteWrongContentHeader) 441 { 442 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 443 crow::Response resp; 444 populateCollectionResponse(asyncResp->res); 445 populateCollectionResponse(resp); 446 convertToSat(resp); 447 448 // Ignore the satellite even though otherwise valid 449 resp.clearHeader(boost::beast::http::field::content_type); 450 451 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp); 452 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); 453 EXPECT_EQ(asyncResp->res.resultInt(), 200); 454 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), 455 "application/json"); 456 EXPECT_EQ(asyncResp->res.jsonValue["Members@odata.count"], 1); 457 for (auto& member : asyncResp->res.jsonValue["Members"]) 458 { 459 EXPECT_EQ(member["@odata.id"], "/redfish/v1/Systems/system"); 460 } 461 } 462 463 TEST(processCollectionResponse, neitherExist) 464 { 465 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 466 crow::Response resp; 467 populateCollectionNotFound(asyncResp->res); 468 populateCollectionNotFound(resp); 469 convertToSat(resp); 470 471 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp); 472 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); 473 EXPECT_EQ(asyncResp->res.resultInt(), 404); 474 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), ""); 475 } 476 477 TEST(processCollectionResponse, preserveHeaders) 478 { 479 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 480 crow::Response resp; 481 populateCollectionNotFound(asyncResp->res); 482 populateCollectionResponse(resp); 483 convertToSat(resp); 484 485 resp.addHeader("OData-Version", "3.0"); 486 resp.addHeader(boost::beast::http::field::location, 487 "/redfish/v1/Chassis/Test"); 488 489 // We skip processing collection responses that have a 429 or 502 code 490 resp.result(boost::beast::http::status::too_many_requests); // 429 491 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp); 492 EXPECT_EQ(asyncResp->res.resultInt(), 404); 493 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); 494 EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), ""); 495 496 resp.result(boost::beast::http::status::bad_gateway); // 502 497 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp); 498 EXPECT_EQ(asyncResp->res.resultInt(), 404); 499 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0"); 500 EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), ""); 501 } 502 void assertProcessResponseContentType(std::string_view contentType) 503 { 504 crow::Response resp; 505 resp.write("responseBody"); 506 resp.addHeader("Content-Type", contentType); 507 resp.addHeader("Location", "/redfish/v1/Chassis/TestChassis"); 508 resp.addHeader("Link", "metadataLink"); 509 resp.addHeader("Retry-After", "120"); 510 511 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 512 RedfishAggregator::processResponse("prefix", asyncResp, resp); 513 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), contentType); 514 EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), 515 "/redfish/v1/Chassis/prefix_TestChassis"); 516 EXPECT_EQ(asyncResp->res.getHeaderValue("Link"), ""); 517 EXPECT_EQ(asyncResp->res.getHeaderValue("Retry-After"), "120"); 518 EXPECT_EQ(*asyncResp->res.body(), "responseBody"); 519 } 520 521 TEST(processResponse, DifferentContentType) 522 { 523 assertProcessResponseContentType("application/xml"); 524 assertProcessResponseContentType("application/yaml"); 525 assertProcessResponseContentType("text/event-stream"); 526 assertProcessResponseContentType(";charset=utf-8"); 527 } 528 529 bool containsSubordinateCollection(const std::string_view uri) 530 { 531 return searchCollectionsArray(uri, SearchType::ContainsSubordinate); 532 } 533 534 bool containsCollection(const std::string_view uri) 535 { 536 return searchCollectionsArray(uri, SearchType::Collection); 537 } 538 539 bool isCollOrCon(const std::string_view uri) 540 { 541 return searchCollectionsArray(uri, SearchType::CollOrCon); 542 } 543 544 TEST(searchCollectionsArray, containsSubordinateValidURIs) 545 { 546 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1")); 547 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/")); 548 EXPECT_TRUE( 549 containsSubordinateCollection("/redfish/v1/AggregationService")); 550 EXPECT_TRUE( 551 containsSubordinateCollection("/redfish/v1/CompositionService/")); 552 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/JobService")); 553 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/JobService/Log")); 554 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/KeyService")); 555 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/LicenseService/")); 556 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/PowerEquipment")); 557 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/TaskService")); 558 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/TelemetryService")); 559 EXPECT_TRUE(containsSubordinateCollection( 560 "/redfish/v1/TelemetryService/LogService/")); 561 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/UpdateService")); 562 } 563 564 TEST(searchCollectionsArray, containsSubordinateInvalidURIs) 565 { 566 EXPECT_FALSE(containsSubordinateCollection("")); 567 EXPECT_FALSE(containsSubordinateCollection("http://")); 568 EXPECT_FALSE(containsSubordinateCollection("/redfish")); 569 EXPECT_FALSE(containsSubordinateCollection("/redfish/")); 570 EXPECT_FALSE(containsSubordinateCollection("/redfish//")); 571 EXPECT_FALSE(containsSubordinateCollection("/redfish/v1//")); 572 EXPECT_FALSE(containsSubordinateCollection("/redfish/v11")); 573 EXPECT_FALSE(containsSubordinateCollection("/redfish/v11/")); 574 EXPECT_FALSE(containsSubordinateCollection("www.test.com/redfish/v1")); 575 EXPECT_FALSE(containsSubordinateCollection("/fail")); 576 EXPECT_FALSE(containsSubordinateCollection( 577 "/redfish/v1/AggregationService/Aggregates")); 578 EXPECT_FALSE(containsSubordinateCollection( 579 "/redfish/v1/AggregationService/AggregationSources/")); 580 EXPECT_FALSE(containsSubordinateCollection("/redfish/v1/Cables/")); 581 EXPECT_FALSE( 582 containsSubordinateCollection("/redfish/v1/Chassis/chassisId")); 583 EXPECT_FALSE(containsSubordinateCollection("/redfish/v1/Fake")); 584 EXPECT_FALSE( 585 containsSubordinateCollection("/redfish/v1/TelemetryService//")); 586 EXPECT_FALSE(containsSubordinateCollection( 587 "/redfish/v1/TelemetryService/LogService/Entries")); 588 EXPECT_FALSE(containsSubordinateCollection( 589 "/redfish/v1/UpdateService/SoftwareInventory/")); 590 EXPECT_FALSE(containsSubordinateCollection( 591 "/redfish/v1/UpdateService/SoftwareInventory/Te")); 592 EXPECT_FALSE(containsSubordinateCollection( 593 "/redfish/v1/UpdateService/SoftwareInventory2")); 594 } 595 596 TEST(searchCollectionsArray, collectionURIs) 597 { 598 EXPECT_TRUE(containsCollection("/redfish/v1/Chassis")); 599 EXPECT_TRUE(containsCollection("/redfish/v1/Chassis/")); 600 EXPECT_TRUE(containsCollection("/redfish/v1/Managers")); 601 EXPECT_TRUE(containsCollection("/redfish/v1/Systems")); 602 EXPECT_TRUE( 603 containsCollection("/redfish/v1/TelemetryService/LogService/Entries")); 604 EXPECT_TRUE( 605 containsCollection("/redfish/v1/TelemetryService/LogService/Entries/")); 606 EXPECT_TRUE( 607 containsCollection("/redfish/v1/UpdateService/FirmwareInventory")); 608 EXPECT_TRUE( 609 containsCollection("/redfish/v1/UpdateService/FirmwareInventory/")); 610 611 EXPECT_FALSE(containsCollection("http://")); 612 EXPECT_FALSE(containsCollection("/redfish/v11/Chassis")); 613 EXPECT_FALSE(containsCollection("/redfish/v11/Chassis/")); 614 EXPECT_FALSE(containsCollection("/redfish/v1")); 615 EXPECT_FALSE(containsCollection("/redfish/v1/")); 616 EXPECT_FALSE(containsCollection("/redfish/v1//")); 617 EXPECT_FALSE(containsCollection("/redfish/v1/Chassis//")); 618 EXPECT_FALSE(containsCollection("/redfish/v1/Chassis/Test")); 619 EXPECT_FALSE(containsCollection("/redfish/v1/TelemetryService")); 620 EXPECT_FALSE(containsCollection("/redfish/v1/TelemetryService/")); 621 EXPECT_FALSE(containsCollection("/redfish/v1/UpdateService")); 622 EXPECT_FALSE( 623 containsCollection("/redfish/v1/UpdateService/FirmwareInventory/Test")); 624 EXPECT_FALSE( 625 containsCollection("/redfish/v1/UpdateService/FirmwareInventory/Tes/")); 626 EXPECT_FALSE( 627 containsCollection("/redfish/v1/UpdateService/SoftwareInventory/Te")); 628 EXPECT_FALSE( 629 containsCollection("/redfish/v1/UpdateService/SoftwareInventory2")); 630 EXPECT_FALSE(containsCollection("/redfish/v11")); 631 EXPECT_FALSE(containsCollection("/redfish/v11/")); 632 } 633 634 TEST(searchCollectionsArray, collectionOrContainsURIs) 635 { 636 // Resources that are a top level collection or are uptree of one 637 EXPECT_TRUE(isCollOrCon("/redfish/v1/")); 638 EXPECT_TRUE(isCollOrCon("/redfish/v1/AggregationService")); 639 EXPECT_TRUE(isCollOrCon("/redfish/v1/CompositionService/")); 640 EXPECT_TRUE(isCollOrCon("/redfish/v1/Chassis")); 641 EXPECT_TRUE(isCollOrCon("/redfish/v1/Cables/")); 642 EXPECT_TRUE(isCollOrCon("/redfish/v1/Fabrics")); 643 EXPECT_TRUE(isCollOrCon("/redfish/v1/Managers")); 644 EXPECT_TRUE(isCollOrCon("/redfish/v1/UpdateService/FirmwareInventory")); 645 EXPECT_TRUE(isCollOrCon("/redfish/v1/UpdateService/FirmwareInventory/")); 646 647 EXPECT_FALSE(isCollOrCon("http://")); 648 EXPECT_FALSE(isCollOrCon("/redfish/v11")); 649 EXPECT_FALSE(isCollOrCon("/redfish/v11/")); 650 EXPECT_FALSE(isCollOrCon("/redfish/v1/Chassis/Test")); 651 EXPECT_FALSE(isCollOrCon("/redfish/v1/Managers/Test/")); 652 EXPECT_FALSE(isCollOrCon("/redfish/v1/TaskService/Tasks/0")); 653 EXPECT_FALSE(isCollOrCon("/redfish/v1/UpdateService/FirmwareInventory/Te")); 654 EXPECT_FALSE(isCollOrCon("/redfish/v1/UpdateService/SoftwareInventory/Te")); 655 EXPECT_FALSE(isCollOrCon("/redfish/v1/UpdateService/SoftwareInventory2")); 656 } 657 658 TEST(processContainsSubordinateResponse, addLinks) 659 { 660 crow::Response resp; 661 resp.result(200); 662 nlohmann::json jsonValue; 663 resp.addHeader("Content-Type", "application/json"); 664 jsonValue["@odata.id"] = "/redfish/v1"; 665 jsonValue["Fabrics"]["@odata.id"] = "/redfish/v1/Fabrics"; 666 jsonValue["Test"]["@odata.id"] = "/redfish/v1/Test"; 667 jsonValue["TelemetryService"]["@odata.id"] = "/redfish/v1/TelemetryService"; 668 jsonValue["UpdateService"]["@odata.id"] = "/redfish/v1/UpdateService"; 669 resp.write( 670 jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace)); 671 672 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 673 asyncResp->res.result(200); 674 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1"; 675 asyncResp->res.jsonValue["Chassis"]["@odata.id"] = "/redfish/v1/Chassis"; 676 677 RedfishAggregator::processContainsSubordinateResponse("prefix", asyncResp, 678 resp); 679 EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"], 680 "/redfish/v1/Chassis"); 681 EXPECT_EQ(asyncResp->res.jsonValue["Fabrics"]["@odata.id"], 682 "/redfish/v1/Fabrics"); 683 EXPECT_EQ(asyncResp->res.jsonValue["TelemetryService"]["@odata.id"], 684 "/redfish/v1/TelemetryService"); 685 EXPECT_EQ(asyncResp->res.jsonValue["UpdateService"]["@odata.id"], 686 "/redfish/v1/UpdateService"); 687 EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test")); 688 } 689 690 TEST(processContainsSubordinateResponse, localNotOK) 691 { 692 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 693 asyncResp->res.addHeader("Content-Type", "application/json"); 694 messages::resourceNotFound(asyncResp->res, "", ""); 695 696 // This field was added by resourceNotFound() 697 // Sanity test to make sure it gets removed later 698 EXPECT_TRUE(asyncResp->res.jsonValue.contains("error")); 699 700 crow::Response resp; 701 resp.result(200); 702 nlohmann::json jsonValue; 703 resp.addHeader("Content-Type", "application/json"); 704 jsonValue["@odata.id"] = "/redfish/v1"; 705 jsonValue["@odata.type"] = "#ServiceRoot.v1_11_0.ServiceRoot"; 706 jsonValue["Id"] = "RootService"; 707 jsonValue["Name"] = "Root Service"; 708 jsonValue["Fabrics"]["@odata.id"] = "/redfish/v1/Fabrics"; 709 jsonValue["Test"]["@odata.id"] = "/redfish/v1/Test"; 710 jsonValue["TelemetryService"]["@odata.id"] = "/redfish/v1/TelemetryService"; 711 jsonValue["UpdateService"]["@odata.id"] = "/redfish/v1/UpdateService"; 712 resp.write( 713 jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace)); 714 715 RedfishAggregator::processContainsSubordinateResponse("prefix", asyncResp, 716 resp); 717 718 // Most of the response should get copied over since asyncResp is a 404 719 EXPECT_EQ(asyncResp->res.resultInt(), 200); 720 EXPECT_EQ(asyncResp->res.jsonValue["@odata.id"], "/redfish/v1"); 721 EXPECT_EQ(asyncResp->res.jsonValue["@odata.type"], 722 "#ServiceRoot.v1_11_0.ServiceRoot"); 723 EXPECT_EQ(asyncResp->res.jsonValue["Id"], "RootService"); 724 EXPECT_EQ(asyncResp->res.jsonValue["Name"], "Root Service"); 725 726 EXPECT_EQ(asyncResp->res.jsonValue["Fabrics"]["@odata.id"], 727 "/redfish/v1/Fabrics"); 728 EXPECT_EQ(asyncResp->res.jsonValue["TelemetryService"]["@odata.id"], 729 "/redfish/v1/TelemetryService"); 730 EXPECT_EQ(asyncResp->res.jsonValue["UpdateService"]["@odata.id"], 731 "/redfish/v1/UpdateService"); 732 EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test")); 733 EXPECT_FALSE(asyncResp->res.jsonValue.contains("error")); 734 735 // Test for local response being partially populated before throwing error 736 asyncResp = std::make_shared<bmcweb::AsyncResp>(); 737 asyncResp->res.addHeader("Content-Type", "application/json"); 738 asyncResp->res.jsonValue["Chassis"]["@odata.id"] = "/redfish/v1/Chassis"; 739 asyncResp->res.jsonValue["Fake"]["@odata.id"] = "/redfish/v1/Fake"; 740 messages::internalError(asyncResp->res); 741 742 RedfishAggregator::processContainsSubordinateResponse("prefix", asyncResp, 743 resp); 744 745 // These should also be copied over since asyncResp is a 500 746 EXPECT_EQ(asyncResp->res.resultInt(), 200); 747 EXPECT_EQ(asyncResp->res.jsonValue["@odata.id"], "/redfish/v1"); 748 EXPECT_EQ(asyncResp->res.jsonValue["@odata.type"], 749 "#ServiceRoot.v1_11_0.ServiceRoot"); 750 EXPECT_EQ(asyncResp->res.jsonValue["Id"], "RootService"); 751 EXPECT_EQ(asyncResp->res.jsonValue["Name"], "Root Service"); 752 753 EXPECT_EQ(asyncResp->res.jsonValue["Fabrics"]["@odata.id"], 754 "/redfish/v1/Fabrics"); 755 EXPECT_EQ(asyncResp->res.jsonValue["TelemetryService"]["@odata.id"], 756 "/redfish/v1/TelemetryService"); 757 EXPECT_EQ(asyncResp->res.jsonValue["UpdateService"]["@odata.id"], 758 "/redfish/v1/UpdateService"); 759 EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test")); 760 EXPECT_FALSE(asyncResp->res.jsonValue.contains("error")); 761 762 // These fields should still be present 763 EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"], 764 "/redfish/v1/Chassis"); 765 EXPECT_EQ(asyncResp->res.jsonValue["Fake"]["@odata.id"], 766 "/redfish/v1/Fake"); 767 } 768 769 TEST(processContainsSubordinateResponse, noValidLinks) 770 { 771 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 772 asyncResp->res.result(500); 773 asyncResp->res.jsonValue["Chassis"]["@odata.id"] = "/redfish/v1/Chassis"; 774 775 crow::Response resp; 776 resp.result(200); 777 nlohmann::json jsonValue; 778 resp.addHeader("Content-Type", "application/json"); 779 jsonValue["@odata.id"] = "/redfish/v1"; 780 resp.write( 781 jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace)); 782 783 RedfishAggregator::processContainsSubordinateResponse("prefix", asyncResp, 784 resp); 785 786 // We won't add any links from response so asyncResp shouldn't change 787 EXPECT_EQ(asyncResp->res.resultInt(), 500); 788 EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"], 789 "/redfish/v1/Chassis"); 790 EXPECT_FALSE(asyncResp->res.jsonValue.contains("@odata.id")); 791 792 // Sat response is non-500 so it shouldn't get copied over 793 asyncResp->res.result(200); 794 resp.result(500); 795 jsonValue["Fabrics"]["@odata.id"] = "/redfish/v1/Fabrics"; 796 jsonValue["Test"]["@odata.id"] = "/redfish/v1/Test"; 797 jsonValue["TelemetryService"]["@odata.id"] = "/redfish/v1/TelemetryService"; 798 jsonValue["UpdateService"]["@odata.id"] = "/redfish/v1/UpdateService"; 799 resp.write( 800 jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace)); 801 802 RedfishAggregator::processContainsSubordinateResponse("prefix", asyncResp, 803 resp); 804 805 EXPECT_EQ(asyncResp->res.resultInt(), 200); 806 EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"], 807 "/redfish/v1/Chassis"); 808 EXPECT_FALSE(asyncResp->res.jsonValue.contains("@odata.id")); 809 EXPECT_FALSE(asyncResp->res.jsonValue.contains("Fabrics")); 810 EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test")); 811 EXPECT_FALSE(asyncResp->res.jsonValue.contains("TelemetryService")); 812 EXPECT_FALSE(asyncResp->res.jsonValue.contains("UpdateService")); 813 } 814 815 } // namespace 816 } // namespace redfish 817