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 index da4e509..cd2b23d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +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 index bbfff89..fb9e447 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ JSON Patch Tests ================ -These are test cases for implementations of the [IETF JSON Patch -draft](http://tools.ietf.org/html/draft-ietf-appsawg-json-patch). +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 @@ -21,7 +22,30 @@ test record is an object with the following members: All fields except 'doc' and 'patch' are optional. Test records consisting only of a comment are also OK. -These tests are not complete, or even correct - help welcome! + +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 @@ -35,7 +59,7 @@ extended by [Mike McCabe](https://github.com/mikemccabe). License ------- - Copyright 2012 The Authors + 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. 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 index 8380915..c160535 100644 --- a/spec_tests.json +++ b/spec_tests.json @@ -174,14 +174,14 @@ }, { - "comment": "A.12. Adding to a Non-existant Target", + "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-existant target" + "error": "add to a non-existent target" }, { @@ -192,7 +192,8 @@ "patch": [ { "op": "add", "path": "/baz", "value": "qux", "op": "remove" } ], - "error": "operation has two 'op' members" + "error": "operation has two 'op' members", + "disabled": true }, { diff --git a/tests.json b/tests.json index e604d1c..ae1f7f0 100644 --- a/tests.json +++ b/tests.json @@ -55,11 +55,31 @@ "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]}], @@ -107,10 +127,16 @@ "patch": [{"op": "add", "path": "/0", "value": "bar"}], "expected": ["bar", "foo", "sil"] }, - { "doc": ["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"}], @@ -176,6 +202,11 @@ "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}], @@ -183,18 +214,47 @@ { "doc": {"foo": null}, "patch": [{"op": "test", "path": "/foo", "value": null}], - "comment": "null value should still be valid obj property" }, + "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]}}, @@ -208,7 +268,8 @@ { "comment": "Empty-string element", "doc": { "": 1 }, - "patch": [{"op": "test", "path": "/", "value": 1}] }, + "patch": [{"op": "test", "path": "/", "value": 1}], + "expected": { "": 1 } }, { "doc": { "foo": ["bar", "baz"], @@ -232,8 +293,23 @@ {"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}] }, - + {"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"}], @@ -276,6 +352,12 @@ "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"}], @@ -299,5 +381,120 @@ { "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" } + "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": 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"} }, + + { "comment": "test copy object then change destination", + "doc": {"foo": {"bar": {"baz": [{"boo": "net"}]}}}, + "patch": [ + {"op": "copy", "from": "/foo", "path": "/bak"}, + {"op": "replace", "path": "/bak/bar/baz/0/boo", "value": "qux"} + ], + "expected": {"foo": {"bar": {"baz": [{"boo": "net"}]}}, "bak": {"bar": {"baz": [{"boo":"qux"}]}}} }, + + { "comment": "test copy object then change source", + "doc": {"foo": {"bar": {"baz": [{"boo": "net"}]}}}, + "patch": [ + {"op": "copy", "from": "/foo", "path": "/bak"}, + {"op": "replace", "path": "/foo/bar/baz/0/boo", "value": "qux"} + ], + "expected": {"foo": {"bar": {"baz": [{"boo": "qux"}]}}, "bak": {"bar": {"baz": [{"boo":"net"}]}}} + } + ]