]> git.ipfire.org Git - thirdparty/json-c.git/commitdiff
tests: test_json_patch: add test suite for JSON patch
authorAlexandru Ardelean <ardeleanalex@gmail.com>
Tue, 20 Apr 2021 12:47:18 +0000 (15:47 +0300)
committerEric Hawicz <erh+git@nimenees.com>
Tue, 1 Aug 2023 02:18:03 +0000 (22:18 -0400)
Essentially, this change adds the test cases from this repo:
   https://github.com/json-patch/json-patch-tests

Specifically:
   https://github.com/json-patch/json-patch-tests/blob/master/spec_tests.json
   https://github.com/json-patch/json-patch-tests/blob/master/tests.json

The files were taken at the date of this commit, at git hash
  ea3af85790cb72893d0676597814b7532019c24e

Some tests may not have an 'expected' or 'error' field. Those are ignored.
One test was disabled manually via "disabled_in_json_c", because it tries
an impossible test, i.e. to add 2 "op" fields in the same patch entry,
something which is impossible in a JSON object.

For the 'error' cases, right now we only test that an error happens.
Later, we can extend this to check the error codes.

Signed-off-by: Alexandru Ardelean <ardeleanalex@gmail.com>
tests/CMakeLists.txt
tests/json_patch_spec_tests.json [new file with mode: 0644]
tests/json_patch_tests.json [new file with mode: 0644]
tests/test_json_patch.c [new file with mode: 0644]
tests/test_json_patch.expected [new file with mode: 0644]
tests/test_json_patch.test [new file with mode: 0755]

index bdee375e8355ce8cfe1309ad4c5a581cb61e1964..f54840d59039e7d73b1ae16f1ca638ba22950cf1 100644 (file)
@@ -39,6 +39,9 @@ set(ALL_TEST_NAMES
 
 if (NOT DISABLE_JSON_POINTER)
     set(ALL_TEST_NAMES ${ALL_TEST_NAMES} test_json_pointer)
+    if (NOT DISABLE_JSON_PATCH)
+        set(ALL_TEST_NAMES ${ALL_TEST_NAMES} test_json_patch)
+    endif()
 endif()
 
 foreach(TESTNAME ${ALL_TEST_NAMES})
diff --git a/tests/json_patch_spec_tests.json b/tests/json_patch_spec_tests.json
new file mode 100644 (file)
index 0000000..c160535
--- /dev/null
@@ -0,0 +1,233 @@
+[
+  {
+    "comment": "4.1. add with missing object",
+    "doc": { "q": { "bar": 2 } },
+    "patch": [ {"op": "add", "path": "/a/b", "value": 1} ],
+    "error":
+       "path /a does not exist -- missing objects are not created recursively"
+  },
+
+  {
+    "comment": "A.1.  Adding an Object Member",
+    "doc": {
+  "foo": "bar"
+},
+    "patch": [
+  { "op": "add", "path": "/baz", "value": "qux" }
+],
+    "expected": {
+  "baz": "qux",
+  "foo": "bar"
+}
+  },
+
+  {
+    "comment": "A.2.  Adding an Array Element",
+    "doc": {
+  "foo": [ "bar", "baz" ]
+},
+    "patch": [
+  { "op": "add", "path": "/foo/1", "value": "qux" }
+],
+    "expected": {
+  "foo": [ "bar", "qux", "baz" ]
+}
+  },
+
+  {
+    "comment": "A.3.  Removing an Object Member",
+    "doc": {
+  "baz": "qux",
+  "foo": "bar"
+},
+    "patch": [
+  { "op": "remove", "path": "/baz" }
+],
+    "expected": {
+  "foo": "bar"
+}
+  },
+
+  {
+    "comment": "A.4.  Removing an Array Element",
+    "doc": {
+  "foo": [ "bar", "qux", "baz" ]
+},
+    "patch": [
+  { "op": "remove", "path": "/foo/1" }
+],
+    "expected": {
+  "foo": [ "bar", "baz" ]
+}
+  },
+
+  {
+    "comment": "A.5.  Replacing a Value",
+    "doc": {
+  "baz": "qux",
+  "foo": "bar"
+},
+    "patch": [
+  { "op": "replace", "path": "/baz", "value": "boo" }
+],
+    "expected": {
+  "baz": "boo",
+  "foo": "bar"
+}
+  },
+
+  {
+    "comment": "A.6.  Moving a Value",
+    "doc": {
+  "foo": {
+    "bar": "baz",
+    "waldo": "fred"
+  },
+  "qux": {
+    "corge": "grault"
+  }
+},
+    "patch": [
+  { "op": "move", "from": "/foo/waldo", "path": "/qux/thud" }
+],
+    "expected": {
+  "foo": {
+    "bar": "baz"
+  },
+  "qux": {
+    "corge": "grault",
+    "thud": "fred"
+  }
+}
+  },
+
+  {
+    "comment": "A.7.  Moving an Array Element",
+    "doc": {
+  "foo": [ "all", "grass", "cows", "eat" ]
+},
+    "patch": [
+  { "op": "move", "from": "/foo/1", "path": "/foo/3" }
+],
+    "expected": {
+  "foo": [ "all", "cows", "eat", "grass" ]
+}
+
+  },
+
+  {
+    "comment": "A.8.  Testing a Value: Success",
+    "doc": {
+  "baz": "qux",
+  "foo": [ "a", 2, "c" ]
+},
+    "patch": [
+  { "op": "test", "path": "/baz", "value": "qux" },
+  { "op": "test", "path": "/foo/1", "value": 2 }
+],
+    "expected": {
+     "baz": "qux",
+     "foo": [ "a", 2, "c" ]
+    }
+  },
+
+  {
+    "comment": "A.9.  Testing a Value: Error",
+    "doc": {
+  "baz": "qux"
+},
+    "patch": [
+  { "op": "test", "path": "/baz", "value": "bar" }
+],
+    "error": "string not equivalent"
+  },
+
+  {
+    "comment": "A.10.  Adding a nested Member Object",
+    "doc": {
+  "foo": "bar"
+},
+    "patch": [
+  { "op": "add", "path": "/child", "value": { "grandchild": { } } }
+],
+    "expected": {
+  "foo": "bar",
+  "child": {
+    "grandchild": {
+    }
+  }
+}
+  },
+
+  {
+    "comment": "A.11.  Ignoring Unrecognized Elements",
+    "doc": {
+  "foo":"bar"
+},
+    "patch": [
+  { "op": "add", "path": "/baz", "value": "qux", "xyz": 123 }
+],
+    "expected": {
+  "foo":"bar",
+  "baz":"qux"
+}
+  },
+
+ {
+    "comment": "A.12.  Adding to a Non-existent Target",
+    "doc": {
+  "foo": "bar"
+},
+    "patch": [
+  { "op": "add", "path": "/baz/bat", "value": "qux" }
+],
+    "error": "add to a non-existent target"
+  },
+
+ {
+    "comment": "A.13 Invalid JSON Patch Document",
+    "doc": {
+     "foo": "bar"
+    },
+    "patch": [
+  { "op": "add", "path": "/baz", "value": "qux", "op": "remove" }
+],
+    "error": "operation has two 'op' members",
+    "disabled": true
+  },
+
+  {
+    "comment": "A.14. ~ Escape Ordering",
+    "doc": {
+       "/": 9,
+       "~1": 10
+    },
+    "patch": [{"op": "test", "path": "/~01", "value": 10}],
+    "expected": {
+       "/": 9,
+       "~1": 10
+    }
+  },
+
+  {
+    "comment": "A.15. Comparing Strings and Numbers",
+    "doc": {
+       "/": 9,
+       "~1": 10
+    },
+    "patch": [{"op": "test", "path": "/~01", "value": "10"}],
+    "error": "number is not equal to string"
+  },
+
+  {
+    "comment": "A.16. Adding an Array Value",
+    "doc": {
+       "foo": ["bar"]
+    },
+    "patch": [{ "op": "add", "path": "/foo/-", "value": ["abc", "def"] }],
+    "expected": {
+      "foo": ["bar", ["abc", "def"]]
+    }
+  }
+
+]
diff --git a/tests/json_patch_tests.json b/tests/json_patch_tests.json
new file mode 100644 (file)
index 0000000..4124d51
--- /dev/null
@@ -0,0 +1,485 @@
+[
+    { "comment": "empty list, empty docs",
+      "doc": {},
+      "patch": [],
+      "expected": {} },
+
+    { "comment": "empty patch list",
+      "doc": {"foo": 1},
+      "patch": [],
+      "expected": {"foo": 1} },
+
+    { "comment": "rearrangements OK?",
+      "doc": {"foo": 1, "bar": 2},
+      "patch": [],
+      "expected": {"bar":2, "foo": 1} },
+
+    { "comment": "rearrangements OK?  How about one level down ... array",
+      "doc": [{"foo": 1, "bar": 2}],
+      "patch": [],
+      "expected": [{"bar":2, "foo": 1}] },
+
+    { "comment": "rearrangements OK?  How about one level down...",
+      "doc": {"foo":{"foo": 1, "bar": 2}},
+      "patch": [],
+      "expected": {"foo":{"bar":2, "foo": 1}} },
+
+    { "comment": "add replaces any existing field",
+      "doc": {"foo": null},
+      "patch": [{"op": "add", "path": "/foo", "value":1}],
+      "expected": {"foo": 1} },
+
+    { "comment": "toplevel array",
+      "doc": [],
+      "patch": [{"op": "add", "path": "/0", "value": "foo"}],
+      "expected": ["foo"] },
+
+    { "comment": "toplevel array, no change",
+      "doc": ["foo"],
+      "patch": [],
+      "expected": ["foo"] },
+
+    { "comment": "toplevel object, numeric string",
+      "doc": {},
+      "patch": [{"op": "add", "path": "/foo", "value": "1"}],
+      "expected": {"foo":"1"} },
+
+    { "comment": "toplevel object, integer",
+      "doc": {},
+      "patch": [{"op": "add", "path": "/foo", "value": 1}],
+      "expected": {"foo":1} },
+
+    { "comment": "Toplevel scalar values OK?",
+      "doc": "foo",
+      "patch": [{"op": "replace", "path": "", "value": "bar"}],
+      "expected": "bar",
+      "disabled": true },
+
+    { "comment": "replace object document with array document?",
+      "doc": {},
+      "patch": [{"op": "add", "path": "", "value": []}],
+      "expected": [] },
+
+    { "comment": "replace array document with object document?",
+      "doc": [],
+      "patch": [{"op": "add", "path": "", "value": {}}],
+      "expected": {} },
+
+    { "comment": "append to root array document?",
+      "doc": [],
+      "patch": [{"op": "add", "path": "/-", "value": "hi"}],
+      "expected": ["hi"] },
+
+    { "comment": "Add, / target",
+      "doc": {},
+      "patch": [ {"op": "add", "path": "/", "value":1 } ],
+      "expected": {"":1} },
+
+    { "comment": "Add, /foo/ deep target (trailing slash)",
+      "doc": {"foo": {}},
+      "patch": [ {"op": "add", "path": "/foo/", "value":1 } ],
+      "expected": {"foo":{"": 1}} },
+
+    { "comment": "Add composite value at top level",
+      "doc": {"foo": 1},
+      "patch": [{"op": "add", "path": "/bar", "value": [1, 2]}],
+      "expected": {"foo": 1, "bar": [1, 2]} },
+
+    { "comment": "Add into composite value",
+      "doc": {"foo": 1, "baz": [{"qux": "hello"}]},
+      "patch": [{"op": "add", "path": "/baz/0/foo", "value": "world"}],
+      "expected": {"foo": 1, "baz": [{"qux": "hello", "foo": "world"}]} },
+
+    { "doc": {"bar": [1, 2]},
+      "patch": [{"op": "add", "path": "/bar/8", "value": "5"}],
+      "error": "Out of bounds (upper)" },
+
+    { "doc": {"bar": [1, 2]},
+      "patch": [{"op": "add", "path": "/bar/-1", "value": "5"}],
+      "error": "Out of bounds (lower)" },
+
+    { "doc": {"foo": 1},
+      "patch": [{"op": "add", "path": "/bar", "value": true}],
+      "expected": {"foo": 1, "bar": true} },
+
+    { "doc": {"foo": 1},
+      "patch": [{"op": "add", "path": "/bar", "value": false}],
+      "expected": {"foo": 1, "bar": false} },
+
+    { "doc": {"foo": 1},
+      "patch": [{"op": "add", "path": "/bar", "value": null}],
+      "expected": {"foo": 1, "bar": null} },
+
+    { "comment": "0 can be an array index or object element name",
+      "doc": {"foo": 1},
+      "patch": [{"op": "add", "path": "/0", "value": "bar"}],
+      "expected": {"foo": 1, "0": "bar" } },
+
+    { "doc": ["foo"],
+      "patch": [{"op": "add", "path": "/1", "value": "bar"}],
+      "expected": ["foo", "bar"] },
+
+    { "doc": ["foo", "sil"],
+      "patch": [{"op": "add", "path": "/1", "value": "bar"}],
+      "expected": ["foo", "bar", "sil"] },
+
+    { "doc": ["foo", "sil"],
+      "patch": [{"op": "add", "path": "/0", "value": "bar"}],
+      "expected": ["bar", "foo", "sil"] },
+
+    { "comment": "push item to array via last index + 1",
+      "doc": ["foo", "sil"],
+      "patch": [{"op":"add", "path": "/2", "value": "bar"}],
+      "expected": ["foo", "sil", "bar"] },
+
+    { "comment": "add item to array at index > length should fail",
+      "doc": ["foo", "sil"],
+      "patch": [{"op":"add", "path": "/3", "value": "bar"}],
+      "error": "index is greater than number of items in array" },
+      
+    { "comment": "test against implementation-specific numeric parsing",
+      "doc": {"1e0": "foo"},
+      "patch": [{"op": "test", "path": "/1e0", "value": "foo"}],
+      "expected": {"1e0": "foo"} },
+
+    { "comment": "test with bad number should fail",
+      "doc": ["foo", "bar"],
+      "patch": [{"op": "test", "path": "/1e0", "value": "bar"}],
+      "error": "test op shouldn't get array element 1" },
+
+    { "doc": ["foo", "sil"],
+      "patch": [{"op": "add", "path": "/bar", "value": 42}],
+      "error": "Object operation on array target" },
+
+    { "doc": ["foo", "sil"],
+      "patch": [{"op": "add", "path": "/1", "value": ["bar", "baz"]}],
+      "expected": ["foo", ["bar", "baz"], "sil"],
+      "comment": "value in array add not flattened" },
+
+    { "doc": {"foo": 1, "bar": [1, 2, 3, 4]},
+      "patch": [{"op": "remove", "path": "/bar"}],
+      "expected": {"foo": 1} },
+
+    { "doc": {"foo": 1, "baz": [{"qux": "hello"}]},
+      "patch": [{"op": "remove", "path": "/baz/0/qux"}],
+      "expected": {"foo": 1, "baz": [{}]} },
+
+    { "doc": {"foo": 1, "baz": [{"qux": "hello"}]},
+      "patch": [{"op": "replace", "path": "/foo", "value": [1, 2, 3, 4]}],
+      "expected": {"foo": [1, 2, 3, 4], "baz": [{"qux": "hello"}]} },
+
+    { "doc": {"foo": [1, 2, 3, 4], "baz": [{"qux": "hello"}]},
+      "patch": [{"op": "replace", "path": "/baz/0/qux", "value": "world"}],
+      "expected": {"foo": [1, 2, 3, 4], "baz": [{"qux": "world"}]} },
+
+    { "doc": ["foo"],
+      "patch": [{"op": "replace", "path": "/0", "value": "bar"}],
+      "expected": ["bar"] },
+
+    { "doc": [""],
+      "patch": [{"op": "replace", "path": "/0", "value": 0}],
+      "expected": [0] },
+
+    { "doc": [""],
+      "patch": [{"op": "replace", "path": "/0", "value": true}],
+      "expected": [true] },
+
+    { "doc": [""],
+      "patch": [{"op": "replace", "path": "/0", "value": false}],
+      "expected": [false] },
+
+    { "doc": [""],
+      "patch": [{"op": "replace", "path": "/0", "value": null}],
+      "expected": [null] },
+
+    { "doc": ["foo", "sil"],
+      "patch": [{"op": "replace", "path": "/1", "value": ["bar", "baz"]}],
+      "expected": ["foo", ["bar", "baz"]],
+      "comment": "value in array replace not flattened" },
+
+    { "comment": "replace whole document",
+      "doc": {"foo": "bar"},
+      "patch": [{"op": "replace", "path": "", "value": {"baz": "qux"}}],
+      "expected": {"baz": "qux"} },
+
+    { "comment": "test replace with missing parent key should fail",
+      "doc": {"bar": "baz"},
+      "patch": [{"op": "replace", "path": "/foo/bar", "value": false}],
+      "error": "replace op should fail with missing parent key" },
+
+    { "comment": "spurious patch properties",
+      "doc": {"foo": 1},
+      "patch": [{"op": "test", "path": "/foo", "value": 1, "spurious": 1}],
+      "expected": {"foo": 1} },
+
+    { "doc": {"foo": null},
+      "patch": [{"op": "test", "path": "/foo", "value": null}],
+      "expected": {"foo": null},
+      "comment": "null value should be valid obj property" },
+
+    { "doc": {"foo": null},
+      "patch": [{"op": "replace", "path": "/foo", "value": "truthy"}],
+      "expected": {"foo": "truthy"},
+      "comment": "null value should be valid obj property to be replaced with something truthy" },
+
+    { "doc": {"foo": null},
+      "patch": [{"op": "move", "from": "/foo", "path": "/bar"}],
+      "expected": {"bar": null},
+      "comment": "null value should be valid obj property to be moved" },
+
+    { "doc": {"foo": null},
+      "patch": [{"op": "copy", "from": "/foo", "path": "/bar"}],
+      "expected": {"foo": null, "bar": null},
+      "comment": "null value should be valid obj property to be copied" },
+
+    { "doc": {"foo": null},
+      "patch": [{"op": "remove", "path": "/foo"}],
+      "expected": {},
+      "comment": "null value should be valid obj property to be removed" },
+
+    { "doc": {"foo": "bar"},
+      "patch": [{"op": "replace", "path": "/foo", "value": null}],
+      "expected": {"foo": null},
+      "comment": "null value should still be valid obj property replace other value" },
+
+    { "doc": {"foo": {"foo": 1, "bar": 2}},
+      "patch": [{"op": "test", "path": "/foo", "value": {"bar": 2, "foo": 1}}],
+      "expected": {"foo": {"foo": 1, "bar": 2}},
+      "comment": "test should pass despite rearrangement" },
+
+    { "doc": {"foo": [{"foo": 1, "bar": 2}]},
+      "patch": [{"op": "test", "path": "/foo", "value": [{"bar": 2, "foo": 1}]}],
+      "expected": {"foo": [{"foo": 1, "bar": 2}]},
+      "comment": "test should pass despite (nested) rearrangement" },
+
+    { "doc": {"foo": {"bar": [1, 2, 5, 4]}},
+      "patch": [{"op": "test", "path": "/foo", "value": {"bar": [1, 2, 5, 4]}}],
+      "expected": {"foo": {"bar": [1, 2, 5, 4]}},
+      "comment": "test should pass - no error" },
+
+    { "doc": {"foo": {"bar": [1, 2, 5, 4]}},
+      "patch": [{"op": "test", "path": "/foo", "value": [1, 2]}],
+      "error": "test op should fail" },
+
+    { "comment": "Whole document",
+      "doc": { "foo": 1 },
+      "patch": [{"op": "test", "path": "", "value": {"foo": 1}}],
+      "disabled": true },
+
+    { "comment": "Empty-string element",
+      "doc": { "": 1 },
+      "patch": [{"op": "test", "path": "/", "value": 1}],
+      "expected": { "": 1 } },
+
+    { "doc": {
+            "foo": ["bar", "baz"],
+            "": 0,
+            "a/b": 1,
+            "c%d": 2,
+            "e^f": 3,
+            "g|h": 4,
+            "i\\j": 5,
+            "k\"l": 6,
+            " ": 7,
+            "m~n": 8
+            },
+      "patch": [{"op": "test", "path": "/foo", "value": ["bar", "baz"]},
+                {"op": "test", "path": "/foo/0", "value": "bar"},
+                {"op": "test", "path": "/", "value": 0},
+                {"op": "test", "path": "/a~1b", "value": 1},
+                {"op": "test", "path": "/c%d", "value": 2},
+                {"op": "test", "path": "/e^f", "value": 3},
+                {"op": "test", "path": "/g|h", "value": 4},
+                {"op": "test", "path":  "/i\\j", "value": 5},
+                {"op": "test", "path": "/k\"l", "value": 6},
+                {"op": "test", "path": "/ ", "value": 7},
+                {"op": "test", "path": "/m~0n", "value": 8}],
+      "expected": {
+            "": 0,
+            " ": 7,
+            "a/b": 1,
+            "c%d": 2,
+            "e^f": 3,
+            "foo": [
+                "bar",
+                "baz"
+            ],
+            "g|h": 4,
+            "i\\j": 5,
+            "k\"l": 6,
+            "m~n": 8
+        }
+    },
+    { "comment": "Move to same location has no effect",
+      "doc": {"foo": 1},
+      "patch": [{"op": "move", "from": "/foo", "path": "/foo"}],
+      "expected": {"foo": 1} },
+
+    { "doc": {"foo": 1, "baz": [{"qux": "hello"}]},
+      "patch": [{"op": "move", "from": "/foo", "path": "/bar"}],
+      "expected": {"baz": [{"qux": "hello"}], "bar": 1} },
+
+    { "doc": {"baz": [{"qux": "hello"}], "bar": 1},
+      "patch": [{"op": "move", "from": "/baz/0/qux", "path": "/baz/1"}],
+      "expected": {"baz": [{}, "hello"], "bar": 1} },
+
+    { "doc": {"baz": [{"qux": "hello"}], "bar": 1},
+      "patch": [{"op": "copy", "from": "/baz/0", "path": "/boo"}],
+      "expected": {"baz":[{"qux":"hello"}],"bar":1,"boo":{"qux":"hello"}} },
+
+    { "comment": "replacing the root of the document is possible with add",
+      "doc": {"foo": "bar"},
+      "patch": [{"op": "add", "path": "", "value": {"baz": "qux"}}],
+      "expected": {"baz":"qux"}},
+
+    { "comment": "Adding to \"/-\" adds to the end of the array",
+      "doc": [ 1, 2 ],
+      "patch": [ { "op": "add", "path": "/-", "value": { "foo": [ "bar", "baz" ] } } ],
+      "expected": [ 1, 2, { "foo": [ "bar", "baz" ] } ]},
+
+    { "comment": "Adding to \"/-\" adds to the end of the array, even n levels down",
+      "doc": [ 1, 2, [ 3, [ 4, 5 ] ] ],
+      "patch": [ { "op": "add", "path": "/2/1/-", "value": { "foo": [ "bar", "baz" ] } } ],
+      "expected": [ 1, 2, [ 3, [ 4, 5, { "foo": [ "bar", "baz" ] } ] ] ]},
+
+    { "comment": "test remove with bad number should fail",
+      "doc": {"foo": 1, "baz": [{"qux": "hello"}]},
+      "patch": [{"op": "remove", "path": "/baz/1e0/qux"}],
+      "error": "remove op shouldn't remove from array with bad number" },
+
+    { "comment": "test remove on array",
+      "doc": [1, 2, 3, 4],
+      "patch": [{"op": "remove", "path": "/0"}],
+      "expected": [2, 3, 4] },
+
+    { "comment": "test repeated removes",
+      "doc": [1, 2, 3, 4],
+      "patch": [{ "op": "remove", "path": "/1" },
+                { "op": "remove", "path": "/2" }],
+      "expected": [1, 3] },
+
+    { "comment": "test remove with bad index should fail",
+      "doc": [1, 2, 3, 4],
+      "patch": [{"op": "remove", "path": "/1e0"}],
+      "error": "remove op shouldn't remove from array with bad number" },
+
+    { "comment": "test replace with bad number should fail",
+      "doc": [""],
+      "patch": [{"op": "replace", "path": "/1e0", "value": false}],
+      "error": "replace op shouldn't replace in array with bad number" },
+
+    { "comment": "test copy with bad number should fail",
+      "doc": {"baz": [1,2,3], "bar": 1},
+      "patch": [{"op": "copy", "from": "/baz/1e0", "path": "/boo"}],
+      "error": "copy op shouldn't work with bad number" },
+
+    { "comment": "test move with bad number should fail",
+      "doc": {"foo": 1, "baz": [1,2,3,4]},
+      "patch": [{"op": "move", "from": "/baz/1e0", "path": "/foo"}],
+      "error": "move op shouldn't work with bad number" },
+
+    { "comment": "test add with bad number should fail",
+      "doc": ["foo", "sil"],
+      "patch": [{"op": "add", "path": "/1e0", "value": "bar"}],
+      "error": "add op shouldn't add to array with bad number" },
+
+    { "comment": "missing 'path' parameter",
+      "doc": {},
+      "patch": [ { "op": "add", "value": "bar" } ],
+      "error": "missing 'path' parameter" },
+
+    { "comment": "'path' parameter with null value",
+      "doc": {},
+      "patch": [ { "op": "add", "path": null, "value": "bar" } ],
+      "error": "null is not valid value for 'path'" },
+
+    { "comment": "invalid JSON Pointer token",
+      "doc": {},
+      "patch": [ { "op": "add", "path": "foo", "value": "bar" } ],
+      "error": "JSON Pointer should start with a slash" },
+
+    { "comment": "missing 'value' parameter to add",
+      "doc": [ 1 ],
+      "patch": [ { "op": "add", "path": "/-" } ],
+      "error": "missing 'value' parameter" },
+
+    { "comment": "missing 'value' parameter to replace",
+      "doc": [ 1 ],
+      "patch": [ { "op": "replace", "path": "/0" } ],
+      "error": "missing 'value' parameter" },
+
+    { "comment": "missing 'value' parameter to test",
+      "doc": [ null ],
+      "patch": [ { "op": "test", "path": "/0" } ],
+      "error": "missing 'value' parameter" },
+
+    { "comment": "missing value parameter to test - where undef is falsy",
+      "doc": [ false ],
+      "patch": [ { "op": "test", "path": "/0" } ],
+      "error": "missing 'value' parameter" },
+
+    { "comment": "missing from parameter to copy",
+      "doc": [ 1 ],
+      "patch": [ { "op": "copy", "path": "/-" } ],
+      "error": "missing 'from' parameter" },
+
+    { "comment": "missing from location to copy",
+      "doc": { "foo": 1 },
+      "patch": [ { "op": "copy", "from": "/bar", "path": "/foo" } ],
+      "error": "missing 'from' location" },
+
+    { "comment": "missing from parameter to move",
+      "doc": { "foo": 1 },
+      "patch": [ { "op": "move", "path": "" } ],
+      "error": "missing 'from' parameter" },
+
+    { "comment": "missing from location to move",
+      "doc": { "foo": 1 },
+      "patch": [ { "op": "move", "from": "/bar", "path": "/foo" } ],
+      "error": "missing 'from' location" },
+
+    { "comment": "duplicate ops",
+      "doc": { "foo": "bar" },
+      "patch": [ { "op": "add", "path": "/baz", "value": "qux",
+                   "op": "move", "from":"/foo" } ],
+      "error": "patch has two 'op' members",
+      "disabled_in_json_c": true,
+      "disabled": true },
+
+    { "comment": "unrecognized op should fail",
+      "doc": {"foo": 1},
+      "patch": [{"op": "spam", "path": "/foo", "value": 1}],
+      "error": "Unrecognized op 'spam'" },
+
+    { "comment": "test with bad array number that has leading zeros",
+      "doc": ["foo", "bar"],
+      "patch": [{"op": "test", "path": "/00", "value": "foo"}],
+      "error": "test op should reject the array value, it has leading zeros" },
+
+    { "comment": "test with bad array number that has leading zeros",
+      "doc": ["foo", "bar"],
+      "patch": [{"op": "test", "path": "/01", "value": "bar"}],
+      "error": "test op should reject the array value, it has leading zeros" },
+
+    { "comment": "Removing nonexistent field",
+      "doc": {"foo" : "bar"},
+      "patch": [{"op": "remove", "path": "/baz"}],
+      "error": "removing a nonexistent field should fail" },
+
+    { "comment": "Removing deep nonexistent path",
+      "doc": {"foo" : "bar"},
+      "patch": [{"op": "remove", "path": "/missing1/missing2"}],
+      "error": "removing a nonexistent field should fail" },
+
+    { "comment": "Removing nonexistent index",
+      "doc": ["foo", "bar"],
+      "patch": [{"op": "remove", "path": "/2"}],
+      "error": "removing a nonexistent index should fail" },
+
+    { "comment": "Patch with different capitalisation than doc",
+       "doc": {"foo":"bar"},
+       "patch": [{"op": "add", "path": "/FOO", "value": "BAR"}],
+       "expected": {"foo": "bar", "FOO": "BAR"}
+    }
+
+]
diff --git a/tests/test_json_patch.c b/tests/test_json_patch.c
new file mode 100644 (file)
index 0000000..dad7521
--- /dev/null
@@ -0,0 +1,119 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "json.h"
+
+#ifndef PATH_MAX
+#define PATH_MAX 256
+#endif
+
+void test_json_patch_op(struct json_object *jo)
+{
+       const char *comment = json_object_get_string(json_object_object_get(jo, "comment"));
+       struct json_object *doc = json_object_object_get(jo, "doc");
+       struct json_object *patch = json_object_object_get(jo, "patch");
+       struct json_object *expected = json_object_object_get(jo, "expected");
+       struct json_object *error = json_object_object_get(jo, "error");
+       int disabled_test = json_object_get_boolean(json_object_object_get(jo, "disabled_in_json_c"));
+       const char *error_s = json_object_get_string(error);
+       struct json_object *res = NULL;
+       int ret;
+
+       printf("Testing '%s', doc '%s' patch '%s' : ",
+               comment ? comment : error_s,
+               json_object_get_string(doc),
+               json_object_get_string(patch));
+       if (disabled_test) {
+               printf("SKIPPING - disabled in the test spec\n");
+               return;
+       }
+       if (!error && !expected) {
+               printf("SKIPPING - no expected or error conditions in test\n");
+               return;
+       }
+       fflush(stdout);
+       if (error) {
+               assert(-1 == json_patch_apply(doc, patch, &res));
+               assert(res == NULL);
+       } else {
+               ret = json_patch_apply(doc, patch, &res);
+               if (ret) {
+                       fprintf(stderr, "json_patch_apply() returned '%d'\n", ret);
+                       fprintf(stderr, "Expected: %s\n", json_object_get_string(expected));
+                       fprintf(stderr, "Got: %s\n", json_object_get_string(res));
+                       fflush(stderr);
+                       assert(0);
+               }
+               assert(res != NULL);
+               ret = json_object_equal(expected, res);
+               if (ret == 0) {
+                       fprintf(stderr, "json_object_equal() returned '%d'\n", ret);
+                       fprintf(stderr, "Expected: %s\n", json_object_get_string(expected));
+                       fprintf(stderr, "Got: %s\n", json_object_get_string(res));
+                       fflush(stderr);
+                       assert(0);
+               }
+               json_object_put(res);
+               res = NULL;
+       }
+
+       printf("OK\n");
+}
+
+void test_json_patch_using_file(const char *testdir, const char *filename)
+{
+       char full_filename[PATH_MAX];
+       (void)snprintf(full_filename, sizeof(full_filename), "%s/%s", testdir, filename);
+       int i;
+
+       json_object *jo = json_object_from_file(full_filename);
+       if (!jo) {
+               fprintf(stderr, "FAIL: unable to open %s: %s\n", full_filename, strerror(errno));
+               exit(EXIT_FAILURE);
+       }
+
+       for (i = 0; i < json_object_array_length(jo); i++) {
+               struct json_object *jo1 = json_object_array_get_idx(jo, i);
+               test_json_patch_op(jo1);
+       }
+
+       json_object_put(jo);
+}
+
+int main(int argc, char **argv)
+{
+       const char *testdir;
+       if (argc < 2)
+       {
+               fprintf(stderr,
+                       "Usage: %s <testdir>\n"
+                       "  <testdir> is the location of input files\n",
+                       argv[0]);
+               return EXIT_FAILURE;
+       }
+       testdir = argv[1];
+
+       //      Test json_c_version.c
+       if (strncmp(json_c_version(), JSON_C_VERSION, sizeof(JSON_C_VERSION)))
+       {
+               printf("FAIL: Output from json_c_version(): %s does not match %s",
+                      json_c_version(), JSON_C_VERSION);
+               return EXIT_FAILURE;
+       }
+       if (json_c_version_num() != JSON_C_VERSION_NUM)
+       {
+               printf("FAIL: Output from json_c_version_num(): %d does not match %d",
+                      json_c_version_num(), JSON_C_VERSION_NUM);
+               return EXIT_FAILURE;
+       }
+
+       test_json_patch_using_file(testdir, "json_patch_spec_tests.json");
+       test_json_patch_using_file(testdir, "json_patch_tests.json");
+       return 0;
+}
diff --git a/tests/test_json_patch.expected b/tests/test_json_patch.expected
new file mode 100644 (file)
index 0000000..64e59b8
--- /dev/null
@@ -0,0 +1,110 @@
+Testing '4.1. add with missing object', doc '{ "q": { "bar": 2 } }' patch '[ { "op": "add", "path": "\/a\/b", "value": 1 } ]' : OK
+Testing 'A.1.  Adding an Object Member', doc '{ "foo": "bar" }' patch '[ { "op": "add", "path": "\/baz", "value": "qux" } ]' : OK
+Testing 'A.2.  Adding an Array Element', doc '{ "foo": [ "bar", "baz" ] }' patch '[ { "op": "add", "path": "\/foo\/1", "value": "qux" } ]' : OK
+Testing 'A.3.  Removing an Object Member', doc '{ "baz": "qux", "foo": "bar" }' patch '[ { "op": "remove", "path": "\/baz" } ]' : OK
+Testing 'A.4.  Removing an Array Element', doc '{ "foo": [ "bar", "qux", "baz" ] }' patch '[ { "op": "remove", "path": "\/foo\/1" } ]' : OK
+Testing 'A.5.  Replacing a Value', doc '{ "baz": "qux", "foo": "bar" }' patch '[ { "op": "replace", "path": "\/baz", "value": "boo" } ]' : OK
+Testing 'A.6.  Moving a Value', doc '{ "foo": { "bar": "baz", "waldo": "fred" }, "qux": { "corge": "grault" } }' patch '[ { "op": "move", "from": "\/foo\/waldo", "path": "\/qux\/thud" } ]' : OK
+Testing 'A.7.  Moving an Array Element', doc '{ "foo": [ "all", "grass", "cows", "eat" ] }' patch '[ { "op": "move", "from": "\/foo\/1", "path": "\/foo\/3" } ]' : OK
+Testing 'A.8.  Testing a Value: Success', doc '{ "baz": "qux", "foo": [ "a", 2, "c" ] }' patch '[ { "op": "test", "path": "\/baz", "value": "qux" }, { "op": "test", "path": "\/foo\/1", "value": 2 } ]' : OK
+Testing 'A.9.  Testing a Value: Error', doc '{ "baz": "qux" }' patch '[ { "op": "test", "path": "\/baz", "value": "bar" } ]' : OK
+Testing 'A.10.  Adding a nested Member Object', doc '{ "foo": "bar" }' patch '[ { "op": "add", "path": "\/child", "value": { "grandchild": { } } } ]' : OK
+Testing 'A.11.  Ignoring Unrecognized Elements', doc '{ "foo": "bar" }' patch '[ { "op": "add", "path": "\/baz", "value": "qux", "xyz": 123 } ]' : OK
+Testing 'A.12.  Adding to a Non-existent Target', doc '{ "foo": "bar" }' patch '[ { "op": "add", "path": "\/baz\/bat", "value": "qux" } ]' : OK
+Testing 'A.13 Invalid JSON Patch Document', doc '{ "foo": "bar" }' patch '[ { "op": "remove", "path": "\/baz", "value": "qux" } ]' : OK
+Testing 'A.14. ~ Escape Ordering', doc '{ "\/": 9, "~1": 10 }' patch '[ { "op": "test", "path": "\/~01", "value": 10 } ]' : OK
+Testing 'A.15. Comparing Strings and Numbers', doc '{ "\/": 9, "~1": 10 }' patch '[ { "op": "test", "path": "\/~01", "value": "10" } ]' : OK
+Testing 'A.16. Adding an Array Value', doc '{ "foo": [ "bar" ] }' patch '[ { "op": "add", "path": "\/foo\/-", "value": [ "abc", "def" ] } ]' : OK
+Testing 'empty list, empty docs', doc '{ }' patch '[ ]' : OK
+Testing 'empty patch list', doc '{ "foo": 1 }' patch '[ ]' : OK
+Testing 'rearrangements OK?', doc '{ "foo": 1, "bar": 2 }' patch '[ ]' : OK
+Testing 'rearrangements OK?  How about one level down ... array', doc '[ { "foo": 1, "bar": 2 } ]' patch '[ ]' : OK
+Testing 'rearrangements OK?  How about one level down...', doc '{ "foo": { "foo": 1, "bar": 2 } }' patch '[ ]' : OK
+Testing 'add replaces any existing field', doc '{ "foo": null }' patch '[ { "op": "add", "path": "\/foo", "value": 1 } ]' : OK
+Testing 'toplevel array', doc '[ ]' patch '[ { "op": "add", "path": "\/0", "value": "foo" } ]' : OK
+Testing 'toplevel array, no change', doc '[ "foo" ]' patch '[ ]' : OK
+Testing 'toplevel object, numeric string', doc '{ }' patch '[ { "op": "add", "path": "\/foo", "value": "1" } ]' : OK
+Testing 'toplevel object, integer', doc '{ }' patch '[ { "op": "add", "path": "\/foo", "value": 1 } ]' : OK
+Testing 'Toplevel scalar values OK?', doc 'foo' patch '[ { "op": "replace", "path": "", "value": "bar" } ]' : OK
+Testing 'replace object document with array document?', doc '{ }' patch '[ { "op": "add", "path": "", "value": [ ] } ]' : OK
+Testing 'replace array document with object document?', doc '[ ]' patch '[ { "op": "add", "path": "", "value": { } } ]' : OK
+Testing 'append to root array document?', doc '[ ]' patch '[ { "op": "add", "path": "\/-", "value": "hi" } ]' : OK
+Testing 'Add, / target', doc '{ }' patch '[ { "op": "add", "path": "\/", "value": 1 } ]' : OK
+Testing 'Add, /foo/ deep target (trailing slash)', doc '{ "foo": { } }' patch '[ { "op": "add", "path": "\/foo\/", "value": 1 } ]' : OK
+Testing 'Add composite value at top level', doc '{ "foo": 1 }' patch '[ { "op": "add", "path": "\/bar", "value": [ 1, 2 ] } ]' : OK
+Testing 'Add into composite value', doc '{ "foo": 1, "baz": [ { "qux": "hello" } ] }' patch '[ { "op": "add", "path": "\/baz\/0\/foo", "value": "world" } ]' : OK
+Testing 'Out of bounds (upper)', doc '{ "bar": [ 1, 2 ] }' patch '[ { "op": "add", "path": "\/bar\/8", "value": "5" } ]' : OK
+Testing 'Out of bounds (lower)', doc '{ "bar": [ 1, 2 ] }' patch '[ { "op": "add", "path": "\/bar\/-1", "value": "5" } ]' : OK
+Testing '(null)', doc '{ "foo": 1 }' patch '[ { "op": "add", "path": "\/bar", "value": true } ]' : OK
+Testing '(null)', doc '{ "foo": 1 }' patch '[ { "op": "add", "path": "\/bar", "value": false } ]' : OK
+Testing '(null)', doc '{ "foo": 1 }' patch '[ { "op": "add", "path": "\/bar", "value": null } ]' : OK
+Testing '0 can be an array index or object element name', doc '{ "foo": 1 }' patch '[ { "op": "add", "path": "\/0", "value": "bar" } ]' : OK
+Testing '(null)', doc '[ "foo" ]' patch '[ { "op": "add", "path": "\/1", "value": "bar" } ]' : OK
+Testing '(null)', doc '[ "foo", "sil" ]' patch '[ { "op": "add", "path": "\/1", "value": "bar" } ]' : OK
+Testing '(null)', doc '[ "foo", "sil" ]' patch '[ { "op": "add", "path": "\/0", "value": "bar" } ]' : OK
+Testing 'push item to array via last index + 1', doc '[ "foo", "sil" ]' patch '[ { "op": "add", "path": "\/2", "value": "bar" } ]' : OK
+Testing 'add item to array at index > length should fail', doc '[ "foo", "sil" ]' patch '[ { "op": "add", "path": "\/3", "value": "bar" } ]' : OK
+Testing 'test against implementation-specific numeric parsing', doc '{ "1e0": "foo" }' patch '[ { "op": "test", "path": "\/1e0", "value": "foo" } ]' : OK
+Testing 'test with bad number should fail', doc '[ "foo", "bar" ]' patch '[ { "op": "test", "path": "\/1e0", "value": "bar" } ]' : OK
+Testing 'Object operation on array target', doc '[ "foo", "sil" ]' patch '[ { "op": "add", "path": "\/bar", "value": 42 } ]' : OK
+Testing 'value in array add not flattened', doc '[ "foo", "sil" ]' patch '[ { "op": "add", "path": "\/1", "value": [ "bar", "baz" ] } ]' : OK
+Testing '(null)', doc '{ "foo": 1, "bar": [ 1, 2, 3, 4 ] }' patch '[ { "op": "remove", "path": "\/bar" } ]' : OK
+Testing '(null)', doc '{ "foo": 1, "baz": [ { "qux": "hello" } ] }' patch '[ { "op": "remove", "path": "\/baz\/0\/qux" } ]' : OK
+Testing '(null)', doc '{ "foo": 1, "baz": [ { "qux": "hello" } ] }' patch '[ { "op": "replace", "path": "\/foo", "value": [ 1, 2, 3, 4 ] } ]' : OK
+Testing '(null)', doc '{ "foo": [ 1, 2, 3, 4 ], "baz": [ { "qux": "hello" } ] }' patch '[ { "op": "replace", "path": "\/baz\/0\/qux", "value": "world" } ]' : OK
+Testing '(null)', doc '[ "foo" ]' patch '[ { "op": "replace", "path": "\/0", "value": "bar" } ]' : OK
+Testing '(null)', doc '[ "" ]' patch '[ { "op": "replace", "path": "\/0", "value": 0 } ]' : OK
+Testing '(null)', doc '[ "" ]' patch '[ { "op": "replace", "path": "\/0", "value": true } ]' : OK
+Testing '(null)', doc '[ "" ]' patch '[ { "op": "replace", "path": "\/0", "value": false } ]' : OK
+Testing '(null)', doc '[ "" ]' patch '[ { "op": "replace", "path": "\/0", "value": null } ]' : OK
+Testing 'value in array replace not flattened', doc '[ "foo", "sil" ]' patch '[ { "op": "replace", "path": "\/1", "value": [ "bar", "baz" ] } ]' : OK
+Testing 'replace whole document', doc '{ "foo": "bar" }' patch '[ { "op": "replace", "path": "", "value": { "baz": "qux" } } ]' : OK
+Testing 'test replace with missing parent key should fail', doc '{ "bar": "baz" }' patch '[ { "op": "replace", "path": "\/foo\/bar", "value": false } ]' : OK
+Testing 'spurious patch properties', doc '{ "foo": 1 }' patch '[ { "op": "test", "path": "\/foo", "value": 1, "spurious": 1 } ]' : OK
+Testing 'null value should be valid obj property', doc '{ "foo": null }' patch '[ { "op": "test", "path": "\/foo", "value": null } ]' : OK
+Testing 'null value should be valid obj property to be replaced with something truthy', doc '{ "foo": null }' patch '[ { "op": "replace", "path": "\/foo", "value": "truthy" } ]' : OK
+Testing 'null value should be valid obj property to be moved', doc '{ "foo": null }' patch '[ { "op": "move", "from": "\/foo", "path": "\/bar" } ]' : OK
+Testing 'null value should be valid obj property to be copied', doc '{ "foo": null }' patch '[ { "op": "copy", "from": "\/foo", "path": "\/bar" } ]' : OK
+Testing 'null value should be valid obj property to be removed', doc '{ "foo": null }' patch '[ { "op": "remove", "path": "\/foo" } ]' : OK
+Testing 'null value should still be valid obj property replace other value', doc '{ "foo": "bar" }' patch '[ { "op": "replace", "path": "\/foo", "value": null } ]' : OK
+Testing 'test should pass despite rearrangement', doc '{ "foo": { "foo": 1, "bar": 2 } }' patch '[ { "op": "test", "path": "\/foo", "value": { "bar": 2, "foo": 1 } } ]' : OK
+Testing 'test should pass despite (nested) rearrangement', doc '{ "foo": [ { "foo": 1, "bar": 2 } ] }' patch '[ { "op": "test", "path": "\/foo", "value": [ { "bar": 2, "foo": 1 } ] } ]' : OK
+Testing 'test should pass - no error', doc '{ "foo": { "bar": [ 1, 2, 5, 4 ] } }' patch '[ { "op": "test", "path": "\/foo", "value": { "bar": [ 1, 2, 5, 4 ] } } ]' : OK
+Testing 'test op should fail', doc '{ "foo": { "bar": [ 1, 2, 5, 4 ] } }' patch '[ { "op": "test", "path": "\/foo", "value": [ 1, 2 ] } ]' : OK
+Testing 'Whole document', doc '{ "foo": 1 }' patch '[ { "op": "test", "path": "", "value": { "foo": 1 } } ]' : SKIPPING - no expected or error conditions in test
+Testing 'Empty-string element', doc '{ "": 1 }' patch '[ { "op": "test", "path": "\/", "value": 1 } ]' : OK
+Testing '(null)', doc '{ "foo": [ "bar", "baz" ], "": 0, "a\/b": 1, "c%d": 2, "e^f": 3, "g|h": 4, "i\\j": 5, "k\"l": 6, " ": 7, "m~n": 8 }' patch '[ { "op": "test", "path": "\/foo", "value": [ "bar", "baz" ] }, { "op": "test", "path": "\/foo\/0", "value": "bar" }, { "op": "test", "path": "\/", "value": 0 }, { "op": "test", "path": "\/a~1b", "value": 1 }, { "op": "test", "path": "\/c%d", "value": 2 }, { "op": "test", "path": "\/e^f", "value": 3 }, { "op": "test", "path": "\/g|h", "value": 4 }, { "op": "test", "path": "\/i\\j", "value": 5 }, { "op": "test", "path": "\/k\"l", "value": 6 }, { "op": "test", "path": "\/ ", "value": 7 }, { "op": "test", "path": "\/m~0n", "value": 8 } ]' : OK
+Testing 'Move to same location has no effect', doc '{ "foo": 1 }' patch '[ { "op": "move", "from": "\/foo", "path": "\/foo" } ]' : OK
+Testing '(null)', doc '{ "foo": 1, "baz": [ { "qux": "hello" } ] }' patch '[ { "op": "move", "from": "\/foo", "path": "\/bar" } ]' : OK
+Testing '(null)', doc '{ "baz": [ { "qux": "hello" } ], "bar": 1 }' patch '[ { "op": "move", "from": "\/baz\/0\/qux", "path": "\/baz\/1" } ]' : OK
+Testing '(null)', doc '{ "baz": [ { "qux": "hello" } ], "bar": 1 }' patch '[ { "op": "copy", "from": "\/baz\/0", "path": "\/boo" } ]' : OK
+Testing 'replacing the root of the document is possible with add', doc '{ "foo": "bar" }' patch '[ { "op": "add", "path": "", "value": { "baz": "qux" } } ]' : OK
+Testing 'Adding to "/-" adds to the end of the array', doc '[ 1, 2 ]' patch '[ { "op": "add", "path": "\/-", "value": { "foo": [ "bar", "baz" ] } } ]' : OK
+Testing 'Adding to "/-" adds to the end of the array, even n levels down', doc '[ 1, 2, [ 3, [ 4, 5 ] ] ]' patch '[ { "op": "add", "path": "\/2\/1\/-", "value": { "foo": [ "bar", "baz" ] } } ]' : OK
+Testing 'test remove with bad number should fail', doc '{ "foo": 1, "baz": [ { "qux": "hello" } ] }' patch '[ { "op": "remove", "path": "\/baz\/1e0\/qux" } ]' : OK
+Testing 'test remove on array', doc '[ 1, 2, 3, 4 ]' patch '[ { "op": "remove", "path": "\/0" } ]' : OK
+Testing 'test repeated removes', doc '[ 1, 2, 3, 4 ]' patch '[ { "op": "remove", "path": "\/1" }, { "op": "remove", "path": "\/2" } ]' : OK
+Testing 'test remove with bad index should fail', doc '[ 1, 2, 3, 4 ]' patch '[ { "op": "remove", "path": "\/1e0" } ]' : OK
+Testing 'test replace with bad number should fail', doc '[ "" ]' patch '[ { "op": "replace", "path": "\/1e0", "value": false } ]' : OK
+Testing 'test copy with bad number should fail', doc '{ "baz": [ 1, 2, 3 ], "bar": 1 }' patch '[ { "op": "copy", "from": "\/baz\/1e0", "path": "\/boo" } ]' : OK
+Testing 'test move with bad number should fail', doc '{ "foo": 1, "baz": [ 1, 2, 3, 4 ] }' patch '[ { "op": "move", "from": "\/baz\/1e0", "path": "\/foo" } ]' : OK
+Testing 'test add with bad number should fail', doc '[ "foo", "sil" ]' patch '[ { "op": "add", "path": "\/1e0", "value": "bar" } ]' : OK
+Testing 'missing 'path' parameter', doc '{ }' patch '[ { "op": "add", "value": "bar" } ]' : OK
+Testing ''path' parameter with null value', doc '{ }' patch '[ { "op": "add", "path": null, "value": "bar" } ]' : OK
+Testing 'invalid JSON Pointer token', doc '{ }' patch '[ { "op": "add", "path": "foo", "value": "bar" } ]' : OK
+Testing 'missing 'value' parameter to add', doc '[ 1 ]' patch '[ { "op": "add", "path": "\/-" } ]' : OK
+Testing 'missing 'value' parameter to replace', doc '[ 1 ]' patch '[ { "op": "replace", "path": "\/0" } ]' : OK
+Testing 'missing 'value' parameter to test', doc '[ null ]' patch '[ { "op": "test", "path": "\/0" } ]' : OK
+Testing 'missing value parameter to test - where undef is falsy', doc '[ false ]' patch '[ { "op": "test", "path": "\/0" } ]' : OK
+Testing 'missing from parameter to copy', doc '[ 1 ]' patch '[ { "op": "copy", "path": "\/-" } ]' : OK
+Testing 'missing from location to copy', doc '{ "foo": 1 }' patch '[ { "op": "copy", "from": "\/bar", "path": "\/foo" } ]' : OK
+Testing 'missing from parameter to move', doc '{ "foo": 1 }' patch '[ { "op": "move", "path": "" } ]' : OK
+Testing 'missing from location to move', doc '{ "foo": 1 }' patch '[ { "op": "move", "from": "\/bar", "path": "\/foo" } ]' : OK
+Testing 'duplicate ops', doc '{ "foo": "bar" }' patch '[ { "op": "move", "path": "\/baz", "value": "qux", "from": "\/foo" } ]' : SKIPPING - disabled in the test spec
+Testing 'unrecognized op should fail', doc '{ "foo": 1 }' patch '[ { "op": "spam", "path": "\/foo", "value": 1 } ]' : OK
+Testing 'test with bad array number that has leading zeros', doc '[ "foo", "bar" ]' patch '[ { "op": "test", "path": "\/00", "value": "foo" } ]' : OK
+Testing 'test with bad array number that has leading zeros', doc '[ "foo", "bar" ]' patch '[ { "op": "test", "path": "\/01", "value": "bar" } ]' : OK
+Testing 'Removing nonexistent field', doc '{ "foo": "bar" }' patch '[ { "op": "remove", "path": "\/baz" } ]' : OK
+Testing 'Removing deep nonexistent path', doc '{ "foo": "bar" }' patch '[ { "op": "remove", "path": "\/missing1\/missing2" } ]' : OK
+Testing 'Removing nonexistent index', doc '[ "foo", "bar" ]' patch '[ { "op": "remove", "path": "\/2" } ]' : OK
+Testing 'Patch with different capitalisation than doc', doc '{ "foo": "bar" }' patch '[ { "op": "add", "path": "\/FOO", "value": "BAR" } ]' : OK
diff --git a/tests/test_json_patch.test b/tests/test_json_patch.test
new file mode 100755 (executable)
index 0000000..0dc5aff
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+export _JSON_C_STRERROR_ENABLE=1
+
+# Common definitions
+if test -z "$srcdir"; then
+    srcdir="${0%/*}"
+    test "$srcdir" = "$0" && srcdir=.
+    test -z "$srcdir" && srcdir=.
+fi
+. "$srcdir/test-defs.sh"
+
+filename=$(basename "$0")
+filename="${filename%.*}"
+
+run_output_test $filename "$srcdir"
+exit $?