xref: /openbmc/bmcweb/test/redfish-core/include/utils/json_utils_test.cpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #include "http_request.hpp"
4 #include "http_response.hpp"
5 #include "utils/json_utils.hpp"
6 
7 #include <boost/beast/http/field.hpp>
8 #include <boost/beast/http/status.hpp>
9 #include <nlohmann/json.hpp>
10 
11 #include <cstddef>
12 #include <cstdint>
13 #include <optional>
14 #include <string>
15 #include <system_error>
16 #include <variant>
17 #include <vector>
18 
19 #include <gmock/gmock.h>
20 #include <gtest/gtest.h>
21 
22 namespace redfish::json_util
23 {
24 namespace
25 {
26 
27 using ::testing::ElementsAre;
28 using ::testing::IsEmpty;
29 using ::testing::Not;
30 
TEST(ReadJson,ValidElementsReturnsTrueResponseOkValuesUnpackedCorrectly)31 TEST(ReadJson, ValidElementsReturnsTrueResponseOkValuesUnpackedCorrectly)
32 {
33     crow::Response res;
34     nlohmann::json jsonRequest = {{"integer", 1},
35                                   {"string", "hello"},
36                                   {"vector", std::vector<uint64_t>{1, 2, 3}}};
37 
38     int64_t integer = 0;
39     std::string str;
40     std::vector<uint64_t> vec;
41     ASSERT_TRUE(readJson(jsonRequest, res, "integer", integer, "string", str,
42                          "vector", vec));
43     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
44     EXPECT_THAT(res.jsonValue, IsEmpty());
45 
46     EXPECT_EQ(integer, 1);
47     EXPECT_EQ(str, "hello");
48     EXPECT_THAT(vec, ElementsAre(1, 2, 3));
49 }
50 
TEST(ReadJson,ValidObjectElementsReturnsTrueResponseOkValuesUnpackedCorrectly)51 TEST(ReadJson, ValidObjectElementsReturnsTrueResponseOkValuesUnpackedCorrectly)
52 {
53     crow::Response res;
54     nlohmann::json::object_t jsonRequest;
55     jsonRequest["integer"] = 1;
56     jsonRequest["string"] = "hello";
57     jsonRequest["vector"] = std::vector<uint64_t>{1, 2, 3};
58 
59     int64_t integer = 0;
60     std::string str;
61     std::vector<uint64_t> vec;
62     ASSERT_TRUE(readJsonObject(jsonRequest, res, "integer", integer, "string",
63                                str, "vector", vec));
64     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
65     EXPECT_THAT(res.jsonValue, IsEmpty());
66 
67     EXPECT_EQ(integer, 1);
68     EXPECT_EQ(str, "hello");
69     EXPECT_THAT(vec, ElementsAre(1, 2, 3));
70 }
71 
TEST(ReadJson,VariantValueUnpackedNull)72 TEST(ReadJson, VariantValueUnpackedNull)
73 {
74     crow::Response res;
75     nlohmann::json jsonRequest = {{"nullval", nullptr}};
76 
77     std::variant<std::string, std::nullptr_t> str;
78 
79     ASSERT_TRUE(readJson(jsonRequest, res, "nullval", str));
80     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
81 
82     EXPECT_TRUE(std::holds_alternative<std::nullptr_t>(str));
83 }
84 
TEST(ReadJson,VariantValueUnpackedValue)85 TEST(ReadJson, VariantValueUnpackedValue)
86 {
87     crow::Response res;
88     nlohmann::json jsonRequest = {{"stringval", "mystring"}};
89 
90     std::variant<std::string, std::nullptr_t> str;
91 
92     ASSERT_TRUE(readJson(jsonRequest, res, "stringval", str));
93     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
94 
95     ASSERT_TRUE(std::holds_alternative<std::string>(str));
96     EXPECT_EQ(std::get<std::string>(str), "mystring");
97 }
98 
TEST(readJson,ExtraElementsReturnsFalseReponseIsBadRequest)99 TEST(readJson, ExtraElementsReturnsFalseReponseIsBadRequest)
100 {
101     crow::Response res;
102     nlohmann::json jsonRequest = {{"integer", 1}, {"string", "hello"}};
103 
104     std::optional<int> integer;
105     std::optional<std::string> str;
106 
107     ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer));
108     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
109     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
110     EXPECT_EQ(integer, 1);
111 
112     ASSERT_FALSE(readJson(jsonRequest, res, "string", str));
113     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
114     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
115     EXPECT_EQ(str, "hello");
116 }
117 
TEST(ReadJson,WrongElementTypeReturnsFalseReponseIsBadRequest)118 TEST(ReadJson, WrongElementTypeReturnsFalseReponseIsBadRequest)
119 {
120     crow::Response res;
121     nlohmann::json jsonRequest = {{"integer", 1}, {"string0", "hello"}};
122 
123     int64_t integer = 0;
124     std::string str0;
125     ASSERT_FALSE(readJson(jsonRequest, res, "integer", str0));
126     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
127     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
128 
129     ASSERT_FALSE(readJson(jsonRequest, res, "string0", integer));
130     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
131     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
132 
133     ASSERT_FALSE(
134         readJson(jsonRequest, res, "integer", str0, "string0", integer));
135     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
136     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
137 }
138 
TEST(ReadJson,MissingElementReturnsFalseReponseIsBadRequest)139 TEST(ReadJson, MissingElementReturnsFalseReponseIsBadRequest)
140 {
141     crow::Response res;
142     nlohmann::json jsonRequest = {{"integer", 1}, {"string0", "hello"}};
143 
144     int64_t integer = 0;
145     std::string str0;
146     std::string str1;
147     std::vector<uint8_t> vec;
148     ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string0", str0,
149                           "vector", vec));
150     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
151     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
152 
153     ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string0", str0,
154                           "string1", str1));
155     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
156     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
157 }
158 
TEST(ReadJson,JsonArrayAreUnpackedCorrectly)159 TEST(ReadJson, JsonArrayAreUnpackedCorrectly)
160 {
161     crow::Response res;
162     nlohmann::json jsonRequest = R"(
163         {
164             "TestJson": [{"hello": "yes"}, [{"there": "no"}, "nice"]]
165         }
166     )"_json;
167 
168     std::vector<nlohmann::json> jsonVec;
169     ASSERT_TRUE(readJson(jsonRequest, res, "TestJson", jsonVec));
170     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
171     EXPECT_THAT(res.jsonValue, IsEmpty());
172     EXPECT_THAT(jsonVec, ElementsAre(R"({"hello": "yes"})"_json,
173                                      R"([{"there": "no"}, "nice"])"_json));
174 }
175 
TEST(ReadJson,JsonSubElementValueAreUnpackedCorrectly)176 TEST(ReadJson, JsonSubElementValueAreUnpackedCorrectly)
177 {
178     crow::Response res;
179     nlohmann::json jsonRequest = R"(
180         {
181             "json": {"integer": 42}
182         }
183     )"_json;
184 
185     int integer = 0;
186     ASSERT_TRUE(readJson(jsonRequest, res, "json/integer", integer));
187     EXPECT_EQ(integer, 42);
188     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
189     EXPECT_THAT(res.jsonValue, IsEmpty());
190 }
191 
TEST(ReadJson,JsonDeeperSubElementValueAreUnpackedCorrectly)192 TEST(ReadJson, JsonDeeperSubElementValueAreUnpackedCorrectly)
193 {
194     crow::Response res;
195     nlohmann::json jsonRequest = R"(
196         {
197             "json": {
198                 "json2": {"string": "foobar"}
199             }
200         }
201     )"_json;
202 
203     std::string foobar;
204     ASSERT_TRUE(readJson(jsonRequest, res, "json/json2/string", foobar));
205     EXPECT_EQ(foobar, "foobar");
206     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
207     EXPECT_THAT(res.jsonValue, IsEmpty());
208 }
209 
TEST(ReadJson,MultipleJsonSubElementValueAreUnpackedCorrectly)210 TEST(ReadJson, MultipleJsonSubElementValueAreUnpackedCorrectly)
211 {
212     crow::Response res;
213     nlohmann::json jsonRequest = R"(
214         {
215             "json": {
216                 "integer": 42,
217                 "string": "foobar"
218             },
219             "string": "bazbar"
220         }
221     )"_json;
222 
223     int integer = 0;
224     std::string foobar;
225     std::string bazbar;
226     ASSERT_TRUE(readJson(jsonRequest, res, "json/integer", integer,
227                          "json/string", foobar, "string", bazbar));
228     EXPECT_EQ(integer, 42);
229     EXPECT_EQ(foobar, "foobar");
230     EXPECT_EQ(bazbar, "bazbar");
231     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
232     EXPECT_THAT(res.jsonValue, IsEmpty());
233 }
234 
TEST(ReadJson,ExtraElement)235 TEST(ReadJson, ExtraElement)
236 {
237     crow::Response res;
238     nlohmann::json jsonRequest = {{"integer", 1}, {"string", "hello"}};
239 
240     std::optional<int> integer;
241     std::optional<std::string> str;
242 
243     EXPECT_FALSE(readJson(jsonRequest, res, "integer", integer));
244     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
245     EXPECT_FALSE(res.jsonValue.empty());
246     EXPECT_EQ(integer, 1);
247 
248     EXPECT_FALSE(readJson(jsonRequest, res, "string", str));
249     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
250     EXPECT_FALSE(res.jsonValue.empty());
251     EXPECT_EQ(str, "hello");
252 }
253 
TEST(ReadJson,ValidMissingElementReturnsTrue)254 TEST(ReadJson, ValidMissingElementReturnsTrue)
255 {
256     crow::Response res;
257     nlohmann::json jsonRequest = {{"integer", 1}};
258 
259     std::optional<int> integer;
260     int requiredInteger = 0;
261     std::optional<std::string> str0;
262     std::optional<std::string> str1;
263     std::optional<std::vector<uint8_t>> vec;
264     ASSERT_TRUE(readJson(jsonRequest, res, "missing_integer", integer,
265                          "integer", requiredInteger));
266     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
267     EXPECT_TRUE(res.jsonValue.empty());
268     EXPECT_EQ(integer, std::nullopt);
269 
270     ASSERT_TRUE(readJson(jsonRequest, res, "missing_string", str0, "integer",
271                          requiredInteger));
272     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
273     EXPECT_THAT(res.jsonValue, IsEmpty());
274     EXPECT_EQ(str0, std::nullopt);
275 
276     ASSERT_TRUE(readJson(jsonRequest, res, "integer", integer, "string", str0,
277                          "vector", vec));
278     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
279     EXPECT_THAT(res.jsonValue, IsEmpty());
280     EXPECT_EQ(integer, 1);
281     EXPECT_EQ(str0, std::nullopt);
282     EXPECT_EQ(vec, std::nullopt);
283 
284     ASSERT_TRUE(readJson(jsonRequest, res, "integer", integer, "string0", str0,
285                          "missing_string", str1));
286     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
287     EXPECT_THAT(res.jsonValue, IsEmpty());
288     EXPECT_EQ(str1, std::nullopt);
289 }
290 
TEST(ReadJson,InvalidMissingElementReturnsFalse)291 TEST(ReadJson, InvalidMissingElementReturnsFalse)
292 {
293     crow::Response res;
294     nlohmann::json jsonRequest = {{"integer", 1}, {"string", "hello"}};
295 
296     int integer = 0;
297     std::string str0;
298     std::string str1;
299     std::vector<uint8_t> vec;
300     ASSERT_FALSE(readJson(jsonRequest, res, "missing_integer", integer));
301     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
302     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
303 
304     ASSERT_FALSE(readJson(jsonRequest, res, "missing_string", str0));
305     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
306     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
307 
308     ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string", str0,
309                           "vector", vec));
310     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
311     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
312 
313     ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string0", str0,
314                           "missing_string", str1));
315     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
316     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
317 }
318 
TEST(ReadJsonPatch,ValidElementsReturnsTrueResponseOkValuesUnpackedCorrectly)319 TEST(ReadJsonPatch, ValidElementsReturnsTrueResponseOkValuesUnpackedCorrectly)
320 {
321     crow::Response res;
322     std::error_code ec;
323     crow::Request req("{\"integer\": 1}", ec);
324 
325     // Ignore errors intentionally
326     req.addHeader(boost::beast::http::field::content_type, "application/json");
327 
328     int64_t integer = 0;
329     ASSERT_TRUE(readJsonPatch(req, res, "integer", integer));
330     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
331     EXPECT_THAT(res.jsonValue, IsEmpty());
332     EXPECT_EQ(integer, 1);
333 }
334 
TEST(ReadJsonPatch,EmptyObjectReturnsFalseResponseBadRequest)335 TEST(ReadJsonPatch, EmptyObjectReturnsFalseResponseBadRequest)
336 {
337     crow::Response res;
338     std::error_code ec;
339     crow::Request req("{}", ec);
340     // Ignore errors intentionally
341 
342     std::optional<int64_t> integer = 0;
343     ASSERT_FALSE(readJsonPatch(req, res, "integer", integer));
344     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
345     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
346 }
347 
TEST(ReadJsonPatch,OdataIgnored)348 TEST(ReadJsonPatch, OdataIgnored)
349 {
350     crow::Response res;
351     std::error_code ec;
352     crow::Request req(R"({"@odata.etag": "etag", "integer": 1})", ec);
353     req.addHeader(boost::beast::http::field::content_type, "application/json");
354     // Ignore errors intentionally
355 
356     std::optional<int64_t> integer = 0;
357     ASSERT_TRUE(readJsonPatch(req, res, "integer", integer));
358     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
359     EXPECT_THAT(res.jsonValue, IsEmpty());
360     EXPECT_EQ(integer, 1);
361 }
362 
TEST(ReadJsonPatch,OnlyOdataGivesNoOperation)363 TEST(ReadJsonPatch, OnlyOdataGivesNoOperation)
364 {
365     crow::Response res;
366     std::error_code ec;
367     crow::Request req(R"({"@odata.etag": "etag"})", ec);
368     // Ignore errors intentionally
369 
370     std::optional<int64_t> integer = 0;
371     ASSERT_FALSE(readJsonPatch(req, res, "integer", integer));
372     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
373     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
374 }
375 
TEST(ReadJsonPatch,VerifyReadJsonPatchIntegerReturnsOutOfRange)376 TEST(ReadJsonPatch, VerifyReadJsonPatchIntegerReturnsOutOfRange)
377 {
378     crow::Response res;
379     std::error_code ec;
380 
381     // 4294967296 is an out-of-range value for uint32_t
382     crow::Request req(R"({"@odata.etag": "etag", "integer": 4294967296})", ec);
383     req.addHeader(boost::beast::http::field::content_type, "application/json");
384 
385     uint32_t integer = 0;
386     ASSERT_FALSE(readJsonPatch(req, res, "integer", integer));
387     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
388 
389     const nlohmann::json& resExtInfo =
390         res.jsonValue["error"]["@Message.ExtendedInfo"];
391     EXPECT_THAT(resExtInfo[0]["@odata.type"], "#Message.v1_1_1.Message");
392     EXPECT_THAT(resExtInfo[0]["MessageId"],
393                 "Base.1.19.PropertyValueOutOfRange");
394     EXPECT_THAT(resExtInfo[0]["MessageSeverity"], "Warning");
395 }
396 
TEST(ReadJsonAction,ValidElementsReturnsTrueResponseOkValuesUnpackedCorrectly)397 TEST(ReadJsonAction, ValidElementsReturnsTrueResponseOkValuesUnpackedCorrectly)
398 {
399     crow::Response res;
400     std::error_code ec;
401     crow::Request req("{\"integer\": 1}", ec);
402     req.addHeader(boost::beast::http::field::content_type, "application/json");
403     // Ignore errors intentionally
404 
405     int64_t integer = 0;
406     ASSERT_TRUE(readJsonAction(req, res, "integer", integer));
407     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
408     EXPECT_THAT(res.jsonValue, IsEmpty());
409     EXPECT_EQ(integer, 1);
410 }
411 
TEST(ReadJsonAction,EmptyObjectReturnsTrueResponseOk)412 TEST(ReadJsonAction, EmptyObjectReturnsTrueResponseOk)
413 {
414     crow::Response res;
415     std::error_code ec;
416     crow::Request req({"{}"}, ec);
417     req.addHeader(boost::beast::http::field::content_type, "application/json");
418     // Ignore errors intentionally
419 
420     std::optional<int64_t> integer = 0;
421     ASSERT_TRUE(readJsonAction(req, res, "integer", integer));
422     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
423     EXPECT_THAT(res.jsonValue, IsEmpty());
424 }
425 
TEST(odataObjectCmp,PositiveCases)426 TEST(odataObjectCmp, PositiveCases)
427 {
428     EXPECT_EQ(0, odataObjectCmp(R"({"@odata.id": "/redfish/v1/1"})"_json,
429                                 R"({"@odata.id": "/redfish/v1/1"})"_json));
430     EXPECT_EQ(0, odataObjectCmp(R"({"@odata.id": ""})"_json,
431                                 R"({"@odata.id": ""})"_json));
432     EXPECT_EQ(0, odataObjectCmp(R"({"@odata.id": 42})"_json,
433                                 R"({"@odata.id": 0})"_json));
434     EXPECT_EQ(0, odataObjectCmp(R"({})"_json, R"({})"_json));
435 
436     EXPECT_GT(0, odataObjectCmp(R"({"@odata.id": "/redfish/v1"})"_json,
437                                 R"({"@odata.id": "/redfish/v1/1"})"_json));
438     EXPECT_LT(0, odataObjectCmp(R"({"@odata.id": "/redfish/v1/1"})"_json,
439                                 R"({"@odata.id": "/redfish/v1"})"_json));
440 
441     EXPECT_LT(0, odataObjectCmp(R"({"@odata.id": "/10"})"_json,
442                                 R"({"@odata.id": "/1"})"_json));
443     EXPECT_GT(0, odataObjectCmp(R"({"@odata.id": "/1"})"_json,
444                                 R"({"@odata.id": "/10"})"_json));
445 
446     EXPECT_GT(0, odataObjectCmp(R"({})"_json, R"({"@odata.id": "/1"})"_json));
447     EXPECT_LT(0, odataObjectCmp(R"({"@odata.id": "/1"})"_json, R"({})"_json));
448 
449     EXPECT_GT(0, odataObjectCmp(R"({"@odata.id": 4})"_json,
450                                 R"({"@odata.id": "/1"})"_json));
451     EXPECT_LT(0, odataObjectCmp(R"({"@odata.id": "/1"})"_json,
452                                 R"({"@odata.id": 4})"_json));
453 }
454 
TEST(SortJsonArrayByOData,ElementMissingKeyReturnsFalseArrayIsPartlySorted)455 TEST(SortJsonArrayByOData, ElementMissingKeyReturnsFalseArrayIsPartlySorted)
456 {
457     nlohmann::json::array_t array =
458         R"([{"@odata.id" : "/redfish/v1/100"}, {"@odata.id": "/redfish/v1/1"}, {"@odata.id" : "/redfish/v1/20"}])"_json;
459     sortJsonArrayByOData(array);
460     // Objects with other keys are always larger than those with the specified
461     // key.
462     EXPECT_THAT(array,
463                 ElementsAre(R"({"@odata.id": "/redfish/v1/1"})"_json,
464                             R"({"@odata.id" : "/redfish/v1/20"})"_json,
465                             R"({"@odata.id" : "/redfish/v1/100"})"_json));
466 }
467 
TEST(SortJsonArrayByOData,SortedByStringValueOnSuccessArrayIsSorted)468 TEST(SortJsonArrayByOData, SortedByStringValueOnSuccessArrayIsSorted)
469 {
470     nlohmann::json::array_t array =
471         R"([{"@odata.id": "/redfish/v1/20"}, {"@odata.id" : "/redfish/v1"}, {"@odata.id" : "/redfish/v1/100"}])"_json;
472     sortJsonArrayByOData(array);
473     EXPECT_THAT(array,
474                 ElementsAre(R"({"@odata.id": "/redfish/v1"})"_json,
475                             R"({"@odata.id" : "/redfish/v1/20"})"_json,
476                             R"({"@odata.id" : "/redfish/v1/100"})"_json));
477 }
478 
TEST(objectKeyCmp,PositiveCases)479 TEST(objectKeyCmp, PositiveCases)
480 {
481     EXPECT_EQ(
482         0, objectKeyCmp("@odata.id",
483                         R"({"@odata.id": "/redfish/v1/1", "Name": "a"})"_json,
484                         R"({"@odata.id": "/redfish/v1/1", "Name": "b"})"_json));
485     EXPECT_GT(
486         0, objectKeyCmp("Name",
487                         R"({"@odata.id": "/redfish/v1/1", "Name": "a"})"_json,
488                         R"({"@odata.id": "/redfish/v1/1", "Name": "b"})"_json));
489     EXPECT_EQ(0, objectKeyCmp(
490                      "Name",
491                      R"({"@odata.id": "/redfish/v1/1", "Name": "a 45"})"_json,
492                      R"({"@odata.id": "/redfish/v1/1", "Name": "a 45"})"_json));
493     EXPECT_GT(0, objectKeyCmp(
494                      "Name",
495                      R"({"@odata.id": "/redfish/v1/1", "Name": "a 45"})"_json,
496                      R"({"@odata.id": "/redfish/v1/1", "Name": "b 45"})"_json));
497 
498     EXPECT_GT(
499         0, objectKeyCmp("@odata.id",
500                         R"({"@odata.id": "/redfish/v1/1", "Name": "b"})"_json,
501                         R"({"@odata.id": "/redfish/v1/2", "Name": "b"})"_json));
502     EXPECT_EQ(
503         0, objectKeyCmp("Name",
504                         R"({"@odata.id": "/redfish/v1/1", "Name": "b"})"_json,
505                         R"({"@odata.id": "/redfish/v1/2", "Name": "b"})"_json));
506 
507     EXPECT_LT(0,
508               objectKeyCmp(
509                   "@odata.id",
510                   R"({"@odata.id": "/redfish/v1/p10/", "Name": "a1"})"_json,
511                   R"({"@odata.id": "/redfish/v1/p1/", "Name": "a10"})"_json));
512     EXPECT_GT(0,
513               objectKeyCmp(
514                   "Name",
515                   R"({"@odata.id": "/redfish/v1/p10/", "Name": "a1"})"_json,
516                   R"({"@odata.id": "/redfish/v1/p1/", "Name": "a10"})"_json));
517 
518     nlohmann::json leftRequest =
519         R"({"Name": "fan1", "@odata.id": "/redfish/v1/Chassis/chassis2"})"_json;
520     nlohmann::json rightRequest =
521         R"({"Name": "fan2", "@odata.id": "/redfish/v1/Chassis/chassis1"})"_json;
522 
523     EXPECT_GT(0, objectKeyCmp("Name", leftRequest, rightRequest));
524     EXPECT_LT(0, objectKeyCmp("@odata.id", leftRequest, rightRequest));
525     EXPECT_EQ(0, objectKeyCmp("DataSourceUri", leftRequest, rightRequest));
526 }
527 
TEST(SortJsonArrayByKey,ElementMissingKeyReturnsFalseArrayIsPartlySorted)528 TEST(SortJsonArrayByKey, ElementMissingKeyReturnsFalseArrayIsPartlySorted)
529 {
530     nlohmann::json::array_t array =
531         R"([{"@odata.id" : "/redfish/v1/100"}, {"Name" : "/redfish/v1/5"}, {"@odata.id": "/redfish/v1/1"}, {"@odata.id" : "/redfish/v1/20"}])"_json;
532     sortJsonArrayByKey(array, "@odata.id");
533     // Objects with other keys are always smaller than those with the specified
534     // key.
535     EXPECT_THAT(array,
536                 ElementsAre(R"({"Name" : "/redfish/v1/5"})"_json,
537                             R"({"@odata.id": "/redfish/v1/1"})"_json,
538                             R"({"@odata.id" : "/redfish/v1/20"})"_json,
539                             R"({"@odata.id" : "/redfish/v1/100"})"_json));
540 }
541 
TEST(SortJsonArrayByKey,SortedByStringValueOnSuccessArrayIsSorted)542 TEST(SortJsonArrayByKey, SortedByStringValueOnSuccessArrayIsSorted)
543 {
544     nlohmann::json::array_t array =
545         R"([{"@odata.id": "/redfish/v1/20", "Name": "a"}, {"@odata.id" : "/redfish/v1", "Name": "c"}, {"@odata.id" : "/redfish/v1/100", "Name": "b"}])"_json;
546 
547     sortJsonArrayByKey(array, "@odata.id");
548     EXPECT_THAT(
549         array,
550         ElementsAre(R"({"@odata.id": "/redfish/v1", "Name": "c"})"_json,
551                     R"({"@odata.id": "/redfish/v1/20", "Name": "a"})"_json,
552                     R"({"@odata.id": "/redfish/v1/100", "Name": "b"})"_json));
553 
554     sortJsonArrayByKey(array, "Name");
555     EXPECT_THAT(
556         array,
557         ElementsAre(R"({"@odata.id": "/redfish/v1/20", "Name": "a"})"_json,
558                     R"({"@odata.id": "/redfish/v1/100", "Name": "b"})"_json,
559                     R"({"@odata.id": "/redfish/v1", "Name": "c"})"_json));
560 }
561 
TEST(GetEstimatedJsonSize,NumberIs8Bytpes)562 TEST(GetEstimatedJsonSize, NumberIs8Bytpes)
563 {
564     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json(123)), 8);
565     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json(77777777777)), 8);
566     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json(3.14)), 8);
567 }
568 
TEST(GetEstimatedJsonSize,BooleanIs5Byte)569 TEST(GetEstimatedJsonSize, BooleanIs5Byte)
570 {
571     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json(true)), 5);
572     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json(false)), 5);
573 }
574 
TEST(GetEstimatedJsonSize,NullIs4Byte)575 TEST(GetEstimatedJsonSize, NullIs4Byte)
576 {
577     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json()), 4);
578 }
579 
TEST(GetEstimatedJsonSize,StringAndBytesReturnsLengthAndQuote)580 TEST(GetEstimatedJsonSize, StringAndBytesReturnsLengthAndQuote)
581 {
582     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json("1234")), 6);
583     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json::binary({1, 2, 3, 4})), 4);
584 }
585 
TEST(GetEstimatedJsonSize,ArrayReturnsSum)586 TEST(GetEstimatedJsonSize, ArrayReturnsSum)
587 {
588     nlohmann::json arr = {1, 3.14, "123", nlohmann::json::binary({1, 2, 3, 4})};
589     EXPECT_EQ(getEstimatedJsonSize(arr), 8 + 8 + 5 + 4);
590 }
591 
TEST(GetEstimatedJsonSize,ObjectsReturnsSumWithKeyAndValue)592 TEST(GetEstimatedJsonSize, ObjectsReturnsSumWithKeyAndValue)
593 {
594     nlohmann::json obj = R"(
595 {
596   "key0": 123,
597   "key1": "123",
598   "key2": [1, 2, 3],
599   "key3": {"key4": "123"}
600 }
601 )"_json;
602 
603     uint64_t expected = 0;
604     // 5 keys of length 4
605     expected += uint64_t(5) * 4;
606     // 5 colons, 5 quote pairs, and 5 spaces for 5 keys
607     expected += uint64_t(5) * (1 + 2 + 1);
608     // 2 string values of length 3
609     expected += uint64_t(2) * (3 + 2);
610     // 1 number value
611     expected += 8;
612     // 1 array value of 3 numbers
613     expected += uint64_t(3) * 8;
614     EXPECT_EQ(getEstimatedJsonSize(obj), expected);
615 }
616 
617 } // namespace
618 } // namespace redfish::json_util
619