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