1 #include "async_resp.hpp"
2 #include "error_messages.hpp"
3 #include "http_response.hpp"
4 #include "redfish_aggregator.hpp"
5
6 #include <boost/beast/http/field.hpp>
7 #include <boost/beast/http/status.hpp>
8 #include <nlohmann/json.hpp>
9
10 #include <array>
11 #include <memory>
12 #include <string>
13 #include <utility>
14
15 #include <gtest/gtest.h>
16
17 namespace redfish
18 {
19 namespace
20 {
21
TEST(IsPropertyUri,SupportedPropertyReturnsTrue)22 TEST(IsPropertyUri, SupportedPropertyReturnsTrue)
23 {
24 EXPECT_TRUE(isPropertyUri("@Redfish.ActionInfo"));
25 EXPECT_TRUE(isPropertyUri("@odata.id"));
26 EXPECT_TRUE(isPropertyUri("Image"));
27 EXPECT_TRUE(isPropertyUri("MetricProperty"));
28 EXPECT_TRUE(isPropertyUri("TaskMonitor"));
29 EXPECT_TRUE(isPropertyUri("target"));
30 }
31
TEST(IsPropertyUri,CaseInsensitiveURIReturnsTrue)32 TEST(IsPropertyUri, CaseInsensitiveURIReturnsTrue)
33 {
34 EXPECT_TRUE(isPropertyUri("AdditionalDataURI"));
35 EXPECT_TRUE(isPropertyUri("DataSourceUri"));
36 EXPECT_TRUE(isPropertyUri("uri"));
37 EXPECT_TRUE(isPropertyUri("URI"));
38 }
39
TEST(IsPropertyUri,SpeificallyIgnoredPropertyReturnsFalse)40 TEST(IsPropertyUri, SpeificallyIgnoredPropertyReturnsFalse)
41 {
42 EXPECT_FALSE(isPropertyUri("@odata.context"));
43 EXPECT_FALSE(isPropertyUri("Destination"));
44 EXPECT_FALSE(isPropertyUri("HostName"));
45 EXPECT_FALSE(isPropertyUri("OriginOfCondition"));
46 }
47
TEST(IsPropertyUri,UnsupportedPropertyReturnsFalse)48 TEST(IsPropertyUri, UnsupportedPropertyReturnsFalse)
49 {
50 EXPECT_FALSE(isPropertyUri("Name"));
51 EXPECT_FALSE(isPropertyUri("Health"));
52 EXPECT_FALSE(isPropertyUri("Id"));
53 }
54
TEST(addPrefixToItem,ValidURIs)55 TEST(addPrefixToItem, ValidURIs)
56 {
57 nlohmann::json jsonRequest;
58 constexpr std::array validRoots{
59 "Cables",
60 "Chassis",
61 "Fabrics",
62 "PowerEquipment/FloorPDUs",
63 "Systems",
64 "TaskService/Tasks",
65 "TaskService/TaskMonitors",
66 "TelemetryService/LogService/Entries",
67 "UpdateService/SoftwareInventory"};
68
69 // We're only testing prefix fixing so it's alright that some of the
70 // resulting URIs will not actually be possible as defined by the schema
71 constexpr std::array validIDs{
72 "1",
73 "1/",
74 "Test",
75 "Test/",
76 "Extra_Test",
77 "Extra_Test/",
78 "Extra_Test/Sensors",
79 "Extra_Test/Sensors/",
80 "Extra_Test/Sensors/power_sensor",
81 "Extra_Test/Sensors/power_sensor/"};
82
83 // Construct URIs which should have prefix fixing applied
84 for (const auto& root : validRoots)
85 {
86 for (const auto& id : validIDs)
87 {
88 std::string initial("/redfish/v1/" + std::string(root) + "/");
89 std::string correct(initial + "asdfjkl_" + std::string(id));
90 initial += id;
91 jsonRequest["@odata.id"] = initial;
92 addPrefixToItem(jsonRequest["@odata.id"], "asdfjkl");
93 EXPECT_EQ(jsonRequest["@odata.id"], correct);
94 }
95 }
96 }
97
TEST(addPrefixToItem,UnsupportedURIs)98 TEST(addPrefixToItem, UnsupportedURIs)
99 {
100 nlohmann::json jsonRequest;
101 constexpr std::array invalidRoots{
102 "FakeCollection", "JsonSchemas",
103 "PowerEquipment", "TaskService",
104 "TelemetryService/Entries", "UpdateService"};
105
106 constexpr std::array validIDs{
107 "1",
108 "1/",
109 "Test",
110 "Test/",
111 "Extra_Test",
112 "Extra_Test/",
113 "Extra_Test/Sensors",
114 "Extra_Test/Sensors/",
115 "Extra_Test/Sensors/power_sensor",
116 "Extra_Test/Sensors/power_sensor/"};
117
118 // Construct URIs which should NOT have prefix fixing applied
119 for (const auto& root : invalidRoots)
120 {
121 for (const auto& id : validIDs)
122 {
123 std::string initial("/redfish/v1/" + std::string(root) + "/");
124 initial += id;
125 jsonRequest["@odata.id"] = initial;
126 addPrefixToItem(jsonRequest["@odata.id"], "asdfjkl");
127 EXPECT_EQ(jsonRequest["@odata.id"], initial);
128 }
129 }
130 }
131
TEST(addPrefixToItem,TopLevelCollections)132 TEST(addPrefixToItem, TopLevelCollections)
133 {
134 nlohmann::json jsonRequest;
135 constexpr std::array validRoots{
136 "Cables",
137 "Chassis/",
138 "Fabrics",
139 "JsonSchemas",
140 "PowerEquipment/FloorPDUs",
141 "Systems",
142 "TaskService/Tasks",
143 "TelemetryService/LogService/Entries",
144 "TelemetryService/LogService/Entries/",
145 "UpdateService/SoftwareInventory/"};
146
147 // Construct URIs for top level collections. Prefixes should NOT be
148 // applied to any of the URIs
149 for (const auto& root : validRoots)
150 {
151 std::string initial("/redfish/v1/" + std::string(root));
152 jsonRequest["@odata.id"] = initial;
153 addPrefixToItem(jsonRequest["@odata.id"], "prefix");
154 EXPECT_EQ(jsonRequest["@odata.id"], initial);
155 }
156 }
157
TEST(addPrefixes,ParseJsonObject)158 TEST(addPrefixes, ParseJsonObject)
159 {
160 nlohmann::json parameter;
161 parameter["Name"] = "/redfish/v1/Chassis/fakeName";
162 parameter["@odata.id"] = "/redfish/v1/Chassis/fakeChassis";
163
164 addPrefixes(parameter, "abcd");
165 EXPECT_EQ(parameter["Name"], "/redfish/v1/Chassis/fakeName");
166 EXPECT_EQ(parameter["@odata.id"], "/redfish/v1/Chassis/abcd_fakeChassis");
167 }
168
TEST(addPrefixes,ParseJsonArray)169 TEST(addPrefixes, ParseJsonArray)
170 {
171 nlohmann::json array = nlohmann::json::parse(R"(
172 {
173 "Conditions": [
174 {
175 "Message": "This is a test",
176 "@odata.id": "/redfish/v1/Chassis/TestChassis"
177 },
178 {
179 "Message": "This is also a test",
180 "@odata.id": "/redfish/v1/Chassis/TestChassis2"
181 }
182 ]
183 }
184 )",
185 nullptr, false);
186
187 addPrefixes(array, "5B42");
188 EXPECT_EQ(array["Conditions"][0]["@odata.id"],
189 "/redfish/v1/Chassis/5B42_TestChassis");
190 EXPECT_EQ(array["Conditions"][1]["@odata.id"],
191 "/redfish/v1/Chassis/5B42_TestChassis2");
192 }
193
TEST(addPrefixes,ParseJsonObjectNestedArray)194 TEST(addPrefixes, ParseJsonObjectNestedArray)
195 {
196 nlohmann::json objWithArray = nlohmann::json::parse(R"(
197 {
198 "Status": {
199 "Conditions": [
200 {
201 "Message": "This is a test",
202 "MessageId": "Test",
203 "OriginOfCondition": {
204 "@odata.id": "/redfish/v1/Chassis/TestChassis"
205 },
206 "Severity": "Critical"
207 }
208 ],
209 "Health": "Critical",
210 "State": "Enabled"
211 }
212 }
213 )",
214 nullptr, false);
215
216 addPrefixes(objWithArray, "5B42");
217 nlohmann::json& array = objWithArray["Status"]["Conditions"];
218 EXPECT_EQ(array[0]["OriginOfCondition"]["@odata.id"],
219 "/redfish/v1/Chassis/5B42_TestChassis");
220 }
221
TEST(addPrefixes,FixHttpTaskMonitor)222 TEST(addPrefixes, FixHttpTaskMonitor)
223 {
224 // Previously bmcweb hosted task monitors incorrectly.
225 // It has been corrected in the next test, but ensure that the "old"
226 // way still produces the correct result.
227 nlohmann::json taskResp = R"(
228 {
229 "TaskMonitor": "/redfish/v1/TaskService/Tasks/0/Monitor"
230 }
231 )"_json;
232
233 addPrefixes(taskResp, "5B247A");
234 EXPECT_EQ(taskResp["TaskMonitor"],
235 "/redfish/v1/TaskService/Tasks/5B247A_0/Monitor");
236 }
237
TEST(addPrefixes,FixHttpHeadersInResponseBody)238 TEST(addPrefixes, FixHttpHeadersInResponseBody)
239 {
240 nlohmann::json taskResp = nlohmann::json::parse(R"(
241 {
242 "@odata.id": "/redfish/v1/TaskService/Tasks/0",
243 "Name": "Task 0",
244 "Payload": {
245 "HttpHeaders": [
246 "User-Agent: curl/7.87.0",
247 "Accept: */*",
248 "Host: 127.127.12.7",
249 "Content-Length: 33",
250 "Location: /redfish/v1/Managers/bmc/LogServices/Dump/Entries/0"
251 ]
252 },
253 "PercentComplete": 100,
254 "TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/0",
255 "TaskState": "Completed",
256 "TaskStatus": "OK"
257 }
258 )",
259 nullptr, false);
260
261 addPrefixes(taskResp, "5B247A");
262 EXPECT_EQ(taskResp["@odata.id"], "/redfish/v1/TaskService/Tasks/5B247A_0");
263 EXPECT_EQ(taskResp["TaskMonitor"],
264 "/redfish/v1/TaskService/TaskMonitors/5B247A_0");
265 nlohmann::json& httpHeaders = taskResp["Payload"]["HttpHeaders"];
266 EXPECT_EQ(
267 httpHeaders[4],
268 "Location: /redfish/v1/Managers/5B247A_bmc/LogServices/Dump/Entries/0");
269 }
270
271 // Attempts to perform prefix fixing on a response with response code "result".
272 // Fixing should always occur
273 void assertProcessResponse(unsigned result)
274 {
275 nlohmann::json jsonResp;
276 jsonResp["@odata.id"] = "/redfish/v1/Chassis/TestChassis";
277 jsonResp["Name"] = "Test";
278
279 crow::Response resp;
280 resp.write(
281 jsonResp.dump(2, ' ', true, nlohmann::json::error_handler_t::replace));
282 resp.addHeader("Content-Type", "application/json");
283 resp.addHeader("Allow", "GET");
284 resp.addHeader("Location", "/redfish/v1/Chassis/TestChassis");
285 resp.addHeader("Link", "</redfish/v1/Test.json>; rel=describedby");
286 resp.addHeader("Retry-After", "120");
287 resp.result(result);
288
289 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
290 RedfishAggregator::processResponse("prefix", asyncResp, resp);
291
292 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"),
293 "application/json");
294 EXPECT_EQ(asyncResp->res.getHeaderValue("Allow"), "GET");
295 EXPECT_EQ(asyncResp->res.getHeaderValue("Location"),
296 "/redfish/v1/Chassis/prefix_TestChassis");
297 EXPECT_EQ(asyncResp->res.getHeaderValue("Link"), "");
298 EXPECT_EQ(asyncResp->res.getHeaderValue("Retry-After"), "120");
299
300 EXPECT_EQ(asyncResp->res.jsonValue["Name"], "Test");
301 EXPECT_EQ(asyncResp->res.jsonValue["@odata.id"],
302 "/redfish/v1/Chassis/prefix_TestChassis");
303 EXPECT_EQ(asyncResp->res.resultInt(), result);
304 }
305
306 TEST(processResponse, validResponseCodes)
307 {
308 assertProcessResponse(100);
309 assertProcessResponse(200);
310 assertProcessResponse(204);
311 assertProcessResponse(300);
312 assertProcessResponse(404);
313 assertProcessResponse(405);
314 assertProcessResponse(500);
315 assertProcessResponse(507);
316 }
317
318 TEST(processResponse, preserveHeaders)
319 {
320 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
321 asyncResp->res.addHeader("OData-Version", "4.0");
322 asyncResp->res.result(boost::beast::http::status::ok);
323
324 crow::Response resp;
325 resp.addHeader("OData-Version", "3.0");
326 resp.addHeader(boost::beast::http::field::location,
327 "/redfish/v1/Chassis/Test");
328 resp.result(boost::beast::http::status::too_many_requests); // 429
329
330 RedfishAggregator::processResponse("prefix", asyncResp, resp);
331 EXPECT_EQ(asyncResp->res.resultInt(), 429);
332 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0");
333 EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), "");
334
335 asyncResp->res.result(boost::beast::http::status::ok);
336 resp.result(boost::beast::http::status::bad_gateway); // 502
337
338 RedfishAggregator::processResponse("prefix", asyncResp, resp);
339 EXPECT_EQ(asyncResp->res.resultInt(), 502);
340 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0");
341 EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), "");
342 }
343
344 // Helper function to correctly populate a ComputerSystem collection response
345 void populateCollectionResponse(crow::Response& resp)
346 {
347 nlohmann::json jsonResp = nlohmann::json::parse(R"(
348 {
349 "@odata.id": "/redfish/v1/Systems",
350 "@odata.type": "#ComputerSystemCollection.ComputerSystemCollection",
351 "Members": [
352 {
353 "@odata.id": "/redfish/v1/Systems/system"
354 }
355 ],
356 "Members@odata.count": 1,
357 "Name": "Computer System Collection"
358 }
359 )",
360 nullptr, false);
361
362 resp.clear();
363 // resp.body() =
364 // jsonResp.dump(2, ' ', true,
365 // nlohmann::json::error_handler_t::replace);
366 resp.jsonValue = std::move(jsonResp);
367 resp.addHeader("OData-Version", "4.0");
368 resp.addHeader("Content-Type", "application/json");
369 resp.result(boost::beast::http::status::ok);
370 }
371
372 void populateCollectionNotFound(crow::Response& resp)
373 {
374 resp.clear();
375 resp.addHeader("OData-Version", "4.0");
376 resp.result(boost::beast::http::status::not_found);
377 }
378
379 // Used with the above functions to convert the response to appear like it's
380 // from a satellite which will not have a json component
381 void convertToSat(crow::Response& resp)
382 {
383 resp.write(resp.jsonValue.dump(2, ' ', true,
384 nlohmann::json::error_handler_t::replace));
385 resp.jsonValue.clear();
386 }
387
388 TEST(processCollectionResponse, localOnly)
389 {
390 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
391 crow::Response resp;
392 populateCollectionResponse(asyncResp->res);
393 populateCollectionNotFound(resp);
394
395 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp);
396 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0");
397 EXPECT_EQ(asyncResp->res.resultInt(), 200);
398 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"),
399 "application/json");
400 EXPECT_EQ(asyncResp->res.jsonValue["Members@odata.count"], 1);
401 for (auto& member : asyncResp->res.jsonValue["Members"])
402 {
403 // There should only be one member
404 EXPECT_EQ(member["@odata.id"], "/redfish/v1/Systems/system");
405 }
406 }
407
408 TEST(processCollectionResponse, satelliteOnly)
409 {
410 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
411 crow::Response resp;
412 populateCollectionNotFound(asyncResp->res);
413 populateCollectionResponse(resp);
414 convertToSat(resp);
415
416 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp);
417 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0");
418 EXPECT_EQ(asyncResp->res.resultInt(), 200);
419 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"),
420 "application/json");
421 EXPECT_EQ(asyncResp->res.jsonValue["Members@odata.count"], 1);
422 for (auto& member : asyncResp->res.jsonValue["Members"])
423 {
424 // There should only be one member
425 EXPECT_EQ(member["@odata.id"], "/redfish/v1/Systems/prefix_system");
426 }
427 }
428
429 TEST(processCollectionResponse, bothExist)
430 {
431 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
432 crow::Response resp;
433 populateCollectionResponse(asyncResp->res);
434 populateCollectionResponse(resp);
435 convertToSat(resp);
436
437 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp);
438 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0");
439 EXPECT_EQ(asyncResp->res.resultInt(), 200);
440 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"),
441 "application/json");
442 EXPECT_EQ(asyncResp->res.jsonValue["Members@odata.count"], 2);
443
444 bool foundLocal = false;
445 bool foundSat = false;
446 for (const auto& member : asyncResp->res.jsonValue["Members"])
447 {
448 if (member["@odata.id"] == "/redfish/v1/Systems/system")
449 {
450 foundLocal = true;
451 }
452 else if (member["@odata.id"] == "/redfish/v1/Systems/prefix_system")
453 {
454 foundSat = true;
455 }
456 }
457 EXPECT_TRUE(foundLocal);
458 EXPECT_TRUE(foundSat);
459 }
460
461 TEST(processCollectionResponse, satelliteWrongContentHeader)
462 {
463 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
464 crow::Response resp;
465 populateCollectionResponse(asyncResp->res);
466 populateCollectionResponse(resp);
467 convertToSat(resp);
468
469 // Ignore the satellite even though otherwise valid
470 resp.clearHeader(boost::beast::http::field::content_type);
471
472 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp);
473 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0");
474 EXPECT_EQ(asyncResp->res.resultInt(), 200);
475 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"),
476 "application/json");
477 EXPECT_EQ(asyncResp->res.jsonValue["Members@odata.count"], 1);
478 for (auto& member : asyncResp->res.jsonValue["Members"])
479 {
480 EXPECT_EQ(member["@odata.id"], "/redfish/v1/Systems/system");
481 }
482 }
483
484 TEST(processCollectionResponse, neitherExist)
485 {
486 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
487 crow::Response resp;
488 populateCollectionNotFound(asyncResp->res);
489 populateCollectionNotFound(resp);
490 convertToSat(resp);
491
492 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp);
493 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0");
494 EXPECT_EQ(asyncResp->res.resultInt(), 404);
495 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), "");
496 }
497
498 TEST(processCollectionResponse, preserveHeaders)
499 {
500 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
501 crow::Response resp;
502 populateCollectionNotFound(asyncResp->res);
503 populateCollectionResponse(resp);
504 convertToSat(resp);
505
506 resp.addHeader("OData-Version", "3.0");
507 resp.addHeader(boost::beast::http::field::location,
508 "/redfish/v1/Chassis/Test");
509
510 // We skip processing collection responses that have a 429 or 502 code
511 resp.result(boost::beast::http::status::too_many_requests); // 429
512 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp);
513 EXPECT_EQ(asyncResp->res.resultInt(), 404);
514 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0");
515 EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), "");
516
517 resp.result(boost::beast::http::status::bad_gateway); // 502
518 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp);
519 EXPECT_EQ(asyncResp->res.resultInt(), 404);
520 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0");
521 EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), "");
522 }
523 void assertProcessResponseContentType(std::string_view contentType)
524 {
525 crow::Response resp;
526 resp.write("responseBody");
527 resp.addHeader("Content-Type", contentType);
528 resp.addHeader("Location", "/redfish/v1/Chassis/TestChassis");
529 resp.addHeader("Link", "metadataLink");
530 resp.addHeader("Retry-After", "120");
531
532 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
533 RedfishAggregator::processResponse("prefix", asyncResp, resp);
534 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), contentType);
535 EXPECT_EQ(asyncResp->res.getHeaderValue("Location"),
536 "/redfish/v1/Chassis/prefix_TestChassis");
537 EXPECT_EQ(asyncResp->res.getHeaderValue("Link"), "");
538 EXPECT_EQ(asyncResp->res.getHeaderValue("Retry-After"), "120");
539 EXPECT_EQ(*asyncResp->res.body(), "responseBody");
540 }
541
542 TEST(processResponse, DifferentContentType)
543 {
544 assertProcessResponseContentType("application/xml");
545 assertProcessResponseContentType("application/yaml");
546 assertProcessResponseContentType("text/event-stream");
547 assertProcessResponseContentType(";charset=utf-8");
548 }
549
550 bool containsSubordinateCollection(const std::string_view uri)
551 {
552 return searchCollectionsArray(uri, SearchType::ContainsSubordinate);
553 }
554
555 bool containsCollection(const std::string_view uri)
556 {
557 return searchCollectionsArray(uri, SearchType::Collection);
558 }
559
560 bool isCollOrCon(const std::string_view uri)
561 {
562 return searchCollectionsArray(uri, SearchType::CollOrCon);
563 }
564
565 TEST(searchCollectionsArray, containsSubordinateValidURIs)
566 {
567 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1"));
568 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/"));
569 EXPECT_TRUE(
570 containsSubordinateCollection("/redfish/v1/AggregationService"));
571 EXPECT_TRUE(
572 containsSubordinateCollection("/redfish/v1/CompositionService/"));
573 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/JobService"));
574 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/JobService/Log"));
575 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/KeyService"));
576 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/LicenseService/"));
577 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/PowerEquipment"));
578 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/TaskService"));
579 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/TelemetryService"));
580 EXPECT_TRUE(containsSubordinateCollection(
581 "/redfish/v1/TelemetryService/LogService/"));
582 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/UpdateService"));
583
584 EXPECT_TRUE(containsSubordinateCollection(
585 "/redfish/v1/UpdateService?$expand=.($levels=1)"));
586 }
587
588 TEST(searchCollectionsArray, containsSubordinateInvalidURIs)
589 {
590 EXPECT_FALSE(containsSubordinateCollection(""));
591 EXPECT_FALSE(containsSubordinateCollection("http://"));
592 EXPECT_FALSE(containsSubordinateCollection("/redfish"));
593 EXPECT_FALSE(containsSubordinateCollection("/redfish/"));
594 EXPECT_FALSE(containsSubordinateCollection("/redfish//"));
595 EXPECT_FALSE(containsSubordinateCollection("/redfish/v1//"));
596 EXPECT_FALSE(containsSubordinateCollection("/redfish/v11"));
597 EXPECT_FALSE(containsSubordinateCollection("/redfish/v11/"));
598 EXPECT_FALSE(containsSubordinateCollection("www.test.com/redfish/v1"));
599 EXPECT_FALSE(containsSubordinateCollection("/fail"));
600 EXPECT_FALSE(containsSubordinateCollection(
601 "/redfish/v1/AggregationService/Aggregates"));
602 EXPECT_FALSE(containsSubordinateCollection(
603 "/redfish/v1/AggregationService/AggregationSources/"));
604 EXPECT_FALSE(containsSubordinateCollection("/redfish/v1/Cables/"));
605 EXPECT_FALSE(
606 containsSubordinateCollection("/redfish/v1/Chassis/chassisId"));
607 EXPECT_FALSE(containsSubordinateCollection("/redfish/v1/Fake"));
608 EXPECT_FALSE(
609 containsSubordinateCollection("/redfish/v1/TelemetryService//"));
610 EXPECT_FALSE(containsSubordinateCollection(
611 "/redfish/v1/TelemetryService/LogService/Entries"));
612 EXPECT_FALSE(containsSubordinateCollection(
613 "/redfish/v1/UpdateService/SoftwareInventory/"));
614 EXPECT_FALSE(containsSubordinateCollection(
615 "/redfish/v1/UpdateService/SoftwareInventory/Te"));
616 EXPECT_FALSE(containsSubordinateCollection(
617 "/redfish/v1/UpdateService/SoftwareInventory2"));
618 }
619
620 TEST(searchCollectionsArray, collectionURIs)
621 {
622 EXPECT_TRUE(containsCollection("/redfish/v1/Chassis"));
623 EXPECT_TRUE(containsCollection("/redfish/v1/Chassis/"));
624 EXPECT_TRUE(containsCollection("/redfish/v1/Managers"));
625 EXPECT_TRUE(containsCollection("/redfish/v1/Systems"));
626 EXPECT_TRUE(
627 containsCollection("/redfish/v1/TelemetryService/LogService/Entries"));
628 EXPECT_TRUE(
629 containsCollection("/redfish/v1/TelemetryService/LogService/Entries/"));
630 EXPECT_TRUE(
631 containsCollection("/redfish/v1/UpdateService/FirmwareInventory"));
632 EXPECT_TRUE(
633 containsCollection("/redfish/v1/UpdateService/FirmwareInventory/"));
634
635 EXPECT_FALSE(containsCollection("http://"));
636 EXPECT_FALSE(containsCollection("/redfish/v11/Chassis"));
637 EXPECT_FALSE(containsCollection("/redfish/v11/Chassis/"));
638 EXPECT_FALSE(containsCollection("/redfish/v1"));
639 EXPECT_FALSE(containsCollection("/redfish/v1/"));
640 EXPECT_FALSE(containsCollection("/redfish/v1//"));
641 EXPECT_FALSE(containsCollection("/redfish/v1/Chassis//"));
642 EXPECT_FALSE(containsCollection("/redfish/v1/Chassis/Test"));
643 EXPECT_FALSE(containsCollection("/redfish/v1/TelemetryService"));
644 EXPECT_FALSE(containsCollection("/redfish/v1/TelemetryService/"));
645 EXPECT_FALSE(containsCollection("/redfish/v1/UpdateService"));
646 EXPECT_FALSE(
647 containsCollection("/redfish/v1/UpdateService/FirmwareInventory/Test"));
648 EXPECT_FALSE(
649 containsCollection("/redfish/v1/UpdateService/FirmwareInventory/Tes/"));
650 EXPECT_FALSE(
651 containsCollection("/redfish/v1/UpdateService/SoftwareInventory/Te"));
652 EXPECT_FALSE(
653 containsCollection("/redfish/v1/UpdateService/SoftwareInventory2"));
654 EXPECT_FALSE(containsCollection("/redfish/v11"));
655 EXPECT_FALSE(containsCollection("/redfish/v11/"));
656 }
657
658 TEST(searchCollectionsArray, collectionOrContainsURIs)
659 {
660 // Resources that are a top level collection or are uptree of one
661 EXPECT_TRUE(isCollOrCon("/redfish/v1/"));
662 EXPECT_TRUE(isCollOrCon("/redfish/v1/AggregationService"));
663 EXPECT_TRUE(isCollOrCon("/redfish/v1/CompositionService/"));
664 EXPECT_TRUE(isCollOrCon("/redfish/v1/Chassis"));
665 EXPECT_TRUE(isCollOrCon("/redfish/v1/Cables/"));
666 EXPECT_TRUE(isCollOrCon("/redfish/v1/Fabrics"));
667 EXPECT_TRUE(isCollOrCon("/redfish/v1/Managers"));
668 EXPECT_TRUE(isCollOrCon("/redfish/v1/UpdateService/FirmwareInventory"));
669 EXPECT_TRUE(isCollOrCon("/redfish/v1/UpdateService/FirmwareInventory/"));
670
671 EXPECT_FALSE(isCollOrCon("http://"));
672 EXPECT_FALSE(isCollOrCon("/redfish/v11"));
673 EXPECT_FALSE(isCollOrCon("/redfish/v11/"));
674 EXPECT_FALSE(isCollOrCon("/redfish/v1/Chassis/Test"));
675 EXPECT_FALSE(isCollOrCon("/redfish/v1/Managers/Test/"));
676 EXPECT_FALSE(isCollOrCon("/redfish/v1/TaskService/Tasks/0"));
677 EXPECT_FALSE(isCollOrCon("/redfish/v1/UpdateService/FirmwareInventory/Te"));
678 EXPECT_FALSE(isCollOrCon("/redfish/v1/UpdateService/SoftwareInventory/Te"));
679 EXPECT_FALSE(isCollOrCon("/redfish/v1/UpdateService/SoftwareInventory2"));
680 }
681
682 TEST(processContainsSubordinateResponse, addLinks)
683 {
684 crow::Response resp;
685 resp.result(200);
686 nlohmann::json jsonValue;
687 resp.addHeader("Content-Type", "application/json");
688 jsonValue["@odata.id"] = "/redfish/v1";
689 jsonValue["Fabrics"]["@odata.id"] = "/redfish/v1/Fabrics";
690 jsonValue["Test"]["@odata.id"] = "/redfish/v1/Test";
691 jsonValue["TelemetryService"]["@odata.id"] = "/redfish/v1/TelemetryService";
692 jsonValue["UpdateService"]["@odata.id"] = "/redfish/v1/UpdateService";
693 resp.write(
694 jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace));
695
696 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
697 asyncResp->res.result(200);
698 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1";
699 asyncResp->res.jsonValue["Chassis"]["@odata.id"] = "/redfish/v1/Chassis";
700
701 RedfishAggregator::processContainsSubordinateResponse(
702 "prefix", asyncResp, resp);
703 EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"],
704 "/redfish/v1/Chassis");
705 EXPECT_EQ(asyncResp->res.jsonValue["Fabrics"]["@odata.id"],
706 "/redfish/v1/Fabrics");
707 EXPECT_EQ(asyncResp->res.jsonValue["TelemetryService"]["@odata.id"],
708 "/redfish/v1/TelemetryService");
709 EXPECT_EQ(asyncResp->res.jsonValue["UpdateService"]["@odata.id"],
710 "/redfish/v1/UpdateService");
711 EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test"));
712 }
713
714 TEST(processContainsSubordinateResponse, localNotOK)
715 {
716 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
717 asyncResp->res.addHeader("Content-Type", "application/json");
718 messages::resourceNotFound(asyncResp->res, "", "");
719
720 // This field was added by resourceNotFound()
721 // Sanity test to make sure it gets removed later
722 EXPECT_TRUE(asyncResp->res.jsonValue.contains("error"));
723
724 crow::Response resp;
725 resp.result(200);
726 nlohmann::json jsonValue;
727 resp.addHeader("Content-Type", "application/json");
728 jsonValue["@odata.id"] = "/redfish/v1";
729 jsonValue["@odata.type"] = "#ServiceRoot.v1_11_0.ServiceRoot";
730 jsonValue["Id"] = "RootService";
731 jsonValue["Name"] = "Root Service";
732 jsonValue["Fabrics"]["@odata.id"] = "/redfish/v1/Fabrics";
733 jsonValue["Test"]["@odata.id"] = "/redfish/v1/Test";
734 jsonValue["TelemetryService"]["@odata.id"] = "/redfish/v1/TelemetryService";
735 jsonValue["UpdateService"]["@odata.id"] = "/redfish/v1/UpdateService";
736 resp.write(
737 jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace));
738
739 RedfishAggregator::processContainsSubordinateResponse(
740 "prefix", asyncResp, resp);
741
742 // Most of the response should get copied over since asyncResp is a 404
743 EXPECT_EQ(asyncResp->res.resultInt(), 200);
744 EXPECT_EQ(asyncResp->res.jsonValue["@odata.id"], "/redfish/v1");
745 EXPECT_EQ(asyncResp->res.jsonValue["@odata.type"],
746 "#ServiceRoot.v1_11_0.ServiceRoot");
747 EXPECT_EQ(asyncResp->res.jsonValue["Id"], "RootService");
748 EXPECT_EQ(asyncResp->res.jsonValue["Name"], "Root Service");
749
750 EXPECT_EQ(asyncResp->res.jsonValue["Fabrics"]["@odata.id"],
751 "/redfish/v1/Fabrics");
752 EXPECT_EQ(asyncResp->res.jsonValue["TelemetryService"]["@odata.id"],
753 "/redfish/v1/TelemetryService");
754 EXPECT_EQ(asyncResp->res.jsonValue["UpdateService"]["@odata.id"],
755 "/redfish/v1/UpdateService");
756 EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test"));
757 EXPECT_FALSE(asyncResp->res.jsonValue.contains("error"));
758
759 // Test for local response being partially populated before throwing error
760 asyncResp = std::make_shared<bmcweb::AsyncResp>();
761 asyncResp->res.addHeader("Content-Type", "application/json");
762 asyncResp->res.jsonValue["Chassis"]["@odata.id"] = "/redfish/v1/Chassis";
763 asyncResp->res.jsonValue["Fake"]["@odata.id"] = "/redfish/v1/Fake";
764 messages::internalError(asyncResp->res);
765
766 RedfishAggregator::processContainsSubordinateResponse(
767 "prefix", asyncResp, resp);
768
769 // These should also be copied over since asyncResp is a 500
770 EXPECT_EQ(asyncResp->res.resultInt(), 200);
771 EXPECT_EQ(asyncResp->res.jsonValue["@odata.id"], "/redfish/v1");
772 EXPECT_EQ(asyncResp->res.jsonValue["@odata.type"],
773 "#ServiceRoot.v1_11_0.ServiceRoot");
774 EXPECT_EQ(asyncResp->res.jsonValue["Id"], "RootService");
775 EXPECT_EQ(asyncResp->res.jsonValue["Name"], "Root Service");
776
777 EXPECT_EQ(asyncResp->res.jsonValue["Fabrics"]["@odata.id"],
778 "/redfish/v1/Fabrics");
779 EXPECT_EQ(asyncResp->res.jsonValue["TelemetryService"]["@odata.id"],
780 "/redfish/v1/TelemetryService");
781 EXPECT_EQ(asyncResp->res.jsonValue["UpdateService"]["@odata.id"],
782 "/redfish/v1/UpdateService");
783 EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test"));
784 EXPECT_FALSE(asyncResp->res.jsonValue.contains("error"));
785
786 // These fields should still be present
787 EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"],
788 "/redfish/v1/Chassis");
789 EXPECT_EQ(asyncResp->res.jsonValue["Fake"]["@odata.id"],
790 "/redfish/v1/Fake");
791 }
792
793 TEST(processContainsSubordinateResponse, noValidLinks)
794 {
795 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
796 asyncResp->res.result(500);
797 asyncResp->res.jsonValue["Chassis"]["@odata.id"] = "/redfish/v1/Chassis";
798
799 crow::Response resp;
800 resp.result(200);
801 nlohmann::json jsonValue;
802 resp.addHeader("Content-Type", "application/json");
803 jsonValue["@odata.id"] = "/redfish/v1";
804 resp.write(
805 jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace));
806
807 RedfishAggregator::processContainsSubordinateResponse(
808 "prefix", asyncResp, resp);
809
810 // We won't add any links from response so asyncResp shouldn't change
811 EXPECT_EQ(asyncResp->res.resultInt(), 500);
812 EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"],
813 "/redfish/v1/Chassis");
814 EXPECT_FALSE(asyncResp->res.jsonValue.contains("@odata.id"));
815
816 // Sat response is non-500 so it shouldn't get copied over
817 asyncResp->res.result(200);
818 resp.result(500);
819 jsonValue["Fabrics"]["@odata.id"] = "/redfish/v1/Fabrics";
820 jsonValue["Test"]["@odata.id"] = "/redfish/v1/Test";
821 jsonValue["TelemetryService"]["@odata.id"] = "/redfish/v1/TelemetryService";
822 jsonValue["UpdateService"]["@odata.id"] = "/redfish/v1/UpdateService";
823 resp.write(
824 jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace));
825
826 RedfishAggregator::processContainsSubordinateResponse(
827 "prefix", asyncResp, resp);
828
829 EXPECT_EQ(asyncResp->res.resultInt(), 200);
830 EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"],
831 "/redfish/v1/Chassis");
832 EXPECT_FALSE(asyncResp->res.jsonValue.contains("@odata.id"));
833 EXPECT_FALSE(asyncResp->res.jsonValue.contains("Fabrics"));
834 EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test"));
835 EXPECT_FALSE(asyncResp->res.jsonValue.contains("TelemetryService"));
836 EXPECT_FALSE(asyncResp->res.jsonValue.contains("UpdateService"));
837 }
838
839 } // namespace
840 } // namespace redfish
841