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