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