diff options
Diffstat (limited to 'source/test/json_test.cpp')
-rw-r--r-- | source/test/json_test.cpp | 703 |
1 files changed, 703 insertions, 0 deletions
diff --git a/source/test/json_test.cpp b/source/test/json_test.cpp new file mode 100644 index 0000000..f95c276 --- /dev/null +++ b/source/test/json_test.cpp @@ -0,0 +1,703 @@ +#include "StarJson.hpp" +#include "StarFile.hpp" +#include "StarJsonPatch.hpp" +#include "StarJsonPath.hpp" + +#include "gtest/gtest.h" + +using namespace Star; + +TEST(JsonTest, ImplicitSharing) { + Json map1 = JsonObject{{"foo", 1}, {"bar", 10}}; + + Json map2 = JsonObject{{"foo", 5}, {"bar", 50}}; + + std::swap(map1, map2); + Json map3 = map1; + map1 = map2; + map2 = map3; + + EXPECT_EQ(map1.get("foo"), 1); + EXPECT_EQ(map2.get("bar"), 50); +} + +TEST(JsonTest, Defaults) { + Json obj = JsonObject{{"null", Json()}}; + Json arr = JsonArray{"array", JsonArray{Json(), Json()}}; + + EXPECT_EQ(obj.getInt("null", 5), 5); + EXPECT_EQ(arr.getInt(2, 5), 5); + EXPECT_EQ(arr.getInt(3, 5), 5); + EXPECT_THROW(arr.getInt(2), JsonException); +} + +TEST(JsonTest, Merging) { + JsonObject a{{"I", "feel"}, {"friendly", "now"}}; + JsonObject b{{"hello", "there"}, {"leg", "friend"}}; + JsonObject c{{"hello", "you"}, {"leg", "fiend"}}; + JsonObject d{{"goodbye", "you"}, {"friendly", "leg"}}; + + Json merged = jsonMerge(a, b, c, d); + + EXPECT_EQ(merged.get("I"), "feel"); + EXPECT_EQ(merged.get("hello"), "you"); + EXPECT_EQ(merged.get("friendly"), "leg"); + EXPECT_EQ(merged.get("leg"), "fiend"); + + Json e = JsonObject(); + e = e.set("1", 2); + e = e.setAll({{"a", "b"}, {"c", "d"}}); + Json f = JsonObject{{"1", 2}, {"a", "b"}, {"c", "d"}}; + + EXPECT_EQ(e, f); + + Json g = JsonObject{{"a", "a"}, {"sub", JsonObject()}}; + g = g.setPath("sub.field", 1); + g = g.setPath("sub.field2", 2); + g = g.erasePath("sub.field2"); + Json h = JsonObject{{"a", "a"}, {"sub", JsonObject{{"field", 1}}}}; + EXPECT_EQ(g, h); +} + +TEST(JsonTest, Unicode) { + Json v = Json::parse("{ \"first\" : \"日本語\", \"second\" : \"foobar\\u0019\" }"); + EXPECT_EQ(v.getString("first"), String("日本語")); + EXPECT_EQ(v.get("second").repr(), String("\"foobar\\u0019\"")); + + String json = v.printJson(); + Json v2 = Json::parseJson(json); + EXPECT_EQ(v2.getString("first"), String("日本語")); + + EXPECT_EQ(v, v2); + + EXPECT_EQ(Json("😀"), Json::parse("\"\\ud83d\\ude00\"")); + EXPECT_EQ(Json::parse("\"\\ud83d\\ude00\"").toString().size(), 1u); +} + +TEST(JsonTest, UnicodeFile) { + Json v = Json::parse("{ \"first\" : \"日本語\", \"second\" : \"foobar\\u0019\" }"); + EXPECT_EQ(v.getString("first"), String("日本語")); + EXPECT_EQ(v.get("second").repr(), String("\"foobar\\u0019\"")); + + String file = File::temporaryFileName(); + auto finallyGuard = finally([&file]() { File::remove(file); }); + + File::writeFile(v.printJson(), file); + Json v2 = Json::parseJson(File::readFileString(file)); + EXPECT_EQ(v2.getString("first"), "日本語"); + + EXPECT_EQ(v, v2); +} + +TEST(JsonTest, JsonParsingEdge) { + auto isValidFragment = [](String const& json) -> bool { + try { + Json::parse(json); + return true; + } catch (JsonParsingException const&) { + return false; + } + }; + + auto isValidJson = [](String const& json) -> bool { + try { + Json::parseJson(json); + return true; + } catch (JsonParsingException const&) { + return false; + } + }; + + EXPECT_TRUE(isValidFragment(" \t 0.0 ")); + EXPECT_TRUE(isValidFragment("-0.0\t ")); + EXPECT_FALSE(isValidFragment("-.0")); + EXPECT_FALSE(isValidFragment("00.0")); + + EXPECT_FALSE(isValidJson(" 0.0")); + EXPECT_FALSE(isValidJson("true")); + EXPECT_TRUE(isValidJson("\t[]")); + EXPECT_TRUE(isValidJson(" {} ")); +} + +TEST(JsonTest, Types) { + Json v; + EXPECT_EQ(v.type(), Json::Type::Null); + v = 0; + EXPECT_EQ(v.type(), Json::Type::Int); + v = 0.0; + EXPECT_EQ(v.type(), Json::Type::Float); + v = true; + EXPECT_EQ(v.type(), Json::Type::Bool); + v = ""; + EXPECT_EQ(v.type(), Json::Type::String); + v = JsonArray(); + EXPECT_EQ(v.type(), Json::Type::Array); + v = JsonObject(); + EXPECT_EQ(v.type(), Json::Type::Object); +} + +TEST(JsonTest, Query) { + Json v = Json::parse(R"JSON( + { + "foo" : "bar", + "baz" : { + "baf" : [1, 2], + "bal" : 2 + }, + + "baf" : null + } + )JSON"); + + EXPECT_EQ(v.query("foo"), Json("bar")); + EXPECT_EQ(v.query("baz.baf[1]"), Json(2)); + EXPECT_EQ(v.query("baz.bal"), Json(2)); + EXPECT_EQ(v.query("blargh", Json("default")), Json("default")); + EXPECT_EQ(v.query("blargh", Json("default")), Json("default")); + EXPECT_EQ(v.query("baz.baf[3]", Json("default")), Json("default")); + EXPECT_EQ(v.query("baz.bal[0]", Json("default")), Json("default")); + EXPECT_EQ(v.query("baz[1]", Json("default")), Json("default")); + EXPECT_EQ(v.query("baz.bal.a", Json("default")), Json("default")); + EXPECT_EQ(v.query("baz[0]", Json("default")), Json("default")); + EXPECT_EQ(v.query("baz.baf.a", Json("default")), Json("default")); + EXPECT_THROW(v.query("blargh"), JsonPath::TraversalException); + EXPECT_THROW(v.query("baz.funk"), JsonPath::TraversalException); + EXPECT_THROW(v.query("baz.baf[3]"), JsonPath::TraversalException); + EXPECT_THROW(v.query("baz.baf[whee]", Json()), JsonPath::ParsingException); + EXPECT_THROW(v.query("baz.baf[[]", Json()), JsonPath::ParsingException); + EXPECT_THROW(v.query("baz..baf", Json()), JsonPath::ParsingException); + EXPECT_THROW(v.query("baf.nothing"), JsonException); +} + +TEST(JsonTest, PatchingAdd) { + Json before = Json::parse(R"JSON( + { + "foo" : "bar", + "baz" : { + "baf" : 1, + "bal" : 2 + }, + "rab" : [0, 1, 2, "foo", false] + } + )JSON"); + + Json after = Json::parse(R"JSON( + { + "foo" : "xyzzy", + "bar" : "foo", + "baz" : { + "baf" : 1, + "bal" : 2, + "0" : "derp", + "rebar" : { + "after" : "party" + } + }, + "rab" : [0, 0, 1, 2, "foo", false, true, { "baz" : "bar"} ] + } + )JSON"); + + Json patch = Json::parse(R"JSON( + [ + {"op" : "add", "path" : "/foo", "value" : "xyzzy"}, + {"op" : "add", "path" : "/bar", "value" : "foo"}, + {"op" : "add", "path" : "/baz/rebar", "value" : {}}, + {"op" : "add", "path" : "/baz/rebar/after", "value" : "party"}, + {"op" : "add", "path" : "/baz/0", "value" : "derp"}, + {"op" : "add", "path" : "/rab/0", "value" : 0}, + {"op" : "add", "path" : "/rab/6", "value" : true}, + {"op" : "add", "path" : "/rab/-", "value" : {"baz" : "bar"} } + ] + )JSON"); + + // Past end of list + Json badPatch1 = Json::parse(R"JSON( + [ + {"op" : "add", "path" : "/rab/6", "value" : {"baz" : "bar"} } + ] + )JSON"); + + // Parent does not exist, map + Json badPatch2 = Json::parse(R"JSON( + [ + {"op" : "add", "path" : "/bar/baz", "value" : {"baz" : "bar"} } + ] + )JSON"); + + // Parent does not exist, list + Json badPatch3 = Json::parse(R"JSON( + [ + {"op" : "add", "path" : "/bar/0", "value" : {"baz" : "bar"} } + ] + )JSON"); + + EXPECT_EQ(jsonPatch(before, patch.toArray()), after); + ASSERT_THROW(jsonPatch(before, badPatch1.toArray()), JsonPatchException); + ASSERT_THROW(jsonPatch(before, badPatch2.toArray()), JsonPatchException); + ASSERT_THROW(jsonPatch(before, badPatch3.toArray()), JsonPatchException); +} + +TEST(JsonTest, PatchingRemove) { + Json before = Json::parse(R"JSON( + { + "foo" : "xyzzy", + "bar" : "foo", + "baz" : { + "baf" : 1, + "bal" : 2, + "rebar" : true + }, + "rab" : [0, 0, 1, 2, "foo", false, {"baz" : "bar"} ] + } + )JSON"); + + Json after = Json::parse(R"JSON( + { + "bar" : "foo", + "baz" : { + "baf" : 1, + "bal" : 2 + }, + "rab" : [0, 1, 2, "foo", false] + } + )JSON"); + + Json patch = Json::parse(R"JSON( + [ + {"op" : "remove", "path" : "/foo"}, + {"op" : "remove", "path" : "/baz/rebar"}, + {"op" : "remove", "path" : "/rab/0"}, + {"op" : "remove", "path" : "/rab/5"} + ] + )JSON"); + + // Removing end of list + Json badPatch1 = Json::parse(R"JSON( + [ + {"op" : "remove", "path" : "/rab/-"} + ] + )JSON"); + + // Removing past end of list + Json badPatch2 = Json::parse(R"JSON( + [ + {"op" : "add", "path" : "/rab/7"} + ] + )JSON"); + + // Path wrong type + Json badPatch3 = Json::parse(R"JSON( + [ + {"op" : "remove", "path" : "/bar/baz"} + ] + )JSON"); + + EXPECT_EQ(jsonPatch(before, patch.toArray()), after); + ASSERT_THROW(jsonPatch(before, badPatch1.toArray()), JsonPatchException); + ASSERT_THROW(jsonPatch(before, badPatch2.toArray()), JsonPatchException); + ASSERT_THROW(jsonPatch(before, badPatch3.toArray()), JsonPatchException); +} + +TEST(JsonTest, PatchingReplace) { + Json before = Json::parse(R"JSON( + { + "foo" : "bar", + "bar" : { + "baf" : 1, + "bal" : 2 + }, + "baz" : { + "baf" : 1, + "bal" : 2 + }, + "rab" : [0, 1, 2, "foo", false], + "rabby" : [0, 1, 2, "foo", false] + } + )JSON"); + + Json after = Json::parse(R"JSON( + { + "foo" : "xyzzy", + "bar" : [3, 2, 1, "contact"], + "baz" : { + "baf" : 1, + "bal" : "touched" + }, + "rab" : [{"omg" : "no"}, 1, 2, "foo", false], + "rabby" : false + } + )JSON"); + + Json patch = Json::parse(R"JSON( + [ + {"op" : "replace", "path" : "/foo", "value" : "xyzzy"}, + {"op" : "replace", "path" : "/bar", "value" : [3, 2, 1, "contact"]}, + {"op" : "replace", "path" : "/baz/bal", "value" : "touched"}, + {"op" : "replace", "path" : "/rab/0", "value" : {"omg" : "yes"}}, + {"op" : "replace", "path" : "/rab/0/omg", "value" : "no"}, + {"op" : "replace", "path" : "/rab/2", "value" : 2}, + {"op" : "replace", "path" : "/rabby", "value" : false} + ] + )JSON"); + + // End of list + Json badPatch1 = Json::parse(R"JSON( + [ + {"op" : "replace", "path" : "/rab/-", "value" : {"baz" : "bar"} } + ] + )JSON"); + + // Past end of list + Json badPatch2 = Json::parse(R"JSON( + [ + {"op" : "replace", "path" : "/rab/5", "value" : {"baz" : "bar"} } + ] + )JSON"); + + // Key does not exist + Json badPatch3 = Json::parse(R"JSON( + [ + {"op" : "replace", "path" : "/bar/baz", "value" : {"baz" : "bar"} } + ] + )JSON"); + + EXPECT_EQ(jsonPatch(before, patch.toArray()), after); + ASSERT_THROW(jsonPatch(before, badPatch1.toArray()), JsonPatchException); + ASSERT_THROW(jsonPatch(before, badPatch2.toArray()), JsonPatchException); + ASSERT_THROW(jsonPatch(before, badPatch3.toArray()), JsonPatchException); +} + +TEST(JsonTest, PatchingMove) { + Json before = Json::parse(R"JSON( + { + "foo" : "bar", + "bar" : [1, 2, 3, "contact"], + "baz" : { + "baf" : 1, + "bar" : 2 + }, + "rab" : [0, 1, 2, "foo", false], + "rabby" : [0, 1, 2, "foo", true] + } + )JSON"); + + Json after = Json::parse(R"JSON( + { + "foot" : "bar", + "baz" : { + "baf" : 1, + "bar" : [3, 2, 1, "contact"] + }, + "bar" : 2, + "rab" : [0, 1, 2, true, "foo"] + } + )JSON"); + + Json patch = Json::parse(R"JSON( + [ + {"op" : "move", "from" : "/foo", "path" : "/foot"}, + {"op" : "move", "from" : "/bar", "path" : "/baz/bal"}, + {"op" : "move", "from" : "/baz/bar", "path" : "/bar"}, + {"op" : "move", "from" : "/baz/bal", "path" : "/baz/bar"}, + {"op" : "move", "from" : "/baz/bar/0", "path" : "/baz/bar/1"}, + {"op" : "move", "from" : "/baz/bar/2", "path" : "/baz/bar/0"}, + {"op" : "move", "from" : "/rabby", "path" : "/rab"}, + {"op" : "move", "from" : "/rab/3", "path" : "/rab/-"} + ] + )JSON"); + + // From end of list + Json badPatch1 = Json::parse(R"JSON( + [ + {"op" : "move", "from" : "/rab/-", "path" : "/doesnotmatter"} + ] + )JSON"); + + // From past end of list + Json badPatch2 = Json::parse(R"JSON( + [ + {"op" : "move", "from" : "/rab/5", "path" : "/doesnotmatter"} + ] + )JSON"); + + // To past end of list + Json badPatch3 = Json::parse(R"JSON( + [ + {"op" : "move", "from" : "/rab/0", "path" : "/rab/5"} + ] + )JSON"); + + // Source path does not exist + Json badPatch4 = Json::parse(R"JSON( + [ + {"op" : "move", "from" : "/omgomg", "path" : "/doesntmatter"} + ] + )JSON"); + + // Dest path wrong type + Json badPatch5 = Json::parse(R"JSON( + [ + {"op" : "move", "from" : "/baz/bar", "path" : "/rabby/bar"} + ] + )JSON"); + + EXPECT_EQ(jsonPatch(before, patch.toArray()), after); + ASSERT_THROW(jsonPatch(before, badPatch1.toArray()), JsonPatchException); + ASSERT_THROW(jsonPatch(before, badPatch2.toArray()), JsonPatchException); + ASSERT_THROW(jsonPatch(before, badPatch3.toArray()), JsonPatchException); + ASSERT_THROW(jsonPatch(before, badPatch4.toArray()), JsonPatchException); + ASSERT_THROW(jsonPatch(before, badPatch5.toArray()), JsonPatchException); +} + +TEST(JsonTest, PatchingCopy) { + Json before = Json::parse(R"JSON( + { + "foo" : "bar", + "foot" : "bar", + "bar" : [1, 2, 3, "contact"], + "baz" : { + "baf" : 1, + "bar" : 2 + }, + "rab" : [0, 1, 2, "foo", false], + "rabby" : [0, 1, 2, "foo", true] + } + )JSON"); + + Json after = Json::parse(R"JSON( + { + "foo" : "bar", + "foot" : "bar", + "baz" : { + "baf" : 1, + "bar" : [2, 1, 1, 2, 3, "contact"], + "bal" : [1, 2, 3, "contact"] + }, + "bar" : 2, + "rab" : [0, 1, 2, "foo", true, "foo"], + "rabby" : [0, 1, 2, "foo", true] + } + )JSON"); + + Json patch = Json::parse(R"JSON( + [ + {"op" : "copy", "from" : "/foo", "path" : "/foot"}, + {"op" : "copy", "from" : "/bar", "path" : "/baz/bal"}, + {"op" : "copy", "from" : "/baz/bar", "path" : "/bar"}, + {"op" : "copy", "from" : "/baz/bal", "path" : "/baz/bar"}, + {"op" : "copy", "from" : "/baz/bar/0", "path" : "/baz/bar/1"}, + {"op" : "copy", "from" : "/baz/bar/2", "path" : "/baz/bar/0"}, + {"op" : "copy", "from" : "/rabby", "path" : "/rab"}, + {"op" : "copy", "from" : "/rab/3", "path" : "/rab/-"} + ] + )JSON"); + + // From end of list + Json badPatch1 = Json::parse(R"JSON( + [ + {"op" : "copy", "from" : "/rab/-", "path" : "/doesnotmatter"} + ] + )JSON"); + + // From past end of list + Json badPatch2 = Json::parse(R"JSON( + [ + {"op" : "copy", "from" : "/rab/5", "path" : "/doesnotmatter"} + ] + )JSON"); + + // To past end of list + Json badPatch3 = Json::parse(R"JSON( + [ + {"op" : "copy", "from" : "/rab/0", "path" : "/rab/6"} + ] + )JSON"); + + // Source path does not exist + Json badPatch4 = Json::parse(R"JSON( + [ + {"op" : "copy", "from" : "/omgomg", "path" : "/doesntmatter"} + ] + )JSON"); + + // Dest path wrong type + Json badPatch5 = Json::parse(R"JSON( + [ + {"op" : "copy", "from" : "/baz/bar", "path" : "/rabby/bar"} + ] + )JSON"); + + EXPECT_EQ(jsonPatch(before, patch.toArray()), after); + ASSERT_THROW(jsonPatch(before, badPatch1.toArray()), JsonPatchException); + ASSERT_THROW(jsonPatch(before, badPatch2.toArray()), JsonPatchException); + ASSERT_THROW(jsonPatch(before, badPatch3.toArray()), JsonPatchException); + ASSERT_THROW(jsonPatch(before, badPatch4.toArray()), JsonPatchException); + ASSERT_THROW(jsonPatch(before, badPatch5.toArray()), JsonPatchException); +} + +TEST(JsonTest, PatchingTest) { + Json base = Json::parse(R"JSON( + { + "foo" : "bar", + "foot" : "bart", + "bar" : [1, 2, 3, "contact"], + "baz" : { + "baf" : 1, + "bar" : 2, + "0" : 3 + } + } + )JSON"); + + Json goodTest = Json::parse(R"JSON( + [ + {"op" : "test", "path" : "/foo", "value" : "bar"}, + {"op" : "test", "path" : "/foo", "value" : "bark", "inverse" : true}, + {"op" : "test", "path" : "/foot", "value" : "bart"}, + {"op" : "test", "path" : "/bar", "value" : [1, 2, 3, "contact"]}, + {"op" : "test", "path" : "/bar/0", "value" : 1}, + {"op" : "test", "path" : "/bar/1", "value" : 2}, + {"op" : "test", "path" : "/bar/2", "value" : 3}, + {"op" : "test", "path" : "/bar/3", "value" : "contact"}, + {"op" : "test", "path" : "/baz", "value" : {"0" : 3, "baf" : 1, "bar" : 2}}, + {"op" : "test", "path" : "/baz/baf", "value" : 1}, + {"op" : "test", "path" : "/baz/bar", "value" : 2}, + {"op" : "test", "path" : "/baz/0", "value" : 3}, + {"op" : "test", "path" : "/nothere", "inverse" : true}, + {"op" : "test", "path" : "/foo" } + ] + )JSON"); + + Json failTest1 = Json::parse(R"JSON( + [ + {"op" : "test", "path" : "/bar", "value" : [1, 3, 2, "contact"]} + ] + )JSON"); + + Json failTest2 = Json::parse(R"JSON( + [ + {"op" : "test", "path" : "/bar/-", "value" : "contact"} + ] + )JSON"); + + Json failTest3 = Json::parse(R"JSON( + [ + {"op" : "test", "path" : "/xyzzy", "value" : null} + ] + )JSON"); + + Json failTest4 = Json::parse(R"JSON( + [ + {"op" : "test", "path" : "/xyzzy/zop", "value" : null} + ] + )JSON"); + + Json failTest5 = Json::parse(R"JSON( + [ + {"op" : "test", "path" : "/nothere" } + ] + )JSON"); + + Json failTest6 = Json::parse(R"JSON( + [ + {"op" : "test", "path" : "/bar", "inverse" : true } + ] + )JSON"); + + Json failTest7 = Json::parse(R"JSON( + [ + {"op" : "test", "path" : "/foo", "value" : "bar", "inverse" : true } + ] + )JSON"); + + jsonPatch(base, goodTest.toArray()); + + ASSERT_THROW(jsonPatch(base, failTest1.toArray()), JsonPatchTestFail); + ASSERT_THROW(jsonPatch(base, failTest2.toArray()), JsonPatchTestFail); + ASSERT_THROW(jsonPatch(base, failTest3.toArray()), JsonPatchTestFail); + ASSERT_THROW(jsonPatch(base, failTest4.toArray()), JsonPatchTestFail); + ASSERT_THROW(jsonPatch(base, failTest5.toArray()), JsonPatchTestFail); + ASSERT_THROW(jsonPatch(base, failTest6.toArray()), JsonPatchTestFail); + ASSERT_THROW(jsonPatch(base, failTest7.toArray()), JsonPatchTestFail); +} + +TEST(JsonTest, PatchingEscaping) { + Json base1 = Json::parse(R"JSON( + { + "~" : true, + "/" : false, + "~~0" : "foo", + "~~1" : "bar", + "~~0~1/~0~" : "ugh" + } + )JSON"); + + Json test1 = Json::parse(R"JSON( + [ + {"op" : "test", "path" : "/~0", "value" : true}, + {"op" : "test", "path" : "/~1", "value" : false}, + {"op" : "test", "path" : "/~0~00", "value" : "foo"}, + {"op" : "test", "path" : "/~0~01", "value" : "bar"}, + {"op" : "test", "path" : "/~0~00~01~1~00~0", "value" : "ugh"} + ] + )JSON"); + + jsonPatch(base1, test1.toArray()); +} + +TEST(JsonTest, MergeQuery) { + Json json1 = Json::parse(R"JSON( + { + "foo" : "foo1", + "bar" : "bar1", + "baz" : { + "1" : "1" + }, + "fob" : {}, + "fizz" : 4 + } + )JSON"); + Json json2 = Json::parse(R"JSON( + { + "foo" : "foo2", + "bar" : "bar2", + "baz" : null, + "baf" : { + "2" : "2" + }, + "fob" : 2 + } + )JSON"); + Json json3 = Json::parse(R"JSON( + { + "baz" : { + "3" : "3" + }, + "baf" : { + "3" : "3" + }, + "fizz" : { + } + } + )JSON"); + + auto testIdentical = [&](String const& key) { + EXPECT_EQ(jsonMergeQuery(key, json1, json2, json3), jsonMerge(json1, json2, json3).query(key, {})); + }; + + testIdentical("foo"); + testIdentical("bar"); + testIdentical("baz"); + testIdentical("baf"); + testIdentical("baz.1"); + testIdentical("baz.2"); + testIdentical("baz.3"); + testIdentical("baf.0"); + testIdentical("baf.2"); + testIdentical("baf.3"); + testIdentical("baz.blip"); + testIdentical("boo.blip"); + testIdentical("fob"); + testIdentical("fiz"); + testIdentical("nothing"); +} |