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