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