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