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