From 7276f4df051bc58af8a2da7d3199517d43d37582 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Tue, 11 Apr 2017 14:45:28 +0200 Subject: [PATCH 01/13] Squashed 'tests/json-patch-tests/' content from commit 716417e git-subtree-dir: tests/json-patch-tests git-subtree-split: 716417e71e328e116dc1e98b903b434578bc1a1c --- .editorconfig | 10 ++ .gitignore | 4 + .npmignore | 2 + README.md | 75 +++++++++ package.json | 15 ++ spec_tests.json | 233 +++++++++++++++++++++++++++ tests.json | 408 ++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 747 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 README.md create mode 100644 package.json create mode 100644 spec_tests.json create mode 100644 tests.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..dc79da4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# EditorConfig is awesome: http://EditorConfig.org + +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd2b23d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*~ +\#* +!.editorconfig +!.gitignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..2d6cdcb --- /dev/null +++ b/.npmignore @@ -0,0 +1,2 @@ +.editorconfig +.gitignore diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb9e447 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +JSON Patch Tests +================ + +These are test cases for implementations of [IETF JSON Patch (RFC6902)](http://tools.ietf.org/html/rfc6902). + +Some implementations can be found at [jsonpatch.com](http://jsonpatch.com). + + +Test Format +----------- + +Each test file is a JSON document that contains an array of test records. A +test record is an object with the following members: + +- doc: The JSON document to test against +- patch: The patch(es) to apply +- expected: The expected resulting document, OR +- error: A string describing an expected error +- comment: A string describing the test +- disabled: True if the test should be skipped + +All fields except 'doc' and 'patch' are optional. Test records consisting only +of a comment are also OK. + + +Files +----- + +- tests.json: the main test file +- spec_tests.json: tests from the RFC6902 spec + + +Writing Tests +------------- + +All tests should have a descriptive comment. Tests should be as +simple as possible - just what's required to test a specific piece of +behavior. If you want to test interacting behaviors, create tests for +each behavior as well as the interaction. + +If an 'error' member is specified, the error text should describe the +error the implementation should raise - *not* what's being tested. +Implementation error strings will vary, but the suggested error should +be easily matched to the implementation error string. Try to avoid +creating error tests that might pass because an incorrect error was +reported. + +Please feel free to contribute! + + +Credits +------- + +The seed test set was adapted from Byron Ruth's +[jsonpatch-js](https://github.com/bruth/jsonpatch-js/blob/master/test.js) and +extended by [Mike McCabe](https://github.com/mikemccabe). + + +License +------- + + Copyright 2014 The Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/package.json b/package.json new file mode 100644 index 0000000..256f936 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "json-patch-test-suite", + "version": "1.1.0", + "description": "JSON Patch RFC 6902 test suite", + "repository": "github:json-patch/json-patch-tests", + "homepage": "https://github.com/json-patch/json-patch-tests", + "bugs": "https://github.com/json-patch/json-patch-tests/issues", + "keywords": [ + "JSON", + "Patch", + "test", + "suite" + ], + "license": "Apache-2.0" +} diff --git a/spec_tests.json b/spec_tests.json new file mode 100644 index 0000000..c160535 --- /dev/null +++ b/spec_tests.json @@ -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 b/tests.json new file mode 100644 index 0000000..3a42eab --- /dev/null +++ b/tests.json @@ -0,0 +1,408 @@ +[ + { "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": "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": "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}], + "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}}], + "comment": "test should pass despite rearrangement" }, + + { "doc": {"foo": [{"foo": 1, "bar": 2}]}, + "patch": [{"op": "test", "path": "/foo", "value": [{"bar": 2, "foo": 1}]}], + "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]}}], + "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}] }, + + { "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}] }, + + { "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 '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 parameter to move", + "doc": { "foo": 1 }, + "patch": [ { "op": "move", "path": "" } ], + "error": "missing 'from' parameter" }, + + { "comment": "duplicate ops", + "doc": { "foo": "bar" }, + "patch": [ { "op": "add", "path": "/baz", "value": "qux", + "op": "move", "from":"/foo" } ], + "error": "patch has two 'op' members", + "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 nonexistent index", + "doc": ["foo", "bar"], + "patch": [{"op": "remove", "path": "/2"}], + "error": "removing a nonexistent index should fail" } + +] From 62ba68fc7df3da0e2cd8003b8584d901cdab381f Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Tue, 11 Apr 2017 16:38:51 +0200 Subject: [PATCH 02/13] cJSONUtils_ApplyPatches: Fix not accepting arrays This was completely broken, arrays weren't accepted. --- cJSON_Utils.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cJSON_Utils.c b/cJSON_Utils.c index 92bb0b4..5d5422d 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -526,12 +526,7 @@ CJSON_PUBLIC(int) cJSONUtils_ApplyPatches(cJSON *object, cJSON *patches) { int err = 0; - if (patches == NULL) - { - return 1; - } - - if (cJSON_IsArray(patches)) + if (!cJSON_IsArray(patches)) { /* malformed patches. */ return 1; From d058a9cd8f3314d40bf955aa230e1b943ae83fcb Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Tue, 11 Apr 2017 17:40:43 +0200 Subject: [PATCH 03/13] cJSON_ApplyPatches: Don't allow adding to array out of bounds --- cJSON_Utils.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/cJSON_Utils.c b/cJSON_Utils.c index 5d5422d..c193ae5 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -364,6 +364,44 @@ static int cJSONUtils_Compare(cJSON *a, cJSON *b) return 0; } +/* non broken version of cJSON_InsertItemInArray */ +static cJSON_bool insert_item_in_array(cJSON *array, size_t which, cJSON *newitem) +{ + cJSON *child = array->child; + while (child && (which > 0)) + { + child = child->next; + which--; + } + if (which > 0) + { + /* item is after the end of the array */ + return 0; + } + if (child == NULL) + { + cJSON_AddItemToArray(array, newitem); + return 1; + } + + /* insert into the linked list */ + newitem->next = child; + newitem->prev = child->prev; + child->prev = newitem; + + /* was it at the beginning */ + if (child == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } + + return 1; +} + static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) { cJSON *op = NULL; @@ -505,7 +543,12 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) } else { - cJSON_InsertItemInArray(parent, atoi((char*)childptr), value); + if (!insert_item_in_array(parent, (size_t)atoi((char*)childptr), value)) + { + free(parentptr); + cJSON_Delete(value); + return 10; + } } } else if (cJSON_IsObject(parent)) From a1602f484bdb6d2ff25dd09ee08097d757d899d6 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Tue, 11 Apr 2017 18:07:19 +0200 Subject: [PATCH 04/13] cJSONUtils_ApplyPatches: Don't accept invalid array indices --- cJSON_Utils.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/cJSON_Utils.c b/cJSON_Utils.c index c193ae5..bba3bf3 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -543,7 +543,25 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) } else { - if (!insert_item_in_array(parent, (size_t)atoi((char*)childptr), value)) + char *end_pointer = NULL; + long int index = strtol((char*)childptr, &end_pointer, 10); + if ((unsigned char*)end_pointer == childptr) + { + /* failed to parse numeric array index */ + free(parentptr); + cJSON_Delete(value); + return 11; + } + + if ((index < 0) || (*end_pointer != '\0')) + { + /* array index is invalid */ + free(parentptr); + cJSON_Delete(value); + return 12; + } + + if (!insert_item_in_array(parent, (size_t)index, value)) { free(parentptr); cJSON_Delete(value); From 8efb287ae214ca98728252025851715d8aa45d4e Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Wed, 12 Apr 2017 10:44:29 +0200 Subject: [PATCH 05/13] cJSONUtils_ApplyPatches: Fail if removal failed --- cJSON_Utils.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cJSON_Utils.c b/cJSON_Utils.c index bba3bf3..f089ed4 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -456,10 +456,15 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) if ((opcode == 1) || (opcode == 2)) { /* Get rid of old. */ - cJSON_Delete(cJSONUtils_PatchDetach(object, (unsigned char*)path->valuestring)); + cJSON *old_item = cJSONUtils_PatchDetach(object, (unsigned char*)path->valuestring); + if (old_item == NULL) + { + return 13; + } + cJSON_Delete(old_item); if (opcode == 1) { - /* For Remove, this is job done. */ + /* For Remove, this job is done. */ return 0; } } From b470d918f33c77fe50b5ba85f361fed6ae26e63f Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Wed, 12 Apr 2017 11:07:21 +0200 Subject: [PATCH 06/13] cJSONUtils: add decode_array_index_from_pointer as common helper function --- cJSON_Utils.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/cJSON_Utils.c b/cJSON_Utils.c index f089ed4..b17a2f1 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -262,6 +262,22 @@ static void cJSONUtils_InplaceDecodePointerString(unsigned char *string) *s2 = '\0'; } +static cJSON_bool decode_array_index_from_pointer(const unsigned char * const pointer, size_t * const index) +{ + char *end_pointer = NULL; + long int parsed_index = strtol((const char*)pointer, &end_pointer, 10); + + if (((unsigned char*)end_pointer == pointer) || (parsed_index < 0) || (*end_pointer != '\0')) + { + /* array index is invalid */ + return 0; + } + + *index = (size_t)parsed_index; + + return 1; +} + static cJSON *cJSONUtils_PatchDetach(cJSON *object, const unsigned char *path) { unsigned char *parentptr = NULL; @@ -548,25 +564,15 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) } else { - char *end_pointer = NULL; - long int index = strtol((char*)childptr, &end_pointer, 10); - if ((unsigned char*)end_pointer == childptr) + size_t index = 0; + if (!decode_array_index_from_pointer(childptr, &index)) { - /* failed to parse numeric array index */ free(parentptr); cJSON_Delete(value); return 11; } - if ((index < 0) || (*end_pointer != '\0')) - { - /* array index is invalid */ - free(parentptr); - cJSON_Delete(value); - return 12; - } - - if (!insert_item_in_array(parent, (size_t)index, value)) + if (!insert_item_in_array(parent, index, value)) { free(parentptr); cJSON_Delete(value); From 3056d85f01146ec3f3b4da7a7dd7add010c55e61 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Wed, 12 Apr 2017 11:21:21 +0200 Subject: [PATCH 07/13] cJSON_Utils: Use new helper function --- cJSON_Utils.c | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/cJSON_Utils.c b/cJSON_Utils.c index b17a2f1..dec06d5 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -278,6 +278,39 @@ static cJSON_bool decode_array_index_from_pointer(const unsigned char * const po return 1; } +/* non-broken cJSON_DetachItemFromArray */ +static cJSON *detach_item_from_array(cJSON *array, size_t which) +{ + cJSON *c = array->child; + while (c && (which > 0)) + { + c = c->next; + which--; + } + if (!c) + { + /* item doesn't exist */ + return NULL; + } + if (c->prev) + { + /* not the first element */ + c->prev->next = c->next; + } + if (c->next) + { + c->next->prev = c->prev; + } + if (c==array->child) + { + array->child = c->next; + } + /* make sure the detached item doesn't point anywhere anymore */ + c->prev = c->next = NULL; + + return c; +} + static cJSON *cJSONUtils_PatchDetach(cJSON *object, const unsigned char *path) { unsigned char *parentptr = NULL; @@ -310,7 +343,13 @@ static cJSON *cJSONUtils_PatchDetach(cJSON *object, const unsigned char *path) } else if (cJSON_IsArray(parent)) { - ret = cJSON_DetachItemFromArray(parent, atoi((char*)childptr)); + size_t index = 0; + if (!decode_array_index_from_pointer(childptr, &index)) + { + free(parentptr); + return NULL; + } + ret = detach_item_from_array(parent, index); } else if (cJSON_IsObject(parent)) { From c960b2b853372bc3dd16b5a182e976df67116953 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Wed, 12 Apr 2017 11:21:48 +0200 Subject: [PATCH 08/13] cJSON_Utils: Fix size_t support of cJSONUtils_GetPointer --- cJSON_Utils.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/cJSON_Utils.c b/cJSON_Utils.c index dec06d5..cb0a9c6 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -194,6 +194,19 @@ CJSON_PUBLIC(char *) cJSONUtils_FindPointerFromObjectTo(cJSON *object, cJSON *ta return NULL; } +/* non broken version of cJSON_GetArrayItem */ +static cJSON *get_array_item(const cJSON *array, size_t item) +{ + cJSON *child = array ? array->child : NULL; + while ((child != NULL) && (item > 0)) + { + item--; + child = child->next; + } + + return child; +} + CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointer(cJSON *object, const char *pointer) { /* follow path of the pointer */ @@ -201,22 +214,18 @@ CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointer(cJSON *object, const char *pointer) { if (cJSON_IsArray(object)) { - size_t which = 0; + size_t index = 0; /* parse array index */ while ((*pointer >= '0') && (*pointer <= '9')) { - which = (10 * which) + (size_t)(*pointer++ - '0'); + index = (10 * index) + (size_t)(*pointer++ - '0'); } if (*pointer && (*pointer != '/')) { /* not end of string or new path token */ return NULL; } - if (which > INT_MAX) - { - return NULL; - } - object = cJSON_GetArrayItem(object, (int)which); + object = get_array_item(object, index); } else if (cJSON_IsObject(object)) { From c66342d871ecbae9d644917d8a0f8f8bdabeff62 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Wed, 12 Apr 2017 11:36:14 +0200 Subject: [PATCH 09/13] cJSON_Utils: Use enum for opcode --- cJSON_Utils.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/cJSON_Utils.c b/cJSON_Utils.c index cb0a9c6..e6f8ceb 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -466,13 +466,15 @@ static cJSON_bool insert_item_in_array(cJSON *array, size_t which, cJSON *newite return 1; } +enum patch_operation { INVALID, ADD, REMOVE, REPLACE, MOVE, COPY, TEST }; + static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) { cJSON *op = NULL; cJSON *path = NULL; cJSON *value = NULL; cJSON *parent = NULL; - int opcode = 0; + enum patch_operation opcode = INVALID; unsigned char *parentptr = NULL; unsigned char *childptr = NULL; @@ -487,23 +489,23 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) /* decode operation */ if (!strcmp(op->valuestring, "add")) { - opcode = 0; + opcode = ADD; } else if (!strcmp(op->valuestring, "remove")) { - opcode = 1; + opcode = REMOVE; } else if (!strcmp(op->valuestring, "replace")) { - opcode = 2; + opcode = REPLACE; } else if (!strcmp(op->valuestring, "move")) { - opcode = 3; + opcode = MOVE; } else if (!strcmp(op->valuestring, "copy")) { - opcode = 4; + opcode = COPY; } else if (!strcmp(op->valuestring, "test")) { @@ -516,8 +518,7 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) return 3; } - /* Remove/Replace */ - if ((opcode == 1) || (opcode == 2)) + if ((opcode == REMOVE) || (opcode == REPLACE)) { /* Get rid of old. */ cJSON *old_item = cJSONUtils_PatchDetach(object, (unsigned char*)path->valuestring); @@ -526,7 +527,7 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) return 13; } cJSON_Delete(old_item); - if (opcode == 1) + if (opcode == REMOVE) { /* For Remove, this job is done. */ return 0; @@ -534,7 +535,7 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) } /* Copy/Move uses "from". */ - if ((opcode == 3) || (opcode == 4)) + if ((opcode == MOVE) || (opcode == COPY)) { cJSON *from = cJSON_GetObjectItem(patch, "from"); if (!from) @@ -543,14 +544,12 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) return 4; } - if (opcode == 3) + if (opcode == MOVE) { - /* move */ value = cJSONUtils_PatchDetach(object, (unsigned char*)from->valuestring); } - if (opcode == 4) + if (opcode == COPY) { - /* copy */ value = cJSONUtils_GetPointer(object, from->valuestring); } if (!value) @@ -558,7 +557,7 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) /* missing "from" for copy/move. */ return 5; } - if (opcode == 4) + if (opcode == COPY) { value = cJSON_Duplicate(value, 1); } From d67b008d4b420a247da269fc5a9c7e4066cbe053 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Wed, 12 Apr 2017 12:06:27 +0200 Subject: [PATCH 10/13] decode_array_index_from_pointer: parse manually This allows checking for leading zeroes and invalid characters after the index --- cJSON_Utils.c | 52 ++++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/cJSON_Utils.c b/cJSON_Utils.c index e6f8ceb..da5414c 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -207,6 +207,33 @@ static cJSON *get_array_item(const cJSON *array, size_t item) return child; } +static cJSON_bool decode_array_index_from_pointer(const unsigned char * const pointer, size_t * const index) +{ + size_t parsed_index = 0; + size_t position = 0; + + if ((pointer[0] == '0') && ((pointer[1] != '\0') && (pointer[1] != '/'))) + { + /* leading zeroes are not permitted */ + return 0; + } + + for (position = 0; (pointer[position] >= '0') && (*pointer <= '9'); position++) + { + parsed_index = (10 * parsed_index) + (size_t)(pointer[position] - '0'); + + } + + if ((pointer[position] != '\0') && (pointer[position] != '/')) + { + return 0; + } + + *index = parsed_index; + + return 1; +} + CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointer(cJSON *object, const char *pointer) { /* follow path of the pointer */ @@ -215,16 +242,11 @@ CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointer(cJSON *object, const char *pointer) if (cJSON_IsArray(object)) { size_t index = 0; - /* parse array index */ - while ((*pointer >= '0') && (*pointer <= '9')) + if (!decode_array_index_from_pointer((const unsigned char*)pointer, &index)) { - index = (10 * index) + (size_t)(*pointer++ - '0'); - } - if (*pointer && (*pointer != '/')) - { - /* not end of string or new path token */ return NULL; } + object = get_array_item(object, index); } else if (cJSON_IsObject(object)) @@ -271,22 +293,6 @@ static void cJSONUtils_InplaceDecodePointerString(unsigned char *string) *s2 = '\0'; } -static cJSON_bool decode_array_index_from_pointer(const unsigned char * const pointer, size_t * const index) -{ - char *end_pointer = NULL; - long int parsed_index = strtol((const char*)pointer, &end_pointer, 10); - - if (((unsigned char*)end_pointer == pointer) || (parsed_index < 0) || (*end_pointer != '\0')) - { - /* array index is invalid */ - return 0; - } - - *index = (size_t)parsed_index; - - return 1; -} - /* non-broken cJSON_DetachItemFromArray */ static cJSON *detach_item_from_array(cJSON *array, size_t which) { From 02a05eea4e6ba41811f130b322660bea8918e1a0 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Wed, 12 Apr 2017 20:47:59 +0200 Subject: [PATCH 11/13] cJSON: Add cJSON_malloc and cJSON_free --- cJSON.c | 10 ++++++++++ cJSON.h | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/cJSON.c b/cJSON.c index bc343ba..a25556d 100644 --- a/cJSON.c +++ b/cJSON.c @@ -2585,3 +2585,13 @@ CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * cons return false; } } + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); +} diff --git a/cJSON.h b/cJSON.h index c29ba17..0ddab6f 100644 --- a/cJSON.h +++ b/cJSON.h @@ -241,6 +241,10 @@ CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); /* Macro for iterating over an array */ #define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + #ifdef __cplusplus } #endif From 134ebf5e891a52503123b88965938c4637890321 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Wed, 12 Apr 2017 19:14:07 +0200 Subject: [PATCH 12/13] cJSONUtils_ApplyPatches: Handle replacement of root --- cJSON_Utils.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/cJSON_Utils.c b/cJSON_Utils.c index da5414c..aac2b07 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -486,7 +486,7 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) op = cJSON_GetObjectItem(patch, "op"); path = cJSON_GetObjectItem(patch, "path"); - if (!op || !path) + if (!cJSON_IsString(op) || !cJSON_IsString(path)) { /* malformed patch. */ return 2; @@ -524,6 +524,81 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) return 3; } + /* special case for replacing the root */ + if (path->valuestring[0] == '\0') + { + if (opcode == REMOVE) + { + /* remove possible children */ + if (object->child != NULL) + { + cJSON_Delete(object->child); + } + + /* remove other allocated resources */ + if (object->string != NULL) + { + cJSON_free(object->string); + } + if (object->valuestring != NULL) + { + cJSON_free(object->valuestring); + } + + /* make it invalid */ + memset(object, '\0', sizeof(cJSON)); + + return 0; + } + + if ((opcode == REPLACE) || (opcode == ADD)) + { + /* remove possible children */ + if (object->child != NULL) + { + cJSON_Delete(object->child); + } + + /* remove other allocated resources */ + if (object->string != NULL) + { + cJSON_free(object->string); + } + if (object->valuestring != NULL) + { + cJSON_free(object->valuestring); + } + + value = cJSON_GetObjectItem(patch, "value"); + if (value == NULL) + { + /* missing "value" for add/replace. */ + return 7; + } + + value = cJSON_Duplicate(value, 1); + if (value == NULL) + { + /* out of memory for add/replace. */ + return 8; + } + /* the string "value" isn't needed */ + if (value->string != NULL) + { + cJSON_free(value->string); + value->string = NULL; + } + + /* copy over the value object */ + memcpy(object, value, sizeof(cJSON)); + + /* delete the duplicated value */ + cJSON_free(value); + + return 0; + } + } + if ((opcode == REMOVE) || (opcode == REPLACE)) { /* Get rid of old. */ From ba7b48b3f3a15fe94f4f5a3bf2aa7fc3bafe25e8 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Wed, 12 Apr 2017 19:15:53 +0200 Subject: [PATCH 13/13] Enable json-patch-tests tests --- tests/CMakeLists.txt | 24 ++++++ tests/json_patch_tests.c | 162 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 tests/json_patch_tests.c diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bb7c6fa..ff56a92 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -75,4 +75,28 @@ if(ENABLE_CJSON_TEST) endforeach() add_dependencies(check ${unity_tests}) + + if (ENABLE_CJSON_UTILS) + #copy test files + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/json-patch-tests") + file(GLOB test_files "json-patch-tests/*") + file(COPY ${test_files} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/json-patch-tests/") + + set (cjson_utils_tests + json_patch_tests) + + foreach (cjson_utils_test ${cjson_utils_tests}) + add_executable("${cjson_utils_test}" "${cjson_utils_test}.c") + target_link_libraries("${cjson_utils_test}" "${CJSON_LIB}" "${CJSON_UTILS_LIB}" unity test-common) + if(MEMORYCHECK_COMMAND) + add_test(NAME "${cjson_utils_test}" + COMMAND "${MEMORYCHECK_COMMAND}" ${MEMORYCHECK_COMMAND_OPTIONS} "${CMAKE_CURRENT_BINARY_DIR}/${cjson_utils_test}") + else() + add_test(NAME "${cjson_utils_test}" + COMMAND "./${cjson_utils_test}") + endif() + endforeach() + + add_dependencies(check ${cjson_utils_tests}) + endif() endif() diff --git a/tests/json_patch_tests.c b/tests/json_patch_tests.c new file mode 100644 index 0000000..a1c03eb --- /dev/null +++ b/tests/json_patch_tests.c @@ -0,0 +1,162 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#include +#include +#include + +#include "unity/examples/unity_config.h" +#include "unity/src/unity.h" +#include "common.h" +#include "../cJSON_Utils.h" + +static cJSON *parse_test_file(const char * const filename) +{ + char *file = NULL; + cJSON *json = NULL; + + file = read_file(filename); + TEST_ASSERT_NOT_NULL_MESSAGE(file, "Failed to read file."); + + json = cJSON_Parse(file); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "Failed to parse test json."); + TEST_ASSERT_TRUE_MESSAGE(cJSON_IsArray(json), "Json is not an array."); + + free(file); + + return json; +} + +static cJSON_bool test_apply_patch(const cJSON * const test) +{ + cJSON *doc = NULL; + cJSON *patch = NULL; + cJSON *expected = NULL; + cJSON *error_element = NULL; + cJSON *comment = NULL; + cJSON *disabled = NULL; + + cJSON *object = NULL; + cJSON_bool successful = false; + + /* extract all the data out of the test */ + comment = cJSON_GetObjectItem(test, "comment"); + if (cJSON_IsString(comment)) + { + printf("Testing \"%s\"\n", comment->valuestring); + } + else + { + printf("Testing unkown\n"); + } + + disabled = cJSON_GetObjectItem(test, "disabled"); + if (cJSON_IsTrue(disabled)) + { + printf("SKIPPED\n"); + return true; + } + + doc = cJSON_GetObjectItem(test, "doc"); + TEST_ASSERT_NOT_NULL_MESSAGE(doc, "No \"doc\" in the test."); + patch = cJSON_GetObjectItem(test, "patch"); + TEST_ASSERT_NOT_NULL_MESSAGE(patch, "No \"patch\"in the test."); + /* Make a working copy of 'doc' */ + object = cJSON_Duplicate(doc, true); + TEST_ASSERT_NOT_NULL(object); + + expected = cJSON_GetObjectItem(test, "expected"); + error_element = cJSON_GetObjectItem(test, "error"); + if (error_element != NULL) + { + /* excepting an error */ + TEST_ASSERT_TRUE_MESSAGE(0 != cJSONUtils_ApplyPatches(object, patch), "Test didn't fail as it's supposed to."); + + successful = true; + } + else + { + /* apply the patch */ + TEST_ASSERT_EQUAL_INT_MESSAGE(0, cJSONUtils_ApplyPatches(object, patch), "Failed to apply patches."); + successful = true; + + if (expected != NULL) + { + successful = cJSON_Compare(object, expected, true); + } + } + + cJSON_Delete(object); + + if (successful) + { + printf("OK\n"); + } + else + { + printf("FAILED\n"); + } + + return successful; +} + +static void cjson_utils_should_pass_json_patch_test_tests(void) +{ + cJSON *tests = parse_test_file("json-patch-tests/tests.json"); + cJSON *test = NULL; + + cJSON_bool failed = false; + cJSON_ArrayForEach(test, tests) + { + failed |= !test_apply_patch(test); + } + + cJSON_Delete(tests); + + TEST_ASSERT_FALSE_MESSAGE(failed, "Some tests failed."); +} + +static void cjson_utils_should_pass_json_patch_test_spec_tests(void) +{ + cJSON *tests = parse_test_file("json-patch-tests/spec_tests.json"); + cJSON *test = NULL; + + cJSON_bool failed = false; + cJSON_ArrayForEach(test, tests) + { + failed |= !test_apply_patch(test); + } + + cJSON_Delete(tests); + + TEST_ASSERT_FALSE_MESSAGE(failed, "Some tests failed."); +} + +int main(void) +{ + UNITY_BEGIN(); + + RUN_TEST(cjson_utils_should_pass_json_patch_test_tests); + RUN_TEST(cjson_utils_should_pass_json_patch_test_spec_tests); + + return UNITY_END(); +}