osb/source/test/json_test.cpp
2023-06-20 14:33:09 +10:00

704 lines
19 KiB
C++

#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");
}