| #include "json_utils.hpp" |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <optional> |
| #include <string> |
| #include <system_error> // NOLINT |
| #include <vector> |
| |
| #include <gmock/gmock.h> // IWYU pragma: keep |
| #include <gtest/gtest.h> // IWYU pragma: keep |
| #include "boost/beast/http/status.hpp" // NOLINT |
| #include "http_request.hpp" |
| #include "http_response.hpp" |
| #include <nlohmann/json.hpp> |
| |
| // IWYU pragma: no_include <gtest/gtest-message.h> |
| // IWYU pragma: no_include <gtest/gtest-test-part.h> |
| // IWYU pragma: no_include "gtest/gtest_pred_impl.h" |
| // IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp> |
| |
| namespace redfish::json_util { |
| namespace { |
| |
| using ::testing::ElementsAre; |
| using ::testing::IsEmpty; |
| using ::testing::Not; |
| |
| using JsonArray = nlohmann::json::array_t; |
| |
| TEST(ReadJson, ValidElementsReturnsTrueResponseOkValuesUnpackedCorrectly) { |
| crow::Response res; |
| nlohmann::json jsonRequest = {{"integer", 1}, |
| {"string", "hello"}, |
| {"vector", std::vector<uint64_t>{1, 2, 3}}}; |
| |
| int64_t integer = 0; |
| std::string str; |
| std::vector<uint64_t> vec; |
| ASSERT_TRUE(readJson(jsonRequest, res, "integer", integer, "string", str, |
| "vector", vec)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::ok); |
| EXPECT_THAT(res.jsonValue, IsEmpty()); |
| |
| EXPECT_EQ(integer, 1); |
| EXPECT_EQ(str, "hello"); |
| EXPECT_THAT(vec, ElementsAre(1, 2, 3)); |
| } |
| |
| TEST(readJson, ExtraElementsReturnsFalseReponseIsBadRequest) { |
| crow::Response res; |
| nlohmann::json jsonRequest = {{"integer", 1}, {"string", "hello"}}; |
| |
| std::optional<int> integer; |
| std::optional<std::string> str; |
| |
| ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::bad_request); |
| EXPECT_THAT(res.jsonValue, Not(IsEmpty())); |
| EXPECT_EQ(integer, 1); |
| |
| ASSERT_FALSE(readJson(jsonRequest, res, "string", str)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::bad_request); |
| EXPECT_THAT(res.jsonValue, Not(IsEmpty())); |
| EXPECT_EQ(str, "hello"); |
| } |
| |
| TEST(ReadJson, WrongElementTypeReturnsFalseReponseIsBadRequest) { |
| crow::Response res; |
| nlohmann::json jsonRequest = {{"integer", 1}, {"string0", "hello"}}; |
| |
| int64_t integer = 0; |
| std::string str0; |
| ASSERT_FALSE(readJson(jsonRequest, res, "integer", str0)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::bad_request); |
| EXPECT_THAT(res.jsonValue, Not(IsEmpty())); |
| |
| ASSERT_FALSE(readJson(jsonRequest, res, "string0", integer)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::bad_request); |
| EXPECT_THAT(res.jsonValue, Not(IsEmpty())); |
| |
| ASSERT_FALSE(readJson(jsonRequest, res, "integer", str0, "string0", integer)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::bad_request); |
| EXPECT_THAT(res.jsonValue, Not(IsEmpty())); |
| } |
| |
| TEST(ReadJson, MissingElementReturnsFalseReponseIsBadRequest) { |
| crow::Response res; |
| nlohmann::json jsonRequest = {{"integer", 1}, {"string0", "hello"}}; |
| |
| int64_t integer = 0; |
| std::string str0; |
| std::string str1; |
| std::vector<uint8_t> vec; |
| ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string0", str0, |
| "vector", vec)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::bad_request); |
| EXPECT_THAT(res.jsonValue, Not(IsEmpty())); |
| |
| ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string0", str0, |
| "string1", str1)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::bad_request); |
| EXPECT_THAT(res.jsonValue, Not(IsEmpty())); |
| } |
| |
| TEST(ReadJson, JsonArrayAreUnpackedCorrectly) { |
| crow::Response res; |
| nlohmann::json jsonRequest = R"( |
| { |
| "TestJson": [{"hello": "yes"}, [{"there": "no"}, "nice"]] |
| } |
| )"_json; |
| |
| std::vector<nlohmann::json> jsonVec; |
| ASSERT_TRUE(readJson(jsonRequest, res, "TestJson", jsonVec)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::ok); |
| EXPECT_THAT(res.jsonValue, IsEmpty()); |
| std::vector<nlohmann::json> exectedVec = |
| R"([{"hello": "yes"}, [{"there": "no"}, "nice"]])"_json; |
| EXPECT_EQ(jsonVec, exectedVec); |
| } |
| |
| TEST(ReadJson, JsonSubElementValueAreUnpackedCorrectly) { |
| crow::Response res; |
| nlohmann::json jsonRequest = R"( |
| { |
| "json": {"integer": 42} |
| } |
| )"_json; |
| |
| int integer = 0; |
| ASSERT_TRUE(readJson(jsonRequest, res, "json/integer", integer)); |
| EXPECT_EQ(integer, 42); |
| EXPECT_EQ(res.result(), boost::beast::http::status::ok); |
| EXPECT_THAT(res.jsonValue, IsEmpty()); |
| } |
| |
| TEST(ReadJson, JsonDeeperSubElementValueAreUnpackedCorrectly) { |
| crow::Response res; |
| nlohmann::json jsonRequest = R"( |
| { |
| "json": { |
| "json2": {"string": "foobar"} |
| } |
| } |
| )"_json; |
| |
| std::string foobar; |
| ASSERT_TRUE(readJson(jsonRequest, res, "json/json2/string", foobar)); |
| EXPECT_EQ(foobar, "foobar"); |
| EXPECT_EQ(res.result(), boost::beast::http::status::ok); |
| EXPECT_THAT(res.jsonValue, IsEmpty()); |
| } |
| |
| TEST(ReadJson, MultipleJsonSubElementValueAreUnpackedCorrectly) { |
| crow::Response res; |
| nlohmann::json jsonRequest = R"( |
| { |
| "json": { |
| "integer": 42, |
| "string": "foobar" |
| }, |
| "string": "bazbar" |
| } |
| )"_json; |
| |
| int integer = 0; |
| std::string foobar; |
| std::string bazbar; |
| ASSERT_TRUE(readJson(jsonRequest, res, "json/integer", integer, "json/string", |
| foobar, "string", bazbar)); |
| EXPECT_EQ(integer, 42); |
| EXPECT_EQ(foobar, "foobar"); |
| EXPECT_EQ(bazbar, "bazbar"); |
| EXPECT_EQ(res.result(), boost::beast::http::status::ok); |
| EXPECT_THAT(res.jsonValue, IsEmpty()); |
| } |
| |
| TEST(ReadJson, ExtraElement) { |
| crow::Response res; |
| nlohmann::json jsonRequest = {{"integer", 1}, {"string", "hello"}}; |
| |
| std::optional<int> integer; |
| std::optional<std::string> str; |
| |
| EXPECT_FALSE(readJson(jsonRequest, res, "integer", integer)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::bad_request); |
| EXPECT_FALSE(res.jsonValue.empty()); |
| EXPECT_EQ(integer, 1); |
| |
| EXPECT_FALSE(readJson(jsonRequest, res, "string", str)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::bad_request); |
| EXPECT_FALSE(res.jsonValue.empty()); |
| EXPECT_EQ(str, "hello"); |
| } |
| |
| TEST(ReadJson, ValidMissingElementReturnsTrue) { |
| crow::Response res; |
| nlohmann::json jsonRequest = {{"integer", 1}}; |
| |
| std::optional<int> integer; |
| int requiredInteger = 0; |
| std::optional<std::string> str0; |
| std::optional<std::string> str1; |
| std::optional<std::vector<uint8_t>> vec; |
| ASSERT_TRUE(readJson(jsonRequest, res, "missing_integer", integer, "integer", |
| requiredInteger)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::ok); |
| EXPECT_TRUE(res.jsonValue.empty()); |
| EXPECT_EQ(integer, std::nullopt); |
| |
| ASSERT_TRUE(readJson(jsonRequest, res, "missing_string", str0, "integer", |
| requiredInteger)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::ok); |
| EXPECT_THAT(res.jsonValue, IsEmpty()); |
| EXPECT_EQ(str0, std::nullopt); |
| |
| ASSERT_TRUE(readJson(jsonRequest, res, "integer", integer, "string", str0, |
| "vector", vec)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::ok); |
| EXPECT_THAT(res.jsonValue, IsEmpty()); |
| EXPECT_EQ(integer, 1); |
| EXPECT_EQ(str0, std::nullopt); |
| EXPECT_EQ(vec, std::nullopt); |
| |
| ASSERT_TRUE(readJson(jsonRequest, res, "integer", integer, "string0", str0, |
| "missing_string", str1)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::ok); |
| EXPECT_THAT(res.jsonValue, IsEmpty()); |
| EXPECT_EQ(str1, std::nullopt); |
| } |
| |
| TEST(ReadJson, InvalidMissingElementReturnsFalse) { |
| crow::Response res; |
| nlohmann::json jsonRequest = {{"integer", 1}, {"string", "hello"}}; |
| |
| int integer = 0; |
| std::string str0; |
| std::string str1; |
| std::vector<uint8_t> vec; |
| ASSERT_FALSE(readJson(jsonRequest, res, "missing_integer", integer)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::bad_request); |
| EXPECT_THAT(res.jsonValue, Not(IsEmpty())); |
| |
| ASSERT_FALSE(readJson(jsonRequest, res, "missing_string", str0)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::bad_request); |
| EXPECT_THAT(res.jsonValue, Not(IsEmpty())); |
| |
| ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string", str0, |
| "vector", vec)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::bad_request); |
| EXPECT_THAT(res.jsonValue, Not(IsEmpty())); |
| |
| ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string0", str0, |
| "missing_string", str1)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::bad_request); |
| EXPECT_THAT(res.jsonValue, Not(IsEmpty())); |
| } |
| |
| TEST(ReadJsonPatch, ValidElementsReturnsTrueResponseOkValuesUnpackedCorrectly) { |
| crow::Response res; |
| std::error_code ec; |
| crow::Request req("{\"integer\": 1}", ec); |
| |
| // Ignore errors intentionally |
| req.req.set(boost::beast::http::field::content_type, "application/json"); |
| |
| int64_t integer = 0; |
| ASSERT_TRUE(readJsonPatch(req, res, "integer", integer)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::ok); |
| EXPECT_THAT(res.jsonValue, IsEmpty()); |
| EXPECT_EQ(integer, 1); |
| } |
| |
| TEST(ReadJsonPatch, EmptyObjectReturnsFalseResponseBadRequest) { |
| crow::Response res; |
| std::error_code ec; |
| crow::Request req("{}", ec); |
| // Ignore errors intentionally |
| |
| std::optional<int64_t> integer = 0; |
| ASSERT_FALSE(readJsonPatch(req, res, "integer", integer)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::bad_request); |
| EXPECT_THAT(res.jsonValue, Not(IsEmpty())); |
| } |
| |
| TEST(ReadJsonPatch, OdataIgnored) { |
| crow::Response res; |
| std::error_code ec; |
| crow::Request req(R"({"@odata.etag": "etag", "integer": 1})", ec); |
| req.req.set(boost::beast::http::field::content_type, "application/json"); |
| // Ignore errors intentionally |
| |
| std::optional<int64_t> integer = 0; |
| ASSERT_TRUE(readJsonPatch(req, res, "integer", integer)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::ok); |
| EXPECT_THAT(res.jsonValue, IsEmpty()); |
| EXPECT_EQ(integer, 1); |
| } |
| |
| TEST(ReadJsonPatch, OnlyOdataGivesNoOperation) { |
| crow::Response res; |
| std::error_code ec; |
| crow::Request req(R"({"@odata.etag": "etag"})", ec); |
| // Ignore errors intentionally |
| |
| std::optional<int64_t> integer = 0; |
| ASSERT_FALSE(readJsonPatch(req, res, "integer", integer)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::bad_request); |
| EXPECT_THAT(res.jsonValue, Not(IsEmpty())); |
| } |
| |
| TEST(ReadJsonAction, |
| ValidElementsReturnsTrueResponseOkValuesUnpackedCorrectly) { |
| crow::Response res; |
| std::error_code ec; |
| crow::Request req("{\"integer\": 1}", ec); |
| req.req.set(boost::beast::http::field::content_type, "application/json"); |
| // Ignore errors intentionally |
| |
| int64_t integer = 0; |
| ASSERT_TRUE(readJsonAction(req, res, "integer", integer)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::ok); |
| EXPECT_THAT(res.jsonValue, IsEmpty()); |
| EXPECT_EQ(integer, 1); |
| } |
| |
| TEST(ReadJsonAction, EmptyObjectReturnsTrueResponseOk) { |
| crow::Response res; |
| std::error_code ec; |
| crow::Request req({"{}"}, ec); |
| req.req.set(boost::beast::http::field::content_type, "application/json"); |
| // Ignore errors intentionally |
| |
| std::optional<int64_t> integer = 0; |
| ASSERT_TRUE(readJsonAction(req, res, "integer", integer)); |
| EXPECT_EQ(res.result(), boost::beast::http::status::ok); |
| EXPECT_THAT(res.jsonValue, IsEmpty()); |
| } |
| |
| TEST(SortJsonArrayByKey, ElementValueNotStringReturnsFalse) { |
| JsonArray array = R"([{"foo" : 100}, {"bar": "1"}, {"foo" : "20"}])"_json; |
| JsonArray expectedArray = array; |
| ASSERT_FALSE(sortJsonArrayByKey("foo", array)); |
| // No sorting is performed if any element value is not string. |
| EXPECT_EQ(array, expectedArray); |
| } |
| |
| TEST(SortJsonArrayByKey, ElementMissingKeyReturnsFalseArrayIsSorted) { |
| JsonArray array = R"([{"foo" : "100"}, {"bar": "1"}, {"foo" : "20"}])"_json; |
| ASSERT_FALSE(sortJsonArrayByKey("foo", array)); |
| // Objects with other keys are always larger than those with the specified |
| // key. |
| JsonArray expectedArray = |
| R"([{"bar": "1"}, {"foo" : "20"}, {"foo" : "100"}])"_json; |
| EXPECT_EQ(array, expectedArray); |
| } |
| |
| TEST(SortJsonArrayByKey, SortedByStringValueOnSuccessArrayIsSorted) { |
| JsonArray array = R"([{"foo": "20"}, {"foo" : "3"}, {"foo" : "100"}])"_json; |
| ASSERT_TRUE(sortJsonArrayByKey("foo", array)); |
| JsonArray expectedArray = |
| R"([{"foo": "3"}, {"foo" : "20"}, {"foo" : "100"}])"_json; |
| EXPECT_EQ(array, expectedArray); |
| } |
| |
| TEST( |
| SortJsonArrayByKey, |
| ComplicatedElementMixtureOfTargetKeyAndNoTargetKeyWithStringValueReturnsFalseArrayIsSorted) { |
| JsonArray array = R"([ |
| { |
| "foo": "100", |
| "reading": 1 |
| }, |
| { |
| "bar": "1", |
| "reading": 21 |
| }, |
| { |
| "foo": "20", |
| "reading": 31 |
| } |
| ])"_json; |
| ASSERT_FALSE(sortJsonArrayByKey("foo", array)); |
| // Objects with other keys are always smaller than those with the specified |
| // key. |
| JsonArray expectedArray = |
| R"([ |
| { |
| "bar": "1", |
| "reading": 21 |
| }, |
| { |
| "foo": "20", |
| "reading": 31 |
| }, |
| { |
| "foo": "100", |
| "reading": 1 |
| } |
| ])"_json; |
| EXPECT_EQ(array, expectedArray); |
| } |
| |
| TEST( |
| SortJsonArrayByKey, |
| ComplicatedElementMixtureOfTargetKeyAndNoTargetKeyWithNonStringValueReturnsFalseArrayIsNotSorted) { |
| JsonArray array = R"([ |
| { |
| "foo": "100", |
| "reading": 1 |
| }, |
| { |
| "bar": 1111, |
| "reading": 21 |
| }, |
| { |
| "foo": "20", |
| "reading": 31 |
| } |
| ])"_json; |
| JsonArray expectedArray = array; |
| ASSERT_FALSE(sortJsonArrayByKey("foo", array)); |
| EXPECT_EQ(array, expectedArray); |
| } |
| |
| TEST(SortJsonArrayByKey, RealMemoryArrayReturnsTrueArrayIsSorted) { |
| JsonArray array = R"( |
| [ |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm31", |
| "Id": "dimm31" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm30", |
| "Id": "dimm30" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm29", |
| "Id": "dimm29" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm28", |
| "Id": "dimm28" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm27", |
| "Id": "dimm27" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm26", |
| "Id": "dimm26" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm25", |
| "Id": "dimm25" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm24", |
| "Id": "dimm24" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm23", |
| "Id": "dimm23" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm22", |
| "Id": "dimm22" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm21", |
| "Id": "dimm21" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm20", |
| "Id": "dimm20" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm19", |
| "Id": "dimm19" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm18", |
| "Id": "dimm18" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm17", |
| "Id": "dimm17" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm16", |
| "Id": "dimm16" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm15", |
| "Id": "dimm15" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm14", |
| "Id": "dimm14" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm13", |
| "Id": "dimm13" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm12", |
| "Id": "dimm12" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm11", |
| "Id": "dimm11" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm10", |
| "Id": "dimm10" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm9", |
| "Id": "dimm9" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm8", |
| "Id": "dimm8" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm7", |
| "Id": "dimm7" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm6", |
| "Id": "dimm6" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm5", |
| "Id": "dimm5" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm4", |
| "Id": "dimm4" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm3", |
| "Id": "dimm3" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm2", |
| "Id": "dimm2" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm1", |
| "Id": "dimm1" |
| }, |
| { |
| "@odata.id": "/redfish/v1/Systems/system/Memory/dimm0", |
| "Id": "dimm0" |
| } |
| ] |
| )"_json; |
| |
| // Sort the array by "@odata.id" and check the order. |
| ASSERT_TRUE(json_util::sortJsonArrayByKey("@odata.id", array)); |
| for (size_t i = 0; i < array.size(); ++i) { |
| EXPECT_EQ(array[i]["@odata.id"], |
| "/redfish/v1/Systems/system/Memory/dimm" + std::to_string(i)); |
| } |
| } |
| |
| } // namespace |
| } // namespace redfish::json_util |