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