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