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