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