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> // IWYU pragma: keep
18 #include <gtest/gtest.h> // IWYU pragma: keep
19 
20 // IWYU pragma: no_include <gtest/gtest-message.h>
21 // IWYU pragma: no_include <gtest/gtest-test-part.h>
22 // IWYU pragma: no_include "gtest/gtest_pred_impl.h"
23 // IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp>
24 
25 namespace redfish::json_util
26 {
27 namespace
28 {
29 
30 using ::testing::ElementsAre;
31 using ::testing::IsEmpty;
32 using ::testing::Not;
33 
34 TEST(ReadJson, ValidElementsReturnsTrueResponseOkValuesUnpackedCorrectly)
35 {
36     crow::Response res;
37     nlohmann::json jsonRequest = {{"integer", 1},
38                                   {"string", "hello"},
39                                   {"vector", std::vector<uint64_t>{1, 2, 3}}};
40 
41     int64_t integer = 0;
42     std::string str;
43     std::vector<uint64_t> vec;
44     ASSERT_TRUE(readJson(jsonRequest, res, "integer", integer, "string", str,
45                          "vector", vec));
46     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
47     EXPECT_THAT(res.jsonValue, IsEmpty());
48 
49     EXPECT_EQ(integer, 1);
50     EXPECT_EQ(str, "hello");
51     EXPECT_THAT(vec, ElementsAre(1, 2, 3));
52 }
53 
54 TEST(ReadJson, ValidObjectElementsReturnsTrueResponseOkValuesUnpackedCorrectly)
55 {
56     crow::Response res;
57     nlohmann::json::object_t jsonRequest;
58     jsonRequest["integer"] = 1;
59     jsonRequest["string"] = "hello";
60     jsonRequest["vector"] = std::vector<uint64_t>{1, 2, 3};
61 
62     int64_t integer = 0;
63     std::string str;
64     std::vector<uint64_t> vec;
65     ASSERT_TRUE(readJsonObject(jsonRequest, res, "integer", integer, "string",
66                                str, "vector", vec));
67     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
68     EXPECT_THAT(res.jsonValue, IsEmpty());
69 
70     EXPECT_EQ(integer, 1);
71     EXPECT_EQ(str, "hello");
72     EXPECT_THAT(vec, ElementsAre(1, 2, 3));
73 }
74 
75 TEST(ReadJson, VariantValueUnpackedNull)
76 {
77     crow::Response res;
78     nlohmann::json jsonRequest = {{"nullval", nullptr}};
79 
80     std::variant<std::string, std::nullptr_t> str;
81 
82     ASSERT_TRUE(readJson(jsonRequest, res, "nullval", str));
83     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
84 
85     EXPECT_TRUE(std::holds_alternative<std::nullptr_t>(str));
86 }
87 
88 TEST(ReadJson, VariantValueUnpackedValue)
89 {
90     crow::Response res;
91     nlohmann::json jsonRequest = {{"stringval", "mystring"}};
92 
93     std::variant<std::string, std::nullptr_t> str;
94 
95     ASSERT_TRUE(readJson(jsonRequest, res, "stringval", str));
96     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
97 
98     ASSERT_TRUE(std::holds_alternative<std::string>(str));
99     EXPECT_EQ(std::get<std::string>(str), "mystring");
100 }
101 
102 TEST(readJson, ExtraElementsReturnsFalseReponseIsBadRequest)
103 {
104     crow::Response res;
105     nlohmann::json jsonRequest = {{"integer", 1}, {"string", "hello"}};
106 
107     std::optional<int> integer;
108     std::optional<std::string> str;
109 
110     ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer));
111     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
112     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
113     EXPECT_EQ(integer, 1);
114 
115     ASSERT_FALSE(readJson(jsonRequest, res, "string", str));
116     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
117     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
118     EXPECT_EQ(str, "hello");
119 }
120 
121 TEST(ReadJson, WrongElementTypeReturnsFalseReponseIsBadRequest)
122 {
123     crow::Response res;
124     nlohmann::json jsonRequest = {{"integer", 1}, {"string0", "hello"}};
125 
126     int64_t integer = 0;
127     std::string str0;
128     ASSERT_FALSE(readJson(jsonRequest, res, "integer", str0));
129     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
130     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
131 
132     ASSERT_FALSE(readJson(jsonRequest, res, "string0", integer));
133     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
134     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
135 
136     ASSERT_FALSE(
137         readJson(jsonRequest, res, "integer", str0, "string0", integer));
138     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
139     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
140 }
141 
142 TEST(ReadJson, MissingElementReturnsFalseReponseIsBadRequest)
143 {
144     crow::Response res;
145     nlohmann::json jsonRequest = {{"integer", 1}, {"string0", "hello"}};
146 
147     int64_t integer = 0;
148     std::string str0;
149     std::string str1;
150     std::vector<uint8_t> vec;
151     ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string0", str0,
152                           "vector", vec));
153     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
154     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
155 
156     ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string0", str0,
157                           "string1", str1));
158     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
159     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
160 }
161 
162 TEST(ReadJson, JsonArrayAreUnpackedCorrectly)
163 {
164     crow::Response res;
165     nlohmann::json jsonRequest = R"(
166         {
167             "TestJson": [{"hello": "yes"}, [{"there": "no"}, "nice"]]
168         }
169     )"_json;
170 
171     std::vector<nlohmann::json> jsonVec;
172     ASSERT_TRUE(readJson(jsonRequest, res, "TestJson", jsonVec));
173     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
174     EXPECT_THAT(res.jsonValue, IsEmpty());
175     EXPECT_THAT(jsonVec, ElementsAre(R"({"hello": "yes"})"_json,
176                                      R"([{"there": "no"}, "nice"])"_json));
177 }
178 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
379 TEST(ReadJsonAction, ValidElementsReturnsTrueResponseOkValuesUnpackedCorrectly)
380 {
381     crow::Response res;
382     std::error_code ec;
383     crow::Request req("{\"integer\": 1}", ec);
384     req.addHeader(boost::beast::http::field::content_type, "application/json");
385     // Ignore errors intentionally
386 
387     int64_t integer = 0;
388     ASSERT_TRUE(readJsonAction(req, res, "integer", integer));
389     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
390     EXPECT_THAT(res.jsonValue, IsEmpty());
391     EXPECT_EQ(integer, 1);
392 }
393 
394 TEST(ReadJsonAction, EmptyObjectReturnsTrueResponseOk)
395 {
396     crow::Response res;
397     std::error_code ec;
398     crow::Request req({"{}"}, ec);
399     req.addHeader(boost::beast::http::field::content_type, "application/json");
400     // Ignore errors intentionally
401 
402     std::optional<int64_t> integer = 0;
403     ASSERT_TRUE(readJsonAction(req, res, "integer", integer));
404     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
405     EXPECT_THAT(res.jsonValue, IsEmpty());
406 }
407 
408 TEST(odataObjectCmp, PositiveCases)
409 {
410     EXPECT_EQ(0, odataObjectCmp(R"({"@odata.id": "/redfish/v1/1"})"_json,
411                                 R"({"@odata.id": "/redfish/v1/1"})"_json));
412     EXPECT_EQ(0, odataObjectCmp(R"({"@odata.id": ""})"_json,
413                                 R"({"@odata.id": ""})"_json));
414     EXPECT_EQ(0, odataObjectCmp(R"({"@odata.id": 42})"_json,
415                                 R"({"@odata.id": 0})"_json));
416     EXPECT_EQ(0, odataObjectCmp(R"({})"_json, R"({})"_json));
417 
418     EXPECT_GT(0, odataObjectCmp(R"({"@odata.id": "/redfish/v1"})"_json,
419                                 R"({"@odata.id": "/redfish/v1/1"})"_json));
420     EXPECT_LT(0, odataObjectCmp(R"({"@odata.id": "/redfish/v1/1"})"_json,
421                                 R"({"@odata.id": "/redfish/v1"})"_json));
422 
423     EXPECT_LT(0, odataObjectCmp(R"({"@odata.id": "/10"})"_json,
424                                 R"({"@odata.id": "/1"})"_json));
425     EXPECT_GT(0, odataObjectCmp(R"({"@odata.id": "/1"})"_json,
426                                 R"({"@odata.id": "/10"})"_json));
427 
428     EXPECT_GT(0, odataObjectCmp(R"({})"_json, R"({"@odata.id": "/1"})"_json));
429     EXPECT_LT(0, odataObjectCmp(R"({"@odata.id": "/1"})"_json, R"({})"_json));
430 
431     EXPECT_GT(0, odataObjectCmp(R"({"@odata.id": 4})"_json,
432                                 R"({"@odata.id": "/1"})"_json));
433     EXPECT_LT(0, odataObjectCmp(R"({"@odata.id": "/1"})"_json,
434                                 R"({"@odata.id": 4})"_json));
435 }
436 
437 TEST(SortJsonArrayByKey, ElementMissingKeyReturnsFalseArrayIsPartlySorted)
438 {
439     nlohmann::json::array_t array =
440         R"([{"@odata.id" : "/redfish/v1/100"}, {"@odata.id": "/redfish/v1/1"}, {"@odata.id" : "/redfish/v1/20"}])"_json;
441     sortJsonArrayByOData(array);
442     // Objects with other keys are always larger than those with the specified
443     // key.
444     EXPECT_THAT(array,
445                 ElementsAre(R"({"@odata.id": "/redfish/v1/1"})"_json,
446                             R"({"@odata.id" : "/redfish/v1/20"})"_json,
447                             R"({"@odata.id" : "/redfish/v1/100"})"_json));
448 }
449 
450 TEST(SortJsonArrayByKey, SortedByStringValueOnSuccessArrayIsSorted)
451 {
452     nlohmann::json::array_t array =
453         R"([{"@odata.id": "/redfish/v1/20"}, {"@odata.id" : "/redfish/v1"}, {"@odata.id" : "/redfish/v1/100"}])"_json;
454     sortJsonArrayByOData(array);
455     EXPECT_THAT(array,
456                 ElementsAre(R"({"@odata.id": "/redfish/v1"})"_json,
457                             R"({"@odata.id" : "/redfish/v1/20"})"_json,
458                             R"({"@odata.id" : "/redfish/v1/100"})"_json));
459 }
460 TEST(GetEstimatedJsonSize, NumberIs8Bytpes)
461 {
462     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json(123)), 8);
463     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json(77777777777)), 8);
464     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json(3.14)), 8);
465 }
466 
467 TEST(GetEstimatedJsonSize, BooleanIs5Byte)
468 {
469     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json(true)), 5);
470     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json(false)), 5);
471 }
472 
473 TEST(GetEstimatedJsonSize, NullIs4Byte)
474 {
475     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json()), 4);
476 }
477 
478 TEST(GetEstimatedJsonSize, StringAndBytesReturnsLengthAndQuote)
479 {
480     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json("1234")), 6);
481     EXPECT_EQ(getEstimatedJsonSize(nlohmann::json::binary({1, 2, 3, 4})), 4);
482 }
483 
484 TEST(GetEstimatedJsonSize, ArrayReturnsSum)
485 {
486     nlohmann::json arr = {1, 3.14, "123", nlohmann::json::binary({1, 2, 3, 4})};
487     EXPECT_EQ(getEstimatedJsonSize(arr), 8 + 8 + 5 + 4);
488 }
489 
490 TEST(GetEstimatedJsonSize, ObjectsReturnsSumWithKeyAndValue)
491 {
492     nlohmann::json obj = R"(
493 {
494   "key0": 123,
495   "key1": "123",
496   "key2": [1, 2, 3],
497   "key3": {"key4": "123"}
498 }
499 )"_json;
500 
501     uint64_t expected = 0;
502     // 5 keys of length 4
503     expected += uint64_t(5) * 4;
504     // 5 colons, 5 quote pairs, and 5 spaces for 5 keys
505     expected += uint64_t(5) * (1 + 2 + 1);
506     // 2 string values of length 3
507     expected += uint64_t(2) * (3 + 2);
508     // 1 number value
509     expected += 8;
510     // 1 array value of 3 numbers
511     expected += uint64_t(3) * 8;
512     EXPECT_EQ(getEstimatedJsonSize(obj), expected);
513 }
514 
515 } // namespace
516 } // namespace redfish::json_util
517