1 #include "bmcweb_config.h" 2 3 #include "utils/query_param.hpp" 4 5 #include <boost/system/result.hpp> 6 #include <boost/url/url_view.hpp> 7 #include <nlohmann/json.hpp> 8 9 #include <new> 10 #include <span> 11 12 #include <gmock/gmock.h> // IWYU pragma: keep 13 #include <gtest/gtest.h> // IWYU pragma: keep 14 15 // IWYU pragma: no_include <gtest/gtest-message.h> 16 // IWYU pragma: no_include <gtest/gtest-test-part.h> 17 // IWYU pragma: no_include "gtest/gtest_pred_impl.h" 18 // IWYU pragma: no_include <boost/url/impl/url_view.hpp> 19 // IWYU pragma: no_include <gmock/gmock-matchers.h> 20 // IWYU pragma: no_include <gtest/gtest-matchers.h> 21 22 namespace redfish::query_param 23 { 24 namespace 25 { 26 27 using ::testing::UnorderedElementsAre; 28 29 TEST(Delegate, OnlyPositive) 30 { 31 Query query{ 32 .isOnly = true, 33 }; 34 QueryCapabilities capabilities{ 35 .canDelegateOnly = true, 36 }; 37 Query delegated = delegate(capabilities, query); 38 EXPECT_TRUE(delegated.isOnly); 39 EXPECT_FALSE(query.isOnly); 40 } 41 42 TEST(Delegate, ExpandPositive) 43 { 44 Query query{ 45 .isOnly = false, 46 .expandLevel = 5, 47 .expandType = ExpandType::Both, 48 }; 49 QueryCapabilities capabilities{ 50 .canDelegateExpandLevel = 3, 51 }; 52 Query delegated = delegate(capabilities, query); 53 EXPECT_FALSE(delegated.isOnly); 54 EXPECT_EQ(delegated.expandLevel, capabilities.canDelegateExpandLevel); 55 EXPECT_EQ(delegated.expandType, ExpandType::Both); 56 EXPECT_EQ(query.expandLevel, 2); 57 } 58 59 TEST(Delegate, OnlyNegative) 60 { 61 Query query{ 62 .isOnly = true, 63 }; 64 QueryCapabilities capabilities{ 65 .canDelegateOnly = false, 66 }; 67 Query delegated = delegate(capabilities, query); 68 EXPECT_FALSE(delegated.isOnly); 69 EXPECT_EQ(query.isOnly, true); 70 } 71 72 TEST(Delegate, ExpandNegative) 73 { 74 Query query{ 75 .isOnly = false, 76 .expandType = ExpandType::None, 77 }; 78 Query delegated = delegate(QueryCapabilities{}, query); 79 EXPECT_EQ(delegated.expandType, ExpandType::None); 80 } 81 82 TEST(Delegate, TopNegative) 83 { 84 Query query{ 85 .top = 42, 86 }; 87 Query delegated = delegate(QueryCapabilities{}, query); 88 EXPECT_EQ(delegated.top, std::nullopt); 89 EXPECT_EQ(query.top, 42); 90 } 91 92 TEST(Delegate, TopPositive) 93 { 94 Query query{ 95 .top = 42, 96 }; 97 QueryCapabilities capabilities{ 98 .canDelegateTop = true, 99 }; 100 Query delegated = delegate(capabilities, query); 101 EXPECT_EQ(delegated.top, 42); 102 EXPECT_EQ(query.top, std::nullopt); 103 } 104 105 TEST(Delegate, SkipNegative) 106 { 107 Query query{ 108 .skip = 42, 109 }; 110 Query delegated = delegate(QueryCapabilities{}, query); 111 EXPECT_EQ(delegated.skip, std::nullopt); 112 EXPECT_EQ(query.skip, 42); 113 } 114 115 TEST(Delegate, SkipPositive) 116 { 117 Query query{ 118 .skip = 42, 119 }; 120 QueryCapabilities capabilities{ 121 .canDelegateSkip = true, 122 }; 123 Query delegated = delegate(capabilities, query); 124 EXPECT_EQ(delegated.skip, 42); 125 EXPECT_EQ(query.skip, 0); 126 } 127 128 TEST(FormatQueryForExpand, NoSubQueryWhenQueryIsEmpty) 129 { 130 EXPECT_EQ(formatQueryForExpand(Query{}), ""); 131 } 132 133 TEST(FormatQueryForExpand, NoSubQueryWhenExpandLevelsLeOne) 134 { 135 EXPECT_EQ(formatQueryForExpand( 136 Query{.expandLevel = 1, .expandType = ExpandType::Both}), 137 ""); 138 EXPECT_EQ(formatQueryForExpand(Query{.expandType = ExpandType::Links}), ""); 139 EXPECT_EQ(formatQueryForExpand(Query{.expandType = ExpandType::NotLinks}), 140 ""); 141 } 142 143 TEST(FormatQueryForExpand, NoSubQueryWhenExpandTypeIsNone) 144 { 145 EXPECT_EQ(formatQueryForExpand( 146 Query{.expandLevel = 2, .expandType = ExpandType::None}), 147 ""); 148 } 149 150 TEST(FormatQueryForExpand, DelegatedSubQueriesHaveSameTypeAndOneLessLevels) 151 { 152 EXPECT_EQ(formatQueryForExpand( 153 Query{.expandLevel = 3, .expandType = ExpandType::Both}), 154 "?$expand=*($levels=2)"); 155 EXPECT_EQ(formatQueryForExpand( 156 Query{.expandLevel = 4, .expandType = ExpandType::Links}), 157 "?$expand=~($levels=3)"); 158 EXPECT_EQ(formatQueryForExpand( 159 Query{.expandLevel = 2, .expandType = ExpandType::NotLinks}), 160 "?$expand=.($levels=1)"); 161 } 162 163 TEST(IsSelectedPropertyAllowed, NotAllowedCharactersReturnsFalse) 164 { 165 EXPECT_FALSE(isSelectedPropertyAllowed("?")); 166 EXPECT_FALSE(isSelectedPropertyAllowed("!")); 167 EXPECT_FALSE(isSelectedPropertyAllowed("-")); 168 EXPECT_FALSE(isSelectedPropertyAllowed("/")); 169 } 170 171 TEST(IsSelectedPropertyAllowed, EmptyStringReturnsFalse) 172 { 173 EXPECT_FALSE(isSelectedPropertyAllowed("")); 174 } 175 176 TEST(IsSelectedPropertyAllowed, TooLongStringReturnsFalse) 177 { 178 std::string strUnderTest = "ab"; 179 // 2^10 180 for (int i = 0; i < 10; ++i) 181 { 182 strUnderTest += strUnderTest; 183 } 184 EXPECT_FALSE(isSelectedPropertyAllowed(strUnderTest)); 185 } 186 187 TEST(IsSelectedPropertyAllowed, ValidPropertReturnsTrue) 188 { 189 EXPECT_TRUE(isSelectedPropertyAllowed("Chassis")); 190 EXPECT_TRUE(isSelectedPropertyAllowed("@odata.type")); 191 EXPECT_TRUE(isSelectedPropertyAllowed("#ComputerSystem.Reset")); 192 EXPECT_TRUE(isSelectedPropertyAllowed( 193 "BootSourceOverrideTarget@Redfish.AllowableValues")); 194 } 195 196 TEST(GetSelectParam, EmptyValueReturnsError) 197 { 198 Query query; 199 EXPECT_FALSE(getSelectParam("", query)); 200 } 201 202 TEST(GetSelectParam, EmptyPropertyReturnsError) 203 { 204 Query query; 205 EXPECT_FALSE(getSelectParam(",", query)); 206 EXPECT_FALSE(getSelectParam(",,", query)); 207 } 208 209 TEST(GetSelectParam, InvalidPathPropertyReturnsError) 210 { 211 Query query; 212 EXPECT_FALSE(getSelectParam("\0,\0", query)); 213 EXPECT_FALSE(getSelectParam("%%%", query)); 214 } 215 216 TEST(GetSelectParam, TrieNodesRespectAllProperties) 217 { 218 Query query; 219 ASSERT_TRUE(getSelectParam("foo/bar,bar", query)); 220 ASSERT_FALSE(query.selectTrie.root.empty()); 221 222 const SelectTrieNode* child = query.selectTrie.root.find("foo"); 223 ASSERT_NE(child, nullptr); 224 EXPECT_FALSE(child->isSelected()); 225 ASSERT_NE(child->find("bar"), nullptr); 226 EXPECT_TRUE(child->find("bar")->isSelected()); 227 228 ASSERT_NE(query.selectTrie.root.find("bar"), nullptr); 229 EXPECT_TRUE(query.selectTrie.root.find("bar")->isSelected()); 230 } 231 232 SelectTrie getTrie(std::span<std::string_view> properties) 233 { 234 SelectTrie trie; 235 for (const auto& property : properties) 236 { 237 EXPECT_TRUE(trie.insertNode(property)); 238 } 239 return trie; 240 } 241 242 TEST(RecursiveSelect, ExpectedKeysAreSelectInSimpleObject) 243 { 244 std::vector<std::string_view> properties = {"SelectMe"}; 245 SelectTrie trie = getTrie(properties); 246 nlohmann::json root = R"({"SelectMe" : "foo", "OmitMe" : "bar"})"_json; 247 nlohmann::json expected = R"({"SelectMe" : "foo"})"_json; 248 recursiveSelect(root, trie.root); 249 EXPECT_EQ(root, expected); 250 } 251 252 TEST(RecursiveSelect, ExpectedKeysAreSelectInNestedObject) 253 { 254 std::vector<std::string_view> properties = { 255 "SelectMe", "Prefix0/ExplicitSelectMe", "Prefix1", "Prefix2", 256 "Prefix4/ExplicitSelectMe"}; 257 SelectTrie trie = getTrie(properties); 258 nlohmann::json root = R"( 259 { 260 "SelectMe":[ 261 "foo" 262 ], 263 "OmitMe":"bar", 264 "Prefix0":{ 265 "ExplicitSelectMe":"123", 266 "OmitMe":"456" 267 }, 268 "Prefix1":{ 269 "ImplicitSelectMe":"123" 270 }, 271 "Prefix2":[ 272 { 273 "ImplicitSelectMe":"123" 274 } 275 ], 276 "Prefix3":[ 277 "OmitMe" 278 ], 279 "Prefix4":[ 280 { 281 "ExplicitSelectMe":"123", 282 "OmitMe": "456" 283 } 284 ] 285 } 286 )"_json; 287 nlohmann::json expected = R"( 288 { 289 "SelectMe":[ 290 "foo" 291 ], 292 "Prefix0":{ 293 "ExplicitSelectMe":"123" 294 }, 295 "Prefix1":{ 296 "ImplicitSelectMe":"123" 297 }, 298 "Prefix2":[ 299 { 300 "ImplicitSelectMe":"123" 301 } 302 ], 303 "Prefix4":[ 304 { 305 "ExplicitSelectMe":"123" 306 } 307 ] 308 } 309 )"_json; 310 recursiveSelect(root, trie.root); 311 EXPECT_EQ(root, expected); 312 } 313 314 TEST(RecursiveSelect, ReservedPropertiesAreSelected) 315 { 316 nlohmann::json root = R"( 317 { 318 "OmitMe":"bar", 319 "@odata.id":1, 320 "@odata.type":2, 321 "@odata.context":3, 322 "@odata.etag":4, 323 "Prefix1":{ 324 "OmitMe":"bar", 325 "@odata.id":1, 326 "ExplicitSelectMe": 1 327 }, 328 "Prefix2":[1, 2, 3], 329 "Prefix3":[ 330 { 331 "OmitMe":"bar", 332 "@odata.id":1, 333 "ExplicitSelectMe": 1 334 } 335 ] 336 } 337 )"_json; 338 nlohmann::json expected = R"( 339 { 340 "@odata.id":1, 341 "@odata.type":2, 342 "@odata.context":3, 343 "@odata.etag":4, 344 "Prefix1":{ 345 "@odata.id":1, 346 "ExplicitSelectMe": 1 347 }, 348 "Prefix3":[ 349 { 350 "@odata.id":1, 351 "ExplicitSelectMe": 1 352 } 353 ] 354 } 355 )"_json; 356 auto ret = boost::urls::parse_relative_ref( 357 "/redfish/v1?$select=Prefix1/ExplicitSelectMe,Prefix3/ExplicitSelectMe"); 358 ASSERT_TRUE(ret); 359 crow::Response res; 360 std::optional<Query> query = parseParameters(ret->params(), res); 361 362 ASSERT_NE(query, std::nullopt); 363 recursiveSelect(root, query->selectTrie.root); 364 EXPECT_EQ(root, expected); 365 } 366 367 TEST(PropogateErrorCode, 500IsWorst) 368 { 369 constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400, 370 401, 500, 501}; 371 for (auto code : codes) 372 { 373 EXPECT_EQ(propogateErrorCode(500, code), 500); 374 EXPECT_EQ(propogateErrorCode(code, 500), 500); 375 } 376 } 377 378 TEST(PropogateErrorCode, 5xxAreWorseThanOthers) 379 { 380 constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400, 381 401, 501, 502}; 382 for (auto code : codes) 383 { 384 EXPECT_EQ(propogateErrorCode(code, 505), 505); 385 EXPECT_EQ(propogateErrorCode(505, code), 505); 386 } 387 EXPECT_EQ(propogateErrorCode(502, 501), 502); 388 EXPECT_EQ(propogateErrorCode(501, 502), 502); 389 EXPECT_EQ(propogateErrorCode(503, 502), 503); 390 } 391 392 TEST(PropogateErrorCode, 401IsWorseThanOthers) 393 { 394 constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400, 401}; 395 for (auto code : codes) 396 { 397 EXPECT_EQ(propogateErrorCode(code, 401), 401); 398 EXPECT_EQ(propogateErrorCode(401, code), 401); 399 } 400 } 401 402 TEST(PropogateErrorCode, 4xxIsWorseThanOthers) 403 { 404 constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400, 402}; 405 for (auto code : codes) 406 { 407 EXPECT_EQ(propogateErrorCode(code, 405), 405); 408 EXPECT_EQ(propogateErrorCode(405, code), 405); 409 } 410 EXPECT_EQ(propogateErrorCode(400, 402), 402); 411 EXPECT_EQ(propogateErrorCode(402, 403), 403); 412 EXPECT_EQ(propogateErrorCode(403, 402), 403); 413 } 414 415 TEST(PropogateError, IntermediateNoErrorMessageMakesNoChange) 416 { 417 crow::Response intermediate; 418 intermediate.result(boost::beast::http::status::ok); 419 420 crow::Response finalRes; 421 finalRes.result(boost::beast::http::status::ok); 422 propogateError(finalRes, intermediate); 423 EXPECT_EQ(finalRes.result(), boost::beast::http::status::ok); 424 EXPECT_EQ(finalRes.jsonValue.find("error"), finalRes.jsonValue.end()); 425 } 426 427 TEST(PropogateError, ErrorsArePropergatedWithErrorInRoot) 428 { 429 nlohmann::json root = R"( 430 { 431 "@odata.type": "#Message.v1_1_1.Message", 432 "Message": "The request failed due to an internal service error. The service is still operational.", 433 "MessageArgs": [], 434 "MessageId": "Base.1.13.0.InternalError", 435 "MessageSeverity": "Critical", 436 "Resolution": "Resubmit the request. If the problem persists, consider resetting the service." 437 } 438 )"_json; 439 crow::Response intermediate; 440 intermediate.result(boost::beast::http::status::internal_server_error); 441 intermediate.jsonValue = root; 442 443 crow::Response final; 444 final.result(boost::beast::http::status::ok); 445 446 propogateError(final, intermediate); 447 448 EXPECT_EQ(final.jsonValue["error"]["code"].get<std::string>(), 449 "Base.1.13.0.InternalError"); 450 EXPECT_EQ( 451 final.jsonValue["error"]["message"].get<std::string>(), 452 "The request failed due to an internal service error. The service is still operational."); 453 EXPECT_EQ(intermediate.jsonValue, R"({})"_json); 454 EXPECT_EQ(final.result(), 455 boost::beast::http::status::internal_server_error); 456 } 457 458 TEST(PropogateError, ErrorsArePropergatedWithErrorCode) 459 { 460 crow::Response intermediate; 461 intermediate.result(boost::beast::http::status::internal_server_error); 462 463 nlohmann::json error = R"( 464 { 465 "error": { 466 "@Message.ExtendedInfo": [], 467 "code": "Base.1.13.0.InternalError", 468 "message": "The request failed due to an internal service error. The service is still operational." 469 } 470 } 471 )"_json; 472 nlohmann::json extendedInfo = R"( 473 { 474 "@odata.type": "#Message.v1_1_1.Message", 475 "Message": "The request failed due to an internal service error. The service is still operational.", 476 "MessageArgs": [], 477 "MessageId": "Base.1.13.0.InternalError", 478 "MessageSeverity": "Critical", 479 "Resolution": "Resubmit the request. If the problem persists, consider resetting the service." 480 } 481 )"_json; 482 483 for (int i = 0; i < 10; ++i) 484 { 485 error["error"][messages::messageAnnotation].push_back(extendedInfo); 486 } 487 intermediate.jsonValue = error; 488 crow::Response final; 489 final.result(boost::beast::http::status::ok); 490 491 propogateError(final, intermediate); 492 EXPECT_EQ(final.jsonValue["error"][messages::messageAnnotation], 493 error["error"][messages::messageAnnotation]); 494 std::string errorCode = messages::messageVersionPrefix; 495 errorCode += "GeneralError"; 496 std::string errorMessage = 497 "A general error has occurred. See Resolution for " 498 "information on how to resolve the error."; 499 EXPECT_EQ(final.jsonValue["error"]["code"].get<std::string>(), errorCode); 500 EXPECT_EQ(final.jsonValue["error"]["message"].get<std::string>(), 501 errorMessage); 502 EXPECT_EQ(intermediate.jsonValue, R"({})"_json); 503 EXPECT_EQ(final.result(), 504 boost::beast::http::status::internal_server_error); 505 } 506 507 TEST(QueryParams, ParseParametersOnly) 508 { 509 auto ret = boost::urls::parse_relative_ref("/redfish/v1?only"); 510 ASSERT_TRUE(ret); 511 512 crow::Response res; 513 std::optional<Query> query = parseParameters(ret->params(), res); 514 ASSERT_TRUE(query != std::nullopt); 515 EXPECT_TRUE(query->isOnly); 516 } 517 518 TEST(QueryParams, ParseParametersExpand) 519 { 520 auto ret = boost::urls::parse_relative_ref("/redfish/v1?$expand=*"); 521 ASSERT_TRUE(ret); 522 523 crow::Response res; 524 525 std::optional<Query> query = parseParameters(ret->params(), res); 526 if constexpr (bmcwebInsecureEnableQueryParams) 527 { 528 ASSERT_NE(query, std::nullopt); 529 EXPECT_TRUE(query->expandType == 530 redfish::query_param::ExpandType::Both); 531 } 532 else 533 { 534 ASSERT_EQ(query, std::nullopt); 535 } 536 } 537 538 TEST(QueryParams, ParseParametersTop) 539 { 540 auto ret = boost::urls::parse_relative_ref("/redfish/v1?$top=1"); 541 ASSERT_TRUE(ret); 542 543 crow::Response res; 544 545 std::optional<Query> query = parseParameters(ret->params(), res); 546 ASSERT_TRUE(query != std::nullopt); 547 EXPECT_EQ(query->top, 1); 548 } 549 550 TEST(QueryParams, ParseParametersTopOutOfRangeNegative) 551 { 552 auto ret = boost::urls::parse_relative_ref("/redfish/v1?$top=-1"); 553 ASSERT_TRUE(ret); 554 555 crow::Response res; 556 557 std::optional<Query> query = parseParameters(ret->params(), res); 558 ASSERT_TRUE(query == std::nullopt); 559 } 560 561 TEST(QueryParams, ParseParametersTopOutOfRangePositive) 562 { 563 auto ret = boost::urls::parse_relative_ref("/redfish/v1?$top=1001"); 564 ASSERT_TRUE(ret); 565 566 crow::Response res; 567 568 std::optional<Query> query = parseParameters(ret->params(), res); 569 ASSERT_TRUE(query == std::nullopt); 570 } 571 572 TEST(QueryParams, ParseParametersSkip) 573 { 574 auto ret = boost::urls::parse_relative_ref("/redfish/v1?$skip=1"); 575 ASSERT_TRUE(ret); 576 577 crow::Response res; 578 579 std::optional<Query> query = parseParameters(ret->params(), res); 580 ASSERT_TRUE(query != std::nullopt); 581 EXPECT_EQ(query->skip, 1); 582 } 583 TEST(QueryParams, ParseParametersSkipOutOfRange) 584 { 585 auto ret = boost::urls::parse_relative_ref( 586 "/redfish/v1?$skip=99999999999999999999"); 587 ASSERT_TRUE(ret); 588 589 crow::Response res; 590 591 std::optional<Query> query = parseParameters(ret->params(), res); 592 ASSERT_EQ(query, std::nullopt); 593 } 594 595 TEST(QueryParams, ParseParametersUnexpectedGetsIgnored) 596 { 597 auto ret = boost::urls::parse_relative_ref("/redfish/v1?unexpected_param"); 598 ASSERT_TRUE(ret); 599 600 crow::Response res; 601 602 std::optional<Query> query = parseParameters(ret->params(), res); 603 ASSERT_TRUE(query != std::nullopt); 604 } 605 606 TEST(QueryParams, ParseParametersUnexpectedDollarGetsError) 607 { 608 auto ret = boost::urls::parse_relative_ref("/redfish/v1?$unexpected_param"); 609 ASSERT_TRUE(ret); 610 611 crow::Response res; 612 613 std::optional<Query> query = parseParameters(ret->params(), res); 614 ASSERT_TRUE(query == std::nullopt); 615 EXPECT_EQ(res.result(), boost::beast::http::status::not_implemented); 616 } 617 618 TEST(QueryParams, GetExpandType) 619 { 620 Query query{}; 621 622 EXPECT_FALSE(getExpandType("", query)); 623 EXPECT_FALSE(getExpandType(".(", query)); 624 EXPECT_FALSE(getExpandType(".()", query)); 625 EXPECT_FALSE(getExpandType(".($levels=1", query)); 626 627 EXPECT_TRUE(getExpandType("*", query)); 628 EXPECT_EQ(query.expandType, ExpandType::Both); 629 EXPECT_TRUE(getExpandType(".", query)); 630 EXPECT_EQ(query.expandType, ExpandType::NotLinks); 631 EXPECT_TRUE(getExpandType("~", query)); 632 EXPECT_EQ(query.expandType, ExpandType::Links); 633 634 // Per redfish specification, level defaults to 1 635 EXPECT_TRUE(getExpandType(".", query)); 636 EXPECT_EQ(query.expandLevel, 1); 637 638 EXPECT_TRUE(getExpandType(".($levels=42)", query)); 639 EXPECT_EQ(query.expandLevel, 42); 640 641 // Overflow 642 EXPECT_FALSE(getExpandType(".($levels=256)", query)); 643 644 // Negative 645 EXPECT_FALSE(getExpandType(".($levels=-1)", query)); 646 647 // No number 648 EXPECT_FALSE(getExpandType(".($levels=a)", query)); 649 } 650 651 TEST(QueryParams, FindNavigationReferencesNonLink) 652 { 653 using nlohmann::json; 654 655 // Responses must include their "@odata.id" property for $expand to work 656 // correctly 657 json singleTreeNode = 658 R"({"@odata.id": "/redfish/v1", 659 "Foo" : {"@odata.id": "/foobar"}})"_json; 660 661 // Parsing as the root should net one entry 662 EXPECT_THAT(findNavigationReferences(ExpandType::Both, 1, singleTreeNode), 663 UnorderedElementsAre( 664 ExpandNode{json::json_pointer("/Foo"), "/foobar"})); 665 666 // Parsing in Non-hyperlinks mode should net one entry 667 EXPECT_THAT( 668 findNavigationReferences(ExpandType::NotLinks, 1, singleTreeNode), 669 UnorderedElementsAre( 670 ExpandNode{json::json_pointer("/Foo"), "/foobar"})); 671 672 // Searching for not types should return empty set 673 EXPECT_TRUE( 674 findNavigationReferences(ExpandType::None, 1, singleTreeNode).empty()); 675 676 // Searching for hyperlinks only should return empty set 677 EXPECT_TRUE( 678 findNavigationReferences(ExpandType::Links, 1, singleTreeNode).empty()); 679 680 // Responses must include their "@odata.id" property for $expand to work 681 // correctly 682 json multiTreeNodes = 683 R"({"@odata.id": "/redfish/v1", 684 "Links": {"@odata.id": "/links"}, 685 "Foo" : {"@odata.id": "/foobar"}})"_json; 686 687 // Should still find Foo 688 EXPECT_THAT( 689 findNavigationReferences(ExpandType::NotLinks, 1, multiTreeNodes), 690 UnorderedElementsAre( 691 ExpandNode{json::json_pointer("/Foo"), "/foobar"})); 692 } 693 694 TEST(QueryParams, FindNavigationReferencesLink) 695 { 696 using nlohmann::json; 697 698 // Responses must include their "@odata.id" property for $expand to work 699 // correctly 700 json singleLinkNode = 701 R"({"@odata.id": "/redfish/v1", 702 "Links" : {"Sessions": {"@odata.id": "/foobar"}}})"_json; 703 704 // Parsing as the root should net one entry 705 EXPECT_THAT(findNavigationReferences(ExpandType::Both, 1, singleLinkNode), 706 UnorderedElementsAre(ExpandNode{ 707 json::json_pointer("/Links/Sessions"), "/foobar"})); 708 // Parsing in hyperlinks mode should net one entry 709 EXPECT_THAT(findNavigationReferences(ExpandType::Links, 1, singleLinkNode), 710 UnorderedElementsAre(ExpandNode{ 711 json::json_pointer("/Links/Sessions"), "/foobar"})); 712 713 // Searching for not types should return empty set 714 EXPECT_TRUE( 715 findNavigationReferences(ExpandType::None, 1, singleLinkNode).empty()); 716 717 // Searching for non-hyperlinks only should return empty set 718 EXPECT_TRUE( 719 findNavigationReferences(ExpandType::NotLinks, 1, singleLinkNode) 720 .empty()); 721 } 722 723 TEST(QueryParams, PreviouslyExpanded) 724 { 725 using nlohmann::json; 726 727 // Responses must include their "@odata.id" property for $expand to work 728 // correctly 729 json expNode = json::parse(R"( 730 { 731 "@odata.id": "/redfish/v1/Chassis", 732 "@odata.type": "#ChassisCollection.ChassisCollection", 733 "Members": [ 734 { 735 "@odata.id": "/redfish/v1/Chassis/5B247A_Sat1", 736 "@odata.type": "#Chassis.v1_17_0.Chassis", 737 "Sensors": { 738 "@odata.id": "/redfish/v1/Chassis/5B247A_Sat1/Sensors" 739 } 740 }, 741 { 742 "@odata.id": "/redfish/v1/Chassis/5B247A_Sat2", 743 "@odata.type": "#Chassis.v1_17_0.Chassis", 744 "Sensors": { 745 "@odata.id": "/redfish/v1/Chassis/5B247A_Sat2/Sensors" 746 } 747 } 748 ], 749 "Members@odata.count": 2, 750 "Name": "Chassis Collection" 751 } 752 )", 753 nullptr, false); 754 755 // Expand has already occurred so we should not do anything 756 EXPECT_TRUE( 757 findNavigationReferences(ExpandType::NotLinks, 1, expNode).empty()); 758 759 // Previous expand was only a single level so we should further expand 760 EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, 2, expNode), 761 UnorderedElementsAre( 762 ExpandNode{json::json_pointer("/Members/0/Sensors"), 763 "/redfish/v1/Chassis/5B247A_Sat1/Sensors"}, 764 ExpandNode{json::json_pointer("/Members/1/Sensors"), 765 "/redfish/v1/Chassis/5B247A_Sat2/Sensors"})); 766 767 // Make sure we can handle when an array was expanded further down the tree 768 json expNode2 = R"({"@odata.id" : "/redfish/v1"})"_json; 769 expNode2["Chassis"] = std::move(expNode); 770 EXPECT_TRUE( 771 findNavigationReferences(ExpandType::NotLinks, 1, expNode2).empty()); 772 EXPECT_TRUE( 773 findNavigationReferences(ExpandType::NotLinks, 2, expNode2).empty()); 774 775 // Previous expand was two levels so we should further expand 776 EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, 3, expNode2), 777 UnorderedElementsAre( 778 ExpandNode{json::json_pointer("/Chassis/Members/0/Sensors"), 779 "/redfish/v1/Chassis/5B247A_Sat1/Sensors"}, 780 ExpandNode{json::json_pointer("/Chassis/Members/1/Sensors"), 781 "/redfish/v1/Chassis/5B247A_Sat2/Sensors"})); 782 } 783 784 TEST(QueryParams, PartiallyPreviouslyExpanded) 785 { 786 using nlohmann::json; 787 788 // Responses must include their "@odata.id" property for $expand to work 789 // correctly 790 json expNode = json::parse(R"( 791 { 792 "@odata.id": "/redfish/v1/Chassis", 793 "@odata.type": "#ChassisCollection.ChassisCollection", 794 "Members": [ 795 { 796 "@odata.id": "/redfish/v1/Chassis/Local" 797 }, 798 { 799 "@odata.id": "/redfish/v1/Chassis/5B247A_Sat1", 800 "@odata.type": "#Chassis.v1_17_0.Chassis", 801 "Sensors": { 802 "@odata.id": "/redfish/v1/Chassis/5B247A_Sat1/Sensors" 803 } 804 } 805 ], 806 "Members@odata.count": 2, 807 "Name": "Chassis Collection" 808 } 809 )", 810 nullptr, false); 811 812 // The 5B247A_Sat1 Chassis was already expanded a single level so we should 813 // only want to expand the Local Chassis 814 EXPECT_THAT( 815 findNavigationReferences(ExpandType::NotLinks, 1, expNode), 816 UnorderedElementsAre(ExpandNode{json::json_pointer("/Members/0"), 817 "/redfish/v1/Chassis/Local"})); 818 819 // The 5B247A_Sat1 Chassis was already expanded a single level so we should 820 // further expand it as well as the Local Chassis 821 EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, 2, expNode), 822 UnorderedElementsAre( 823 ExpandNode{json::json_pointer("/Members/0"), 824 "/redfish/v1/Chassis/Local"}, 825 ExpandNode{json::json_pointer("/Members/1/Sensors"), 826 "/redfish/v1/Chassis/5B247A_Sat1/Sensors"})); 827 828 // Now the response has paths that have been expanded 0, 1, and 2 times 829 json expNode2 = R"({"@odata.id" : "/redfish/v1", 830 "Systems": {"@odata.id": "/redfish/v1/Systems"}})"_json; 831 expNode2["Chassis"] = std::move(expNode); 832 833 EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, 1, expNode2), 834 UnorderedElementsAre(ExpandNode{json::json_pointer("/Systems"), 835 "/redfish/v1/Systems"})); 836 837 EXPECT_THAT( 838 findNavigationReferences(ExpandType::NotLinks, 2, expNode2), 839 UnorderedElementsAre( 840 ExpandNode{json::json_pointer("/Systems"), "/redfish/v1/Systems"}, 841 ExpandNode{json::json_pointer("/Chassis/Members/0"), 842 "/redfish/v1/Chassis/Local"})); 843 844 EXPECT_THAT( 845 findNavigationReferences(ExpandType::NotLinks, 3, expNode2), 846 UnorderedElementsAre( 847 ExpandNode{json::json_pointer("/Systems"), "/redfish/v1/Systems"}, 848 ExpandNode{json::json_pointer("/Chassis/Members/0"), 849 "/redfish/v1/Chassis/Local"}, 850 ExpandNode{json::json_pointer("/Chassis/Members/1/Sensors"), 851 "/redfish/v1/Chassis/5B247A_Sat1/Sensors"})); 852 } 853 854 } // namespace 855 } // namespace redfish::query_param 856