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 const nlohmann::json::array_t* arr =
404 asyncResp->res.jsonValue["Members"]
405 .get_ptr<const nlohmann::json::array_t*>();
406 for (const auto& member : *arr)
407 {
408 // There should only be one member
409 EXPECT_EQ(member["@odata.id"], "/redfish/v1/Systems/system");
410 }
411 }
412
413 TEST(processCollectionResponse, satelliteOnly)
414 {
415 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
416 crow::Response resp;
417 populateCollectionNotFound(asyncResp->res);
418 populateCollectionResponse(resp);
419 convertToSat(resp);
420
421 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp);
422 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0");
423 EXPECT_EQ(asyncResp->res.resultInt(), 200);
424 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"),
425 "application/json");
426 EXPECT_EQ(asyncResp->res.jsonValue["Members@odata.count"], 1);
427 const nlohmann::json::array_t* arr =
428 asyncResp->res.jsonValue["Members"]
429 .get_ptr<const nlohmann::json::array_t*>();
430 for (const auto& member : *arr)
431 {
432 // There should only be one member
433 EXPECT_EQ(member["@odata.id"], "/redfish/v1/Systems/prefix_system");
434 }
435 }
436
437 TEST(processCollectionResponse, bothExist)
438 {
439 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
440 crow::Response resp;
441 populateCollectionResponse(asyncResp->res);
442 populateCollectionResponse(resp);
443 convertToSat(resp);
444
445 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp);
446 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0");
447 EXPECT_EQ(asyncResp->res.resultInt(), 200);
448 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"),
449 "application/json");
450 EXPECT_EQ(asyncResp->res.jsonValue["Members@odata.count"], 2);
451
452 bool foundLocal = false;
453 bool foundSat = false;
454 const nlohmann::json::array_t* arr =
455 asyncResp->res.jsonValue["Members"]
456 .get_ptr<const nlohmann::json::array_t*>();
457 for (const auto& member : *arr)
458 {
459 if (member["@odata.id"] == "/redfish/v1/Systems/system")
460 {
461 foundLocal = true;
462 }
463 else if (member["@odata.id"] == "/redfish/v1/Systems/prefix_system")
464 {
465 foundSat = true;
466 }
467 }
468 EXPECT_TRUE(foundLocal);
469 EXPECT_TRUE(foundSat);
470 }
471
472 TEST(processCollectionResponse, satelliteWrongContentHeader)
473 {
474 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
475 crow::Response resp;
476 populateCollectionResponse(asyncResp->res);
477 populateCollectionResponse(resp);
478 convertToSat(resp);
479
480 // Ignore the satellite even though otherwise valid
481 resp.clearHeader(boost::beast::http::field::content_type);
482
483 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp);
484 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0");
485 EXPECT_EQ(asyncResp->res.resultInt(), 200);
486 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"),
487 "application/json");
488 EXPECT_EQ(asyncResp->res.jsonValue["Members@odata.count"], 1);
489 const nlohmann::json::array_t* arr =
490 asyncResp->res.jsonValue["Members"]
491 .get_ptr<const nlohmann::json::array_t*>();
492 for (const auto& member : *arr)
493 {
494 EXPECT_EQ(member["@odata.id"], "/redfish/v1/Systems/system");
495 }
496 }
497
498 TEST(processCollectionResponse, neitherExist)
499 {
500 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
501 crow::Response resp;
502 populateCollectionNotFound(asyncResp->res);
503 populateCollectionNotFound(resp);
504 convertToSat(resp);
505
506 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp);
507 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0");
508 EXPECT_EQ(asyncResp->res.resultInt(), 404);
509 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), "");
510 }
511
512 TEST(processCollectionResponse, preserveHeaders)
513 {
514 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
515 crow::Response resp;
516 populateCollectionNotFound(asyncResp->res);
517 populateCollectionResponse(resp);
518 convertToSat(resp);
519
520 resp.addHeader("OData-Version", "3.0");
521 resp.addHeader(boost::beast::http::field::location,
522 "/redfish/v1/Chassis/Test");
523
524 // We skip processing collection responses that have a 429 or 502 code
525 resp.result(boost::beast::http::status::too_many_requests); // 429
526 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp);
527 EXPECT_EQ(asyncResp->res.resultInt(), 404);
528 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0");
529 EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), "");
530
531 resp.result(boost::beast::http::status::bad_gateway); // 502
532 RedfishAggregator::processCollectionResponse("prefix", asyncResp, resp);
533 EXPECT_EQ(asyncResp->res.resultInt(), 404);
534 EXPECT_EQ(asyncResp->res.getHeaderValue("OData-Version"), "4.0");
535 EXPECT_EQ(asyncResp->res.getHeaderValue("Location"), "");
536 }
537 void assertProcessResponseContentType(std::string_view contentType)
538 {
539 crow::Response resp;
540 resp.write("responseBody");
541 resp.addHeader("Content-Type", contentType);
542 resp.addHeader("Location", "/redfish/v1/Chassis/TestChassis");
543 resp.addHeader("Link", "metadataLink");
544 resp.addHeader("Retry-After", "120");
545
546 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
547 RedfishAggregator::processResponse("prefix", asyncResp, resp);
548 EXPECT_EQ(asyncResp->res.getHeaderValue("Content-Type"), contentType);
549 EXPECT_EQ(asyncResp->res.getHeaderValue("Location"),
550 "/redfish/v1/Chassis/prefix_TestChassis");
551 EXPECT_EQ(asyncResp->res.getHeaderValue("Link"), "");
552 EXPECT_EQ(asyncResp->res.getHeaderValue("Retry-After"), "120");
553 EXPECT_EQ(*asyncResp->res.body(), "responseBody");
554 }
555
556 TEST(processResponse, DifferentContentType)
557 {
558 assertProcessResponseContentType("application/xml");
559 assertProcessResponseContentType("application/yaml");
560 assertProcessResponseContentType("text/event-stream");
561 assertProcessResponseContentType(";charset=utf-8");
562 }
563
564 bool containsSubordinateCollection(const std::string_view uri)
565 {
566 return searchCollectionsArray(uri, SearchType::ContainsSubordinate);
567 }
568
569 bool containsCollection(const std::string_view uri)
570 {
571 return searchCollectionsArray(uri, SearchType::Collection);
572 }
573
574 bool isCollOrCon(const std::string_view uri)
575 {
576 return searchCollectionsArray(uri, SearchType::CollOrCon);
577 }
578
579 TEST(searchCollectionsArray, containsSubordinateValidURIs)
580 {
581 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1"));
582 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/"));
583 EXPECT_TRUE(
584 containsSubordinateCollection("/redfish/v1/AggregationService"));
585 EXPECT_TRUE(
586 containsSubordinateCollection("/redfish/v1/CompositionService/"));
587 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/JobService"));
588 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/JobService/Log"));
589 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/KeyService"));
590 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/LicenseService/"));
591 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/PowerEquipment"));
592 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/TaskService"));
593 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/TelemetryService"));
594 EXPECT_TRUE(containsSubordinateCollection(
595 "/redfish/v1/TelemetryService/LogService/"));
596 EXPECT_TRUE(containsSubordinateCollection("/redfish/v1/UpdateService"));
597
598 EXPECT_TRUE(containsSubordinateCollection(
599 "/redfish/v1/UpdateService?$expand=.($levels=1)"));
600 }
601
602 TEST(searchCollectionsArray, containsSubordinateInvalidURIs)
603 {
604 EXPECT_FALSE(containsSubordinateCollection(""));
605 EXPECT_FALSE(containsSubordinateCollection("http://"));
606 EXPECT_FALSE(containsSubordinateCollection("/redfish"));
607 EXPECT_FALSE(containsSubordinateCollection("/redfish/"));
608 EXPECT_FALSE(containsSubordinateCollection("/redfish//"));
609 EXPECT_FALSE(containsSubordinateCollection("/redfish/v1//"));
610 EXPECT_FALSE(containsSubordinateCollection("/redfish/v11"));
611 EXPECT_FALSE(containsSubordinateCollection("/redfish/v11/"));
612 EXPECT_FALSE(containsSubordinateCollection("www.test.com/redfish/v1"));
613 EXPECT_FALSE(containsSubordinateCollection("/fail"));
614 EXPECT_FALSE(containsSubordinateCollection(
615 "/redfish/v1/AggregationService/Aggregates"));
616 EXPECT_FALSE(containsSubordinateCollection(
617 "/redfish/v1/AggregationService/AggregationSources/"));
618 EXPECT_FALSE(containsSubordinateCollection("/redfish/v1/Cables/"));
619 EXPECT_FALSE(
620 containsSubordinateCollection("/redfish/v1/Chassis/chassisId"));
621 EXPECT_FALSE(containsSubordinateCollection("/redfish/v1/Fake"));
622 EXPECT_FALSE(
623 containsSubordinateCollection("/redfish/v1/TelemetryService//"));
624 EXPECT_FALSE(containsSubordinateCollection(
625 "/redfish/v1/TelemetryService/LogService/Entries"));
626 EXPECT_FALSE(containsSubordinateCollection(
627 "/redfish/v1/UpdateService/SoftwareInventory/"));
628 EXPECT_FALSE(containsSubordinateCollection(
629 "/redfish/v1/UpdateService/SoftwareInventory/Te"));
630 EXPECT_FALSE(containsSubordinateCollection(
631 "/redfish/v1/UpdateService/SoftwareInventory2"));
632 }
633
634 TEST(searchCollectionsArray, collectionURIs)
635 {
636 EXPECT_TRUE(containsCollection("/redfish/v1/Chassis"));
637 EXPECT_TRUE(containsCollection("/redfish/v1/Chassis/"));
638 EXPECT_TRUE(containsCollection("/redfish/v1/Managers"));
639 EXPECT_TRUE(containsCollection("/redfish/v1/Systems"));
640 EXPECT_TRUE(
641 containsCollection("/redfish/v1/TelemetryService/LogService/Entries"));
642 EXPECT_TRUE(
643 containsCollection("/redfish/v1/TelemetryService/LogService/Entries/"));
644 EXPECT_TRUE(
645 containsCollection("/redfish/v1/UpdateService/FirmwareInventory"));
646 EXPECT_TRUE(
647 containsCollection("/redfish/v1/UpdateService/FirmwareInventory/"));
648
649 EXPECT_FALSE(containsCollection("http://"));
650 EXPECT_FALSE(containsCollection("/redfish/v11/Chassis"));
651 EXPECT_FALSE(containsCollection("/redfish/v11/Chassis/"));
652 EXPECT_FALSE(containsCollection("/redfish/v1"));
653 EXPECT_FALSE(containsCollection("/redfish/v1/"));
654 EXPECT_FALSE(containsCollection("/redfish/v1//"));
655 EXPECT_FALSE(containsCollection("/redfish/v1/Chassis//"));
656 EXPECT_FALSE(containsCollection("/redfish/v1/Chassis/Test"));
657 EXPECT_FALSE(containsCollection("/redfish/v1/TelemetryService"));
658 EXPECT_FALSE(containsCollection("/redfish/v1/TelemetryService/"));
659 EXPECT_FALSE(containsCollection("/redfish/v1/UpdateService"));
660 EXPECT_FALSE(
661 containsCollection("/redfish/v1/UpdateService/FirmwareInventory/Test"));
662 EXPECT_FALSE(
663 containsCollection("/redfish/v1/UpdateService/FirmwareInventory/Tes/"));
664 EXPECT_FALSE(
665 containsCollection("/redfish/v1/UpdateService/SoftwareInventory/Te"));
666 EXPECT_FALSE(
667 containsCollection("/redfish/v1/UpdateService/SoftwareInventory2"));
668 EXPECT_FALSE(containsCollection("/redfish/v11"));
669 EXPECT_FALSE(containsCollection("/redfish/v11/"));
670 }
671
672 TEST(searchCollectionsArray, collectionOrContainsURIs)
673 {
674 // Resources that are a top level collection or are uptree of one
675 EXPECT_TRUE(isCollOrCon("/redfish/v1/"));
676 EXPECT_TRUE(isCollOrCon("/redfish/v1/AggregationService"));
677 EXPECT_TRUE(isCollOrCon("/redfish/v1/CompositionService/"));
678 EXPECT_TRUE(isCollOrCon("/redfish/v1/Chassis"));
679 EXPECT_TRUE(isCollOrCon("/redfish/v1/Cables/"));
680 EXPECT_TRUE(isCollOrCon("/redfish/v1/Fabrics"));
681 EXPECT_TRUE(isCollOrCon("/redfish/v1/Managers"));
682 EXPECT_TRUE(isCollOrCon("/redfish/v1/UpdateService/FirmwareInventory"));
683 EXPECT_TRUE(isCollOrCon("/redfish/v1/UpdateService/FirmwareInventory/"));
684
685 EXPECT_FALSE(isCollOrCon("http://"));
686 EXPECT_FALSE(isCollOrCon("/redfish/v11"));
687 EXPECT_FALSE(isCollOrCon("/redfish/v11/"));
688 EXPECT_FALSE(isCollOrCon("/redfish/v1/Chassis/Test"));
689 EXPECT_FALSE(isCollOrCon("/redfish/v1/Managers/Test/"));
690 EXPECT_FALSE(isCollOrCon("/redfish/v1/TaskService/Tasks/0"));
691 EXPECT_FALSE(isCollOrCon("/redfish/v1/UpdateService/FirmwareInventory/Te"));
692 EXPECT_FALSE(isCollOrCon("/redfish/v1/UpdateService/SoftwareInventory/Te"));
693 EXPECT_FALSE(isCollOrCon("/redfish/v1/UpdateService/SoftwareInventory2"));
694 }
695
696 TEST(processContainsSubordinateResponse, addLinks)
697 {
698 crow::Response resp;
699 resp.result(200);
700 nlohmann::json jsonValue;
701 resp.addHeader("Content-Type", "application/json");
702 jsonValue["@odata.id"] = "/redfish/v1";
703 jsonValue["Fabrics"]["@odata.id"] = "/redfish/v1/Fabrics";
704 jsonValue["Test"]["@odata.id"] = "/redfish/v1/Test";
705 jsonValue["TelemetryService"]["@odata.id"] = "/redfish/v1/TelemetryService";
706 jsonValue["UpdateService"]["@odata.id"] = "/redfish/v1/UpdateService";
707 resp.write(
708 jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace));
709
710 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
711 asyncResp->res.result(200);
712 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1";
713 asyncResp->res.jsonValue["Chassis"]["@odata.id"] = "/redfish/v1/Chassis";
714
715 RedfishAggregator::processContainsSubordinateResponse(
716 "prefix", asyncResp, resp);
717 EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"],
718 "/redfish/v1/Chassis");
719 EXPECT_EQ(asyncResp->res.jsonValue["Fabrics"]["@odata.id"],
720 "/redfish/v1/Fabrics");
721 EXPECT_EQ(asyncResp->res.jsonValue["TelemetryService"]["@odata.id"],
722 "/redfish/v1/TelemetryService");
723 EXPECT_EQ(asyncResp->res.jsonValue["UpdateService"]["@odata.id"],
724 "/redfish/v1/UpdateService");
725 EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test"));
726 }
727
728 TEST(processContainsSubordinateResponse, localNotOK)
729 {
730 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
731 asyncResp->res.addHeader("Content-Type", "application/json");
732 messages::resourceNotFound(asyncResp->res, "", "");
733
734 // This field was added by resourceNotFound()
735 // Sanity test to make sure it gets removed later
736 EXPECT_TRUE(asyncResp->res.jsonValue.contains("error"));
737
738 crow::Response resp;
739 resp.result(200);
740 nlohmann::json jsonValue;
741 resp.addHeader("Content-Type", "application/json");
742 jsonValue["@odata.id"] = "/redfish/v1";
743 jsonValue["@odata.type"] = "#ServiceRoot.v1_11_0.ServiceRoot";
744 jsonValue["Id"] = "RootService";
745 jsonValue["Name"] = "Root Service";
746 jsonValue["Fabrics"]["@odata.id"] = "/redfish/v1/Fabrics";
747 jsonValue["Test"]["@odata.id"] = "/redfish/v1/Test";
748 jsonValue["TelemetryService"]["@odata.id"] = "/redfish/v1/TelemetryService";
749 jsonValue["UpdateService"]["@odata.id"] = "/redfish/v1/UpdateService";
750 resp.write(
751 jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace));
752
753 RedfishAggregator::processContainsSubordinateResponse(
754 "prefix", asyncResp, resp);
755
756 // Most of the response should get copied over since asyncResp is a 404
757 EXPECT_EQ(asyncResp->res.resultInt(), 200);
758 EXPECT_EQ(asyncResp->res.jsonValue["@odata.id"], "/redfish/v1");
759 EXPECT_EQ(asyncResp->res.jsonValue["@odata.type"],
760 "#ServiceRoot.v1_11_0.ServiceRoot");
761 EXPECT_EQ(asyncResp->res.jsonValue["Id"], "RootService");
762 EXPECT_EQ(asyncResp->res.jsonValue["Name"], "Root Service");
763
764 EXPECT_EQ(asyncResp->res.jsonValue["Fabrics"]["@odata.id"],
765 "/redfish/v1/Fabrics");
766 EXPECT_EQ(asyncResp->res.jsonValue["TelemetryService"]["@odata.id"],
767 "/redfish/v1/TelemetryService");
768 EXPECT_EQ(asyncResp->res.jsonValue["UpdateService"]["@odata.id"],
769 "/redfish/v1/UpdateService");
770 EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test"));
771 EXPECT_FALSE(asyncResp->res.jsonValue.contains("error"));
772
773 // Test for local response being partially populated before throwing error
774 asyncResp = std::make_shared<bmcweb::AsyncResp>();
775 asyncResp->res.addHeader("Content-Type", "application/json");
776 asyncResp->res.jsonValue["Chassis"]["@odata.id"] = "/redfish/v1/Chassis";
777 asyncResp->res.jsonValue["Fake"]["@odata.id"] = "/redfish/v1/Fake";
778 messages::internalError(asyncResp->res);
779
780 RedfishAggregator::processContainsSubordinateResponse(
781 "prefix", asyncResp, resp);
782
783 // These should also be copied over since asyncResp is a 500
784 EXPECT_EQ(asyncResp->res.resultInt(), 200);
785 EXPECT_EQ(asyncResp->res.jsonValue["@odata.id"], "/redfish/v1");
786 EXPECT_EQ(asyncResp->res.jsonValue["@odata.type"],
787 "#ServiceRoot.v1_11_0.ServiceRoot");
788 EXPECT_EQ(asyncResp->res.jsonValue["Id"], "RootService");
789 EXPECT_EQ(asyncResp->res.jsonValue["Name"], "Root Service");
790
791 EXPECT_EQ(asyncResp->res.jsonValue["Fabrics"]["@odata.id"],
792 "/redfish/v1/Fabrics");
793 EXPECT_EQ(asyncResp->res.jsonValue["TelemetryService"]["@odata.id"],
794 "/redfish/v1/TelemetryService");
795 EXPECT_EQ(asyncResp->res.jsonValue["UpdateService"]["@odata.id"],
796 "/redfish/v1/UpdateService");
797 EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test"));
798 EXPECT_FALSE(asyncResp->res.jsonValue.contains("error"));
799
800 // These fields should still be present
801 EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"],
802 "/redfish/v1/Chassis");
803 EXPECT_EQ(asyncResp->res.jsonValue["Fake"]["@odata.id"],
804 "/redfish/v1/Fake");
805 }
806
807 TEST(processContainsSubordinateResponse, noValidLinks)
808 {
809 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
810 asyncResp->res.result(500);
811 asyncResp->res.jsonValue["Chassis"]["@odata.id"] = "/redfish/v1/Chassis";
812
813 crow::Response resp;
814 resp.result(200);
815 nlohmann::json jsonValue;
816 resp.addHeader("Content-Type", "application/json");
817 jsonValue["@odata.id"] = "/redfish/v1";
818 resp.write(
819 jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace));
820
821 RedfishAggregator::processContainsSubordinateResponse(
822 "prefix", asyncResp, resp);
823
824 // We won't add any links from response so asyncResp shouldn't change
825 EXPECT_EQ(asyncResp->res.resultInt(), 500);
826 EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"],
827 "/redfish/v1/Chassis");
828 EXPECT_FALSE(asyncResp->res.jsonValue.contains("@odata.id"));
829
830 // Sat response is non-500 so it shouldn't get copied over
831 asyncResp->res.result(200);
832 resp.result(500);
833 jsonValue["Fabrics"]["@odata.id"] = "/redfish/v1/Fabrics";
834 jsonValue["Test"]["@odata.id"] = "/redfish/v1/Test";
835 jsonValue["TelemetryService"]["@odata.id"] = "/redfish/v1/TelemetryService";
836 jsonValue["UpdateService"]["@odata.id"] = "/redfish/v1/UpdateService";
837 resp.write(
838 jsonValue.dump(2, ' ', true, nlohmann::json::error_handler_t::replace));
839
840 RedfishAggregator::processContainsSubordinateResponse(
841 "prefix", asyncResp, resp);
842
843 EXPECT_EQ(asyncResp->res.resultInt(), 200);
844 EXPECT_EQ(asyncResp->res.jsonValue["Chassis"]["@odata.id"],
845 "/redfish/v1/Chassis");
846 EXPECT_FALSE(asyncResp->res.jsonValue.contains("@odata.id"));
847 EXPECT_FALSE(asyncResp->res.jsonValue.contains("Fabrics"));
848 EXPECT_FALSE(asyncResp->res.jsonValue.contains("Test"));
849 EXPECT_FALSE(asyncResp->res.jsonValue.contains("TelemetryService"));
850 EXPECT_FALSE(asyncResp->res.jsonValue.contains("UpdateService"));
851 }
852
853 } // namespace
854 } // namespace redfish
855