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 (auto const& 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     json singleTreeNode = R"({"Foo" : {"@odata.id": "/foobar"}})"_json;
656 
657     // Parsing as the root should net one entry
658     EXPECT_THAT(findNavigationReferences(ExpandType::Both, singleTreeNode),
659                 UnorderedElementsAre(
660                     ExpandNode{json::json_pointer("/Foo"), "/foobar"}));
661 
662     // Parsing in Non-hyperlinks mode should net one entry
663     EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, singleTreeNode),
664                 UnorderedElementsAre(
665                     ExpandNode{json::json_pointer("/Foo"), "/foobar"}));
666 
667     // Searching for not types should return empty set
668     EXPECT_TRUE(
669         findNavigationReferences(ExpandType::None, singleTreeNode).empty());
670 
671     // Searching for hyperlinks only should return empty set
672     EXPECT_TRUE(
673         findNavigationReferences(ExpandType::Links, singleTreeNode).empty());
674 
675     json multiTreeNodes =
676         R"({"Links": {"@odata.id": "/links"}, "Foo" : {"@odata.id": "/foobar"}})"_json;
677     // Should still find Foo
678     EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, multiTreeNodes),
679                 UnorderedElementsAre(
680                     ExpandNode{json::json_pointer("/Foo"), "/foobar"}));
681 }
682 
683 TEST(QueryParams, FindNavigationReferencesLink)
684 {
685     using nlohmann::json;
686 
687     json singleLinkNode =
688         R"({"Links" : {"Sessions": {"@odata.id": "/foobar"}}})"_json;
689 
690     // Parsing as the root should net one entry
691     EXPECT_THAT(findNavigationReferences(ExpandType::Both, singleLinkNode),
692                 UnorderedElementsAre(ExpandNode{
693                     json::json_pointer("/Links/Sessions"), "/foobar"}));
694     // Parsing in hyperlinks mode should net one entry
695     EXPECT_THAT(findNavigationReferences(ExpandType::Links, singleLinkNode),
696                 UnorderedElementsAre(ExpandNode{
697                     json::json_pointer("/Links/Sessions"), "/foobar"}));
698 
699     // Searching for not types should return empty set
700     EXPECT_TRUE(
701         findNavigationReferences(ExpandType::None, singleLinkNode).empty());
702 
703     // Searching for non-hyperlinks only should return empty set
704     EXPECT_TRUE(
705         findNavigationReferences(ExpandType::NotLinks, singleLinkNode).empty());
706 }
707 
708 } // namespace
709 } // namespace redfish::query_param
710