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_EQ(jsonVec, R"([{"hello": "yes"}, [{"there": "no"}, "nice"]])"_json);
125 }
126 
127 TEST(ReadJson, JsonSubElementValueAreUnpackedCorrectly)
128 {
129     crow::Response res;
130     nlohmann::json jsonRequest = R"(
131         {
132             "json": {"integer": 42}
133         }
134     )"_json;
135 
136     int integer = 0;
137     ASSERT_TRUE(readJson(jsonRequest, res, "json/integer", integer));
138     EXPECT_EQ(integer, 42);
139     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
140     EXPECT_THAT(res.jsonValue, IsEmpty());
141 }
142 
143 TEST(ReadJson, JsonDeeperSubElementValueAreUnpackedCorrectly)
144 {
145     crow::Response res;
146     nlohmann::json jsonRequest = R"(
147         {
148             "json": {
149                 "json2": {"string": "foobar"}
150             }
151         }
152     )"_json;
153 
154     std::string foobar;
155     ASSERT_TRUE(readJson(jsonRequest, res, "json/json2/string", foobar));
156     EXPECT_EQ(foobar, "foobar");
157     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
158     EXPECT_THAT(res.jsonValue, IsEmpty());
159 }
160 
161 TEST(ReadJson, MultipleJsonSubElementValueAreUnpackedCorrectly)
162 {
163     crow::Response res;
164     nlohmann::json jsonRequest = R"(
165         {
166             "json": {
167                 "integer": 42,
168                 "string": "foobar"
169             },
170             "string": "bazbar"
171         }
172     )"_json;
173 
174     int integer = 0;
175     std::string foobar;
176     std::string bazbar;
177     ASSERT_TRUE(readJson(jsonRequest, res, "json/integer", integer,
178                          "json/string", foobar, "string", bazbar));
179     EXPECT_EQ(integer, 42);
180     EXPECT_EQ(foobar, "foobar");
181     EXPECT_EQ(bazbar, "bazbar");
182     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
183     EXPECT_THAT(res.jsonValue, IsEmpty());
184 }
185 
186 TEST(ReadJson, ExtraElement)
187 {
188     crow::Response res;
189     nlohmann::json jsonRequest = {{"integer", 1}, {"string", "hello"}};
190 
191     std::optional<int> integer;
192     std::optional<std::string> str;
193 
194     EXPECT_FALSE(readJson(jsonRequest, res, "integer", integer));
195     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
196     EXPECT_FALSE(res.jsonValue.empty());
197     EXPECT_EQ(integer, 1);
198 
199     EXPECT_FALSE(readJson(jsonRequest, res, "string", str));
200     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
201     EXPECT_FALSE(res.jsonValue.empty());
202     EXPECT_EQ(str, "hello");
203 }
204 
205 TEST(ReadJson, ValidMissingElementReturnsTrue)
206 {
207     crow::Response res;
208     nlohmann::json jsonRequest = {{"integer", 1}};
209 
210     std::optional<int> integer;
211     int requiredInteger = 0;
212     std::optional<std::string> str0;
213     std::optional<std::string> str1;
214     std::optional<std::vector<uint8_t>> vec;
215     ASSERT_TRUE(readJson(jsonRequest, res, "missing_integer", integer,
216                          "integer", requiredInteger));
217     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
218     EXPECT_TRUE(res.jsonValue.empty());
219     EXPECT_EQ(integer, std::nullopt);
220 
221     ASSERT_TRUE(readJson(jsonRequest, res, "missing_string", str0, "integer",
222                          requiredInteger));
223     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
224     EXPECT_THAT(res.jsonValue, IsEmpty());
225     EXPECT_EQ(str0, std::nullopt);
226 
227     ASSERT_TRUE(readJson(jsonRequest, res, "integer", integer, "string", str0,
228                          "vector", vec));
229     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
230     EXPECT_THAT(res.jsonValue, IsEmpty());
231     EXPECT_EQ(integer, 1);
232     EXPECT_EQ(str0, std::nullopt);
233     EXPECT_EQ(vec, std::nullopt);
234 
235     ASSERT_TRUE(readJson(jsonRequest, res, "integer", integer, "string0", str0,
236                          "missing_string", str1));
237     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
238     EXPECT_THAT(res.jsonValue, IsEmpty());
239     EXPECT_EQ(str1, std::nullopt);
240 }
241 
242 TEST(ReadJson, InvalidMissingElementReturnsFalse)
243 {
244     crow::Response res;
245     nlohmann::json jsonRequest = {{"integer", 1}, {"string", "hello"}};
246 
247     int integer = 0;
248     std::string str0;
249     std::string str1;
250     std::vector<uint8_t> vec;
251     ASSERT_FALSE(readJson(jsonRequest, res, "missing_integer", integer));
252     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
253     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
254 
255     ASSERT_FALSE(readJson(jsonRequest, res, "missing_string", str0));
256     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
257     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
258 
259     ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string", str0,
260                           "vector", vec));
261     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
262     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
263 
264     ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string0", str0,
265                           "missing_string", str1));
266     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
267     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
268 }
269 
270 TEST(ReadJsonPatch, ValidElementsReturnsTrueResponseOkValuesUnpackedCorrectly)
271 {
272     crow::Response res;
273     std::error_code ec;
274     crow::Request req({}, ec);
275     // Ignore errors intentionally
276     req.body = "{\"integer\": 1}";
277 
278     int64_t integer = 0;
279     ASSERT_TRUE(readJsonPatch(req, res, "integer", integer));
280     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
281     EXPECT_THAT(res.jsonValue, IsEmpty());
282     EXPECT_EQ(integer, 1);
283 }
284 
285 TEST(ReadJsonPatch, EmptyObjectReturnsFalseResponseBadRequest)
286 {
287     crow::Response res;
288     std::error_code ec;
289     crow::Request req({}, ec);
290     // Ignore errors intentionally
291     req.body = "{}";
292 
293     std::optional<int64_t> integer = 0;
294     ASSERT_FALSE(readJsonPatch(req, res, "integer", integer));
295     EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
296     EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
297 }
298 
299 TEST(ReadJsonPatch, OdataIgnored)
300 {
301     crow::Response res;
302     std::error_code ec;
303     crow::Request req({}, ec);
304     // Ignore errors intentionally
305     req.body = R"({"@odata.etag": "etag", "integer": 1})";
306 
307     std::optional<int64_t> integer = 0;
308     ASSERT_TRUE(readJsonPatch(req, res, "integer", integer));
309     EXPECT_EQ(res.result(), boost::beast::http::status::ok);
310     EXPECT_THAT(res.jsonValue, IsEmpty());
311     EXPECT_EQ(integer, 1);
312 }
313 
314 TEST(ReadJsonPatch, OnlyOdataGivesNoOperation)
315 {
316     crow::Response res;
317     std::error_code ec;
318     crow::Request req({}, ec);
319     // Ignore errors intentionally
320     req.body = R"({"@odata.etag": "etag"})";
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({}, ec);
333     // Ignore errors intentionally
334     req.body = "{\"integer\": 1}";
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     // Ignore errors intentionally
349     req.body = "{}";
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 } // namespace
358 } // namespace redfish::json_util