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