xref: /openbmc/bmcweb/test/redfish-core/include/redfish_aggregator_test.cpp (revision 76c2ad64440e2864a0cc3d66a3b8bb0f5ac2f288)
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