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 } // namespace
358 } // namespace redfish::json_util
359