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