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