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