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