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