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, 5); 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 ASSERT_TRUE(query); 362 if (!query) 363 { 364 return; 365 } 366 recursiveSelect(root, query.value().selectTrie.root); 367 EXPECT_EQ(root, expected); 368 } 369 370 TEST(PropogateErrorCode, 500IsWorst) 371 { 372 constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400, 373 401, 500, 501}; 374 for (auto code : codes) 375 { 376 EXPECT_EQ(propogateErrorCode(500, code), 500); 377 EXPECT_EQ(propogateErrorCode(code, 500), 500); 378 } 379 } 380 381 TEST(PropogateErrorCode, 5xxAreWorseThanOthers) 382 { 383 constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400, 384 401, 501, 502}; 385 for (auto code : codes) 386 { 387 EXPECT_EQ(propogateErrorCode(code, 505), 505); 388 EXPECT_EQ(propogateErrorCode(505, code), 505); 389 } 390 EXPECT_EQ(propogateErrorCode(502, 501), 502); 391 EXPECT_EQ(propogateErrorCode(501, 502), 502); 392 EXPECT_EQ(propogateErrorCode(503, 502), 503); 393 } 394 395 TEST(PropogateErrorCode, 401IsWorseThanOthers) 396 { 397 constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400, 401}; 398 for (auto code : codes) 399 { 400 EXPECT_EQ(propogateErrorCode(code, 401), 401); 401 EXPECT_EQ(propogateErrorCode(401, code), 401); 402 } 403 } 404 405 TEST(PropogateErrorCode, 4xxIsWorseThanOthers) 406 { 407 constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400, 402}; 408 for (auto code : codes) 409 { 410 EXPECT_EQ(propogateErrorCode(code, 405), 405); 411 EXPECT_EQ(propogateErrorCode(405, code), 405); 412 } 413 EXPECT_EQ(propogateErrorCode(400, 402), 402); 414 EXPECT_EQ(propogateErrorCode(402, 403), 403); 415 EXPECT_EQ(propogateErrorCode(403, 402), 403); 416 } 417 418 TEST(PropogateError, IntermediateNoErrorMessageMakesNoChange) 419 { 420 crow::Response intermediate; 421 intermediate.result(boost::beast::http::status::ok); 422 423 crow::Response finalRes; 424 finalRes.result(boost::beast::http::status::ok); 425 propogateError(finalRes, intermediate); 426 EXPECT_EQ(finalRes.result(), boost::beast::http::status::ok); 427 EXPECT_EQ(finalRes.jsonValue.find("error"), finalRes.jsonValue.end()); 428 } 429 430 TEST(PropogateError, ErrorsArePropergatedWithErrorInRoot) 431 { 432 nlohmann::json root = R"( 433 { 434 "@odata.type": "#Message.v1_1_1.Message", 435 "Message": "The request failed due to an internal service error. The service is still operational.", 436 "MessageArgs": [], 437 "MessageId": "Base.1.13.0.InternalError", 438 "MessageSeverity": "Critical", 439 "Resolution": "Resubmit the request. If the problem persists, consider resetting the service." 440 } 441 )"_json; 442 crow::Response intermediate; 443 intermediate.result(boost::beast::http::status::internal_server_error); 444 intermediate.jsonValue = root; 445 446 crow::Response final; 447 final.result(boost::beast::http::status::ok); 448 449 propogateError(final, intermediate); 450 451 EXPECT_EQ(final.jsonValue["error"]["code"].get<std::string>(), 452 "Base.1.13.0.InternalError"); 453 EXPECT_EQ( 454 final.jsonValue["error"]["message"].get<std::string>(), 455 "The request failed due to an internal service error. The service is still operational."); 456 EXPECT_EQ(intermediate.jsonValue, R"({})"_json); 457 EXPECT_EQ(final.result(), 458 boost::beast::http::status::internal_server_error); 459 } 460 461 TEST(PropogateError, ErrorsArePropergatedWithErrorCode) 462 { 463 crow::Response intermediate; 464 intermediate.result(boost::beast::http::status::internal_server_error); 465 466 nlohmann::json error = R"( 467 { 468 "error": { 469 "@Message.ExtendedInfo": [], 470 "code": "Base.1.13.0.InternalError", 471 "message": "The request failed due to an internal service error. The service is still operational." 472 } 473 } 474 )"_json; 475 nlohmann::json extendedInfo = R"( 476 { 477 "@odata.type": "#Message.v1_1_1.Message", 478 "Message": "The request failed due to an internal service error. The service is still operational.", 479 "MessageArgs": [], 480 "MessageId": "Base.1.13.0.InternalError", 481 "MessageSeverity": "Critical", 482 "Resolution": "Resubmit the request. If the problem persists, consider resetting the service." 483 } 484 )"_json; 485 486 for (int i = 0; i < 10; ++i) 487 { 488 error["error"][messages::messageAnnotation].push_back(extendedInfo); 489 } 490 intermediate.jsonValue = error; 491 crow::Response final; 492 final.result(boost::beast::http::status::ok); 493 494 propogateError(final, intermediate); 495 EXPECT_EQ(final.jsonValue["error"][messages::messageAnnotation], 496 error["error"][messages::messageAnnotation]); 497 std::string errorCode = messages::messageVersionPrefix; 498 errorCode += "GeneralError"; 499 std::string errorMessage = 500 "A general error has occurred. See Resolution for " 501 "information on how to resolve the error."; 502 EXPECT_EQ(final.jsonValue["error"]["code"].get<std::string>(), errorCode); 503 EXPECT_EQ(final.jsonValue["error"]["message"].get<std::string>(), 504 errorMessage); 505 EXPECT_EQ(intermediate.jsonValue, R"({})"_json); 506 EXPECT_EQ(final.result(), 507 boost::beast::http::status::internal_server_error); 508 } 509 510 TEST(QueryParams, ParseParametersOnly) 511 { 512 auto ret = boost::urls::parse_relative_ref("/redfish/v1?only"); 513 ASSERT_TRUE(ret); 514 if (!ret) 515 { 516 return; 517 } 518 519 crow::Response res; 520 std::optional<Query> query = parseParameters(ret->params(), res); 521 ASSERT_TRUE(query); 522 if (!query) 523 { 524 return; 525 } 526 EXPECT_TRUE(query->isOnly); 527 } 528 529 TEST(QueryParams, ParseParametersExpand) 530 { 531 auto ret = boost::urls::parse_relative_ref("/redfish/v1?$expand=*"); 532 ASSERT_TRUE(ret); 533 if (!ret) 534 { 535 return; 536 } 537 538 crow::Response res; 539 540 std::optional<Query> query = parseParameters(ret->params(), res); 541 if constexpr (bmcwebInsecureEnableQueryParams) 542 { 543 ASSERT_TRUE(query); 544 if (!query) 545 { 546 return; 547 } 548 EXPECT_TRUE(query.value().expandType == 549 redfish::query_param::ExpandType::Both); 550 } 551 else 552 { 553 ASSERT_EQ(query, std::nullopt); 554 } 555 } 556 557 TEST(QueryParams, ParseParametersTop) 558 { 559 auto ret = boost::urls::parse_relative_ref("/redfish/v1?$top=1"); 560 ASSERT_TRUE(ret); 561 if (!ret) 562 { 563 return; 564 } 565 566 crow::Response res; 567 568 std::optional<Query> query = parseParameters(ret->params(), res); 569 ASSERT_TRUE(query); 570 if (!query) 571 { 572 return; 573 } 574 EXPECT_EQ(query.value().top, 1); 575 } 576 577 TEST(QueryParams, ParseParametersTopOutOfRangeNegative) 578 { 579 auto ret = boost::urls::parse_relative_ref("/redfish/v1?$top=-1"); 580 ASSERT_TRUE(ret); 581 582 crow::Response res; 583 584 std::optional<Query> query = parseParameters(ret->params(), res); 585 ASSERT_TRUE(query == std::nullopt); 586 } 587 588 TEST(QueryParams, ParseParametersTopOutOfRangePositive) 589 { 590 auto ret = boost::urls::parse_relative_ref("/redfish/v1?$top=1001"); 591 ASSERT_TRUE(ret); 592 if (!ret) 593 { 594 return; 595 } 596 crow::Response res; 597 598 std::optional<Query> query = parseParameters(ret->params(), res); 599 ASSERT_TRUE(query == std::nullopt); 600 } 601 602 TEST(QueryParams, ParseParametersSkip) 603 { 604 auto ret = boost::urls::parse_relative_ref("/redfish/v1?$skip=1"); 605 ASSERT_TRUE(ret); 606 607 crow::Response res; 608 609 std::optional<Query> query = parseParameters(ret->params(), res); 610 ASSERT_TRUE(query); 611 if (!query) 612 { 613 return; 614 } 615 EXPECT_EQ(query.value().skip, 1); 616 } 617 TEST(QueryParams, ParseParametersSkipOutOfRange) 618 { 619 auto ret = boost::urls::parse_relative_ref( 620 "/redfish/v1?$skip=99999999999999999999"); 621 ASSERT_TRUE(ret); 622 623 crow::Response res; 624 625 std::optional<Query> query = parseParameters(ret->params(), res); 626 ASSERT_EQ(query, std::nullopt); 627 } 628 629 TEST(QueryParams, ParseParametersUnexpectedGetsIgnored) 630 { 631 auto ret = boost::urls::parse_relative_ref("/redfish/v1?unexpected_param"); 632 ASSERT_TRUE(ret); 633 634 crow::Response res; 635 636 std::optional<Query> query = parseParameters(ret->params(), res); 637 ASSERT_TRUE(query != std::nullopt); 638 } 639 640 TEST(QueryParams, ParseParametersUnexpectedDollarGetsError) 641 { 642 auto ret = boost::urls::parse_relative_ref("/redfish/v1?$unexpected_param"); 643 ASSERT_TRUE(ret); 644 645 crow::Response res; 646 647 std::optional<Query> query = parseParameters(ret->params(), res); 648 ASSERT_TRUE(query == std::nullopt); 649 EXPECT_EQ(res.result(), boost::beast::http::status::not_implemented); 650 } 651 652 TEST(QueryParams, GetExpandType) 653 { 654 Query query{}; 655 656 EXPECT_FALSE(getExpandType("", query)); 657 EXPECT_FALSE(getExpandType(".(", query)); 658 EXPECT_FALSE(getExpandType(".()", query)); 659 EXPECT_FALSE(getExpandType(".($levels=1", query)); 660 661 EXPECT_TRUE(getExpandType("*", query)); 662 EXPECT_EQ(query.expandType, ExpandType::Both); 663 EXPECT_TRUE(getExpandType(".", query)); 664 EXPECT_EQ(query.expandType, ExpandType::NotLinks); 665 EXPECT_TRUE(getExpandType("~", query)); 666 EXPECT_EQ(query.expandType, ExpandType::Links); 667 668 // Per redfish specification, level defaults to 1 669 EXPECT_TRUE(getExpandType(".", query)); 670 EXPECT_EQ(query.expandLevel, 1); 671 672 EXPECT_TRUE(getExpandType(".($levels=42)", query)); 673 EXPECT_EQ(query.expandLevel, 42); 674 675 // Overflow 676 EXPECT_FALSE(getExpandType(".($levels=256)", query)); 677 678 // Negative 679 EXPECT_FALSE(getExpandType(".($levels=-1)", query)); 680 681 // No number 682 EXPECT_FALSE(getExpandType(".($levels=a)", query)); 683 } 684 685 TEST(QueryParams, FindNavigationReferencesNonLink) 686 { 687 using nlohmann::json; 688 689 // Responses must include their "@odata.id" property for $expand to work 690 // correctly 691 json singleTreeNode = 692 R"({"@odata.id": "/redfish/v1", 693 "Foo" : {"@odata.id": "/foobar"}})"_json; 694 695 // Parsing as the root should net one entry 696 EXPECT_THAT( 697 findNavigationReferences(ExpandType::Both, 1, 0, singleTreeNode), 698 UnorderedElementsAre( 699 ExpandNode{json::json_pointer("/Foo"), "/foobar"})); 700 701 // Parsing in Non-hyperlinks mode should net one entry 702 EXPECT_THAT( 703 findNavigationReferences(ExpandType::NotLinks, 1, 0, singleTreeNode), 704 UnorderedElementsAre( 705 ExpandNode{json::json_pointer("/Foo"), "/foobar"})); 706 707 // Searching for not types should return empty set 708 EXPECT_TRUE(findNavigationReferences(ExpandType::None, 1, 0, singleTreeNode) 709 .empty()); 710 711 // Searching for hyperlinks only should return empty set 712 EXPECT_TRUE( 713 findNavigationReferences(ExpandType::Links, 1, 0, singleTreeNode) 714 .empty()); 715 716 // Responses must include their "@odata.id" property for $expand to work 717 // correctly 718 json multiTreeNodes = 719 R"({"@odata.id": "/redfish/v1", 720 "Links": {"@odata.id": "/links"}, 721 "Foo" : {"@odata.id": "/foobar"}})"_json; 722 723 // Should still find Foo 724 EXPECT_THAT( 725 findNavigationReferences(ExpandType::NotLinks, 1, 0, multiTreeNodes), 726 UnorderedElementsAre( 727 ExpandNode{json::json_pointer("/Foo"), "/foobar"})); 728 } 729 730 TEST(QueryParams, FindNavigationReferencesLink) 731 { 732 using nlohmann::json; 733 734 // Responses must include their "@odata.id" property for $expand to work 735 // correctly 736 json singleLinkNode = 737 R"({"@odata.id": "/redfish/v1", 738 "Links" : {"Sessions": {"@odata.id": "/foobar"}}})"_json; 739 740 // Parsing as the root should net one entry 741 EXPECT_THAT( 742 findNavigationReferences(ExpandType::Both, 1, 0, singleLinkNode), 743 UnorderedElementsAre( 744 ExpandNode{json::json_pointer("/Links/Sessions"), "/foobar"})); 745 // Parsing in hyperlinks mode should net one entry 746 EXPECT_THAT( 747 findNavigationReferences(ExpandType::Links, 1, 0, singleLinkNode), 748 UnorderedElementsAre( 749 ExpandNode{json::json_pointer("/Links/Sessions"), "/foobar"})); 750 751 // Searching for not types should return empty set 752 EXPECT_TRUE(findNavigationReferences(ExpandType::None, 1, 0, singleLinkNode) 753 .empty()); 754 755 // Searching for non-hyperlinks only should return empty set 756 EXPECT_TRUE( 757 findNavigationReferences(ExpandType::NotLinks, 1, 0, singleLinkNode) 758 .empty()); 759 } 760 761 TEST(QueryParams, PreviouslyExpanded) 762 { 763 using nlohmann::json; 764 765 // Responses must include their "@odata.id" property for $expand to work 766 // correctly 767 json expNode = json::parse(R"( 768 { 769 "@odata.id": "/redfish/v1/Chassis", 770 "@odata.type": "#ChassisCollection.ChassisCollection", 771 "Members": [ 772 { 773 "@odata.id": "/redfish/v1/Chassis/5B247A_Sat1", 774 "@odata.type": "#Chassis.v1_17_0.Chassis", 775 "Sensors": { 776 "@odata.id": "/redfish/v1/Chassis/5B247A_Sat1/Sensors" 777 } 778 }, 779 { 780 "@odata.id": "/redfish/v1/Chassis/5B247A_Sat2", 781 "@odata.type": "#Chassis.v1_17_0.Chassis", 782 "Sensors": { 783 "@odata.id": "/redfish/v1/Chassis/5B247A_Sat2/Sensors" 784 } 785 } 786 ], 787 "Members@odata.count": 2, 788 "Name": "Chassis Collection" 789 } 790 )", 791 nullptr, false); 792 793 // Expand has already occurred so we should not do anything 794 EXPECT_TRUE( 795 findNavigationReferences(ExpandType::NotLinks, 1, 0, expNode).empty()); 796 797 // Previous expand was only a single level so we should further expand 798 EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, 2, 0, expNode), 799 UnorderedElementsAre( 800 ExpandNode{json::json_pointer("/Members/0/Sensors"), 801 "/redfish/v1/Chassis/5B247A_Sat1/Sensors"}, 802 ExpandNode{json::json_pointer("/Members/1/Sensors"), 803 "/redfish/v1/Chassis/5B247A_Sat2/Sensors"})); 804 805 // Make sure we can handle when an array was expanded further down the tree 806 json expNode2 = R"({"@odata.id" : "/redfish/v1"})"_json; 807 expNode2["Chassis"] = std::move(expNode); 808 EXPECT_TRUE( 809 findNavigationReferences(ExpandType::NotLinks, 1, 0, expNode2).empty()); 810 EXPECT_TRUE( 811 findNavigationReferences(ExpandType::NotLinks, 2, 0, expNode2).empty()); 812 813 // Previous expand was two levels so we should further expand 814 EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, 3, 0, expNode2), 815 UnorderedElementsAre( 816 ExpandNode{json::json_pointer("/Chassis/Members/0/Sensors"), 817 "/redfish/v1/Chassis/5B247A_Sat1/Sensors"}, 818 ExpandNode{json::json_pointer("/Chassis/Members/1/Sensors"), 819 "/redfish/v1/Chassis/5B247A_Sat2/Sensors"})); 820 } 821 822 TEST(QueryParams, DelegatedSkipExpanded) 823 { 824 using nlohmann::json; 825 826 // Responses must include their "@odata.id" property for $expand to work 827 // correctly 828 json expNode = json::parse(R"( 829 { 830 "@odata.id": "/redfish/v1", 831 "Foo": { 832 "@odata.id": "/foo" 833 }, 834 "Bar": { 835 "@odata.id": "/bar", 836 "Foo": { 837 "@odata.id": "/barfoo" 838 } 839 } 840 } 841 )", 842 nullptr, false); 843 844 EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, 2, 0, expNode), 845 UnorderedElementsAre( 846 ExpandNode{json::json_pointer("/Foo"), "/foo"}, 847 ExpandNode{json::json_pointer("/Bar/Foo"), "/barfoo"})); 848 849 // Skip the first expand level 850 EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, 1, 1, expNode), 851 UnorderedElementsAre( 852 ExpandNode{json::json_pointer("/Bar/Foo"), "/barfoo"})); 853 } 854 855 TEST(QueryParams, PartiallyPreviouslyExpanded) 856 { 857 using nlohmann::json; 858 859 // Responses must include their "@odata.id" property for $expand to work 860 // correctly 861 json expNode = json::parse(R"( 862 { 863 "@odata.id": "/redfish/v1/Chassis", 864 "@odata.type": "#ChassisCollection.ChassisCollection", 865 "Members": [ 866 { 867 "@odata.id": "/redfish/v1/Chassis/Local" 868 }, 869 { 870 "@odata.id": "/redfish/v1/Chassis/5B247A_Sat1", 871 "@odata.type": "#Chassis.v1_17_0.Chassis", 872 "Sensors": { 873 "@odata.id": "/redfish/v1/Chassis/5B247A_Sat1/Sensors" 874 } 875 } 876 ], 877 "Members@odata.count": 2, 878 "Name": "Chassis Collection" 879 } 880 )", 881 nullptr, false); 882 883 // The 5B247A_Sat1 Chassis was already expanded a single level so we should 884 // only want to expand the Local Chassis 885 EXPECT_THAT( 886 findNavigationReferences(ExpandType::NotLinks, 1, 0, expNode), 887 UnorderedElementsAre(ExpandNode{json::json_pointer("/Members/0"), 888 "/redfish/v1/Chassis/Local"})); 889 890 // The 5B247A_Sat1 Chassis was already expanded a single level so we should 891 // further expand it as well as the Local Chassis 892 EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, 2, 0, expNode), 893 UnorderedElementsAre( 894 ExpandNode{json::json_pointer("/Members/0"), 895 "/redfish/v1/Chassis/Local"}, 896 ExpandNode{json::json_pointer("/Members/1/Sensors"), 897 "/redfish/v1/Chassis/5B247A_Sat1/Sensors"})); 898 899 // Now the response has paths that have been expanded 0, 1, and 2 times 900 json expNode2 = R"({"@odata.id" : "/redfish/v1", 901 "Systems": {"@odata.id": "/redfish/v1/Systems"}})"_json; 902 expNode2["Chassis"] = std::move(expNode); 903 904 EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, 1, 0, expNode2), 905 UnorderedElementsAre(ExpandNode{json::json_pointer("/Systems"), 906 "/redfish/v1/Systems"})); 907 908 EXPECT_THAT( 909 findNavigationReferences(ExpandType::NotLinks, 2, 0, expNode2), 910 UnorderedElementsAre( 911 ExpandNode{json::json_pointer("/Systems"), "/redfish/v1/Systems"}, 912 ExpandNode{json::json_pointer("/Chassis/Members/0"), 913 "/redfish/v1/Chassis/Local"})); 914 915 EXPECT_THAT( 916 findNavigationReferences(ExpandType::NotLinks, 3, 0, expNode2), 917 UnorderedElementsAre( 918 ExpandNode{json::json_pointer("/Systems"), "/redfish/v1/Systems"}, 919 ExpandNode{json::json_pointer("/Chassis/Members/0"), 920 "/redfish/v1/Chassis/Local"}, 921 ExpandNode{json::json_pointer("/Chassis/Members/1/Sensors"), 922 "/redfish/v1/Chassis/5B247A_Sat1/Sensors"})); 923 } 924 925 } // namespace 926 } // namespace redfish::query_param 927