summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Kögl <stefan@skoegl.net>2020-11-20 13:26:48 +0100
committerGitHub <noreply@github.com>2020-11-20 13:26:48 +0100
commit24b5e86dd66824dce232ea07e95d39a67b1dd735 (patch)
treebbcec9e1ba705b381b10f7bf9160b25271c62004
parent511cbc25ec27068fa698818382ec19b6653f34ca (diff)
parent3bb33518194b0cbc6e1512dbeb2ac5ef548d8c72 (diff)
downloadpython-json-patch-24b5e86dd66824dce232ea07e95d39a67b1dd735.tar.gz
Merge pull request #116 from dave-shawley/validate-patch-doc
Fix #110 - validate patch document during creation
-rw-r--r--.coveragerc2
-rw-r--r--jsonpatch.py7
-rw-r--r--requirements-dev.txt1
-rwxr-xr-xtests.py81
4 files changed, 91 insertions, 0 deletions
diff --git a/.coveragerc b/.coveragerc
index 2a98e09..f0d91db 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,8 +1,10 @@
# .coveragerc to control coverage.py
[run]
branch = True
+source = jsonpatch
[report]
+show_missing = True
# Regexes for lines to exclude from consideration
exclude_lines =
# Have to re-enable the standard pragma
diff --git a/jsonpatch.py b/jsonpatch.py
index d7c1988..dc54efc 100644
--- a/jsonpatch.py
+++ b/jsonpatch.py
@@ -225,6 +225,13 @@ class JsonPatch(object):
'copy': CopyOperation,
}
+ # Verify that the structure of the patch document
+ # is correct by retrieving each patch element.
+ # Much of the validation is done in the initializer
+ # though some is delayed until the patch is applied.
+ for op in self.patch:
+ self._get_operation(op)
+
def __str__(self):
"""str(self) -> self.to_string()"""
return self.to_string()
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 21daf9a..c729ece 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,2 +1,3 @@
+coverage
wheel
pypandoc
diff --git a/tests.py b/tests.py
index e7a724c..b69f6e1 100755
--- a/tests.py
+++ b/tests.py
@@ -729,6 +729,85 @@ class JsonPointerTests(unittest.TestCase):
self.assertEqual(result, expected)
+class JsonPatchCreationTest(unittest.TestCase):
+
+ def test_creation_fails_with_invalid_patch(self):
+ invalid_patches = [
+ { 'path': '/foo', 'value': 'bar'},
+ {'op': 0xADD, 'path': '/foo', 'value': 'bar'},
+ {'op': 'boo', 'path': '/foo', 'value': 'bar'},
+ {'op': 'add', 'value': 'bar'},
+ ]
+ for patch in invalid_patches:
+ with self.assertRaises(jsonpatch.InvalidJsonPatch):
+ jsonpatch.JsonPatch([patch])
+
+ with self.assertRaises(jsonpointer.JsonPointerException):
+ jsonpatch.JsonPatch([{'op': 'add', 'path': 'foo', 'value': 'bar'}])
+
+
+class UtilityMethodTests(unittest.TestCase):
+
+ def test_boolean_coercion(self):
+ empty_patch = jsonpatch.JsonPatch([])
+ self.assertFalse(empty_patch)
+
+ def test_patch_equality(self):
+ p = jsonpatch.JsonPatch([{'op': 'add', 'path': '/foo', 'value': 'bar'}])
+ q = jsonpatch.JsonPatch([{'op': 'add', 'path': '/foo', 'value': 'bar'}])
+ different_op = jsonpatch.JsonPatch([{'op': 'remove', 'path': '/foo'}])
+ different_path = jsonpatch.JsonPatch([{'op': 'add', 'path': '/bar', 'value': 'bar'}])
+ different_value = jsonpatch.JsonPatch([{'op': 'add', 'path': '/foo', 'value': 'foo'}])
+ self.assertNotEqual(p, different_op)
+ self.assertNotEqual(p, different_path)
+ self.assertNotEqual(p, different_value)
+ self.assertEqual(p, q)
+
+ def test_operation_equality(self):
+ add = jsonpatch.AddOperation({'path': '/new-element', 'value': 'new-value'})
+ add2 = jsonpatch.AddOperation({'path': '/new-element', 'value': 'new-value'})
+ rm = jsonpatch.RemoveOperation({'path': '/target'})
+ self.assertEqual(add, add2)
+ self.assertNotEqual(add, rm)
+
+ def test_add_operation_structure(self):
+ with self.assertRaises(jsonpatch.InvalidJsonPatch):
+ jsonpatch.AddOperation({'path': '/'}).apply({})
+
+ def test_replace_operation_structure(self):
+ with self.assertRaises(jsonpatch.InvalidJsonPatch):
+ jsonpatch.ReplaceOperation({'path': '/'}).apply({})
+
+ with self.assertRaises(jsonpatch.InvalidJsonPatch):
+ jsonpatch.ReplaceOperation({'path': '/top/-', 'value': 'foo'}).apply({'top': {'inner': 'value'}})
+
+ with self.assertRaises(jsonpatch.JsonPatchConflict):
+ jsonpatch.ReplaceOperation({'path': '/top/missing', 'value': 'foo'}).apply({'top': {'inner': 'value'}})
+
+ def test_move_operation_structure(self):
+ with self.assertRaises(jsonpatch.InvalidJsonPatch):
+ jsonpatch.MoveOperation({'path': '/target'}).apply({})
+
+ with self.assertRaises(jsonpatch.JsonPatchConflict):
+ jsonpatch.MoveOperation({'from': '/source', 'path': '/target'}).apply({})
+
+ def test_test_operation_structure(self):
+ with self.assertRaises(jsonpatch.JsonPatchTestFailed):
+ jsonpatch.TestOperation({'path': '/target'}).apply({})
+
+ with self.assertRaises(jsonpatch.InvalidJsonPatch):
+ jsonpatch.TestOperation({'path': '/target'}).apply({'target': 'value'})
+
+ def test_copy_operation_structure(self):
+ with self.assertRaises(jsonpatch.InvalidJsonPatch):
+ jsonpatch.CopyOperation({'path': '/target'}).apply({})
+
+ with self.assertRaises(jsonpatch.JsonPatchConflict):
+ jsonpatch.CopyOperation({'path': '/target', 'from': '/source'}).apply({})
+
+ with self.assertRaises(jsonpatch.JsonPatchConflict):
+ jsonpatch.CopyOperation({'path': '/target', 'from': '/source'}).apply({})
+
if __name__ == '__main__':
modules = ['jsonpatch']
@@ -745,6 +824,8 @@ if __name__ == '__main__':
suite.addTest(unittest.makeSuite(ConflictTests))
suite.addTest(unittest.makeSuite(OptimizationTests))
suite.addTest(unittest.makeSuite(JsonPointerTests))
+ suite.addTest(unittest.makeSuite(JsonPatchCreationTest))
+ suite.addTest(unittest.makeSuite(UtilityMethodTests))
return suite