4
4
from __future__ import unicode_literals
5
5
6
6
import json
7
+ import decimal
7
8
import doctest
8
9
import unittest
9
10
import jsonpatch
@@ -278,6 +279,34 @@ def test_str(self):
278
279
self .assertEqual (json .dumps (patch_obj ), patch .to_string ())
279
280
280
281
282
+ def custom_types_dumps (obj ):
283
+ def default (obj ):
284
+ if isinstance (obj , decimal .Decimal ):
285
+ return {'__decimal__' : str (obj )}
286
+ raise TypeError ('Unknown type' )
287
+
288
+ return json .dumps (obj , default = default )
289
+
290
+
291
+ def custom_types_loads (obj ):
292
+ def as_decimal (dct ):
293
+ if '__decimal__' in dct :
294
+ return decimal .Decimal (dct ['__decimal__' ])
295
+ return dct
296
+
297
+ return json .loads (obj , object_hook = as_decimal )
298
+
299
+
300
+ class CustomTypesJsonPatch (jsonpatch .JsonPatch ):
301
+ @staticmethod
302
+ def json_dumper (obj ):
303
+ return custom_types_dumps (obj )
304
+
305
+ @staticmethod
306
+ def json_loader (obj ):
307
+ return custom_types_loads (obj )
308
+
309
+
281
310
class MakePatchTestCase (unittest .TestCase ):
282
311
283
312
def test_apply_patch_to_copy (self ):
@@ -456,6 +485,35 @@ def test_issue103(self):
456
485
self .assertEqual (res , dst )
457
486
self .assertIsInstance (res ['A' ], float )
458
487
488
+ def test_custom_types_diff (self ):
489
+ old = {'value' : decimal .Decimal ('1.0' )}
490
+ new = {'value' : decimal .Decimal ('1.00' )}
491
+ generated_patch = jsonpatch .JsonPatch .from_diff (
492
+ old , new , dumps = custom_types_dumps )
493
+ str_patch = generated_patch .to_string (dumps = custom_types_dumps )
494
+ loaded_patch = jsonpatch .JsonPatch .from_string (
495
+ str_patch , loads = custom_types_loads )
496
+ self .assertEqual (generated_patch , loaded_patch )
497
+ new_from_patch = jsonpatch .apply_patch (old , generated_patch )
498
+ self .assertEqual (new , new_from_patch )
499
+
500
+ def test_custom_types_subclass (self ):
501
+ old = {'value' : decimal .Decimal ('1.0' )}
502
+ new = {'value' : decimal .Decimal ('1.00' )}
503
+ generated_patch = CustomTypesJsonPatch .from_diff (old , new )
504
+ str_patch = generated_patch .to_string ()
505
+ loaded_patch = CustomTypesJsonPatch .from_string (str_patch )
506
+ self .assertEqual (generated_patch , loaded_patch )
507
+ new_from_patch = jsonpatch .apply_patch (old , loaded_patch )
508
+ self .assertEqual (new , new_from_patch )
509
+
510
+ def test_custom_types_subclass_load (self ):
511
+ old = {'value' : decimal .Decimal ('1.0' )}
512
+ new = {'value' : decimal .Decimal ('1.00' )}
513
+ patch = CustomTypesJsonPatch .from_string (
514
+ '[{"op": "replace", "path": "/value", "value": {"__decimal__": "1.00"}}]' )
515
+ new_from_patch = jsonpatch .apply_patch (old , patch )
516
+ self .assertEqual (new , new_from_patch )
459
517
460
518
461
519
class OptimizationTests (unittest .TestCase ):
@@ -671,6 +729,86 @@ def test_create_with_pointer(self):
671
729
self .assertEqual (result , expected )
672
730
673
731
732
+ class JsonPatchCreationTest (unittest .TestCase ):
733
+
734
+ def test_creation_fails_with_invalid_patch (self ):
735
+ invalid_patches = [
736
+ { 'path' : '/foo' , 'value' : 'bar' },
737
+ {'op' : 0xADD , 'path' : '/foo' , 'value' : 'bar' },
738
+ {'op' : 'boo' , 'path' : '/foo' , 'value' : 'bar' },
739
+ {'op' : 'add' , 'value' : 'bar' },
740
+ ]
741
+ for patch in invalid_patches :
742
+ with self .assertRaises (jsonpatch .InvalidJsonPatch ):
743
+ jsonpatch .JsonPatch ([patch ])
744
+
745
+ with self .assertRaises (jsonpointer .JsonPointerException ):
746
+ jsonpatch .JsonPatch ([{'op' : 'add' , 'path' : 'foo' , 'value' : 'bar' }])
747
+
748
+
749
+ class UtilityMethodTests (unittest .TestCase ):
750
+
751
+ def test_boolean_coercion (self ):
752
+ empty_patch = jsonpatch .JsonPatch ([])
753
+ self .assertFalse (empty_patch )
754
+
755
+ def test_patch_equality (self ):
756
+ p = jsonpatch .JsonPatch ([{'op' : 'add' , 'path' : '/foo' , 'value' : 'bar' }])
757
+ q = jsonpatch .JsonPatch ([{'op' : 'add' , 'path' : '/foo' , 'value' : 'bar' }])
758
+ different_op = jsonpatch .JsonPatch ([{'op' : 'remove' , 'path' : '/foo' }])
759
+ different_path = jsonpatch .JsonPatch ([{'op' : 'add' , 'path' : '/bar' , 'value' : 'bar' }])
760
+ different_value = jsonpatch .JsonPatch ([{'op' : 'add' , 'path' : '/foo' , 'value' : 'foo' }])
761
+ self .assertNotEqual (p , different_op )
762
+ self .assertNotEqual (p , different_path )
763
+ self .assertNotEqual (p , different_value )
764
+ self .assertEqual (p , q )
765
+
766
+ def test_operation_equality (self ):
767
+ add = jsonpatch .AddOperation ({'path' : '/new-element' , 'value' : 'new-value' })
768
+ add2 = jsonpatch .AddOperation ({'path' : '/new-element' , 'value' : 'new-value' })
769
+ rm = jsonpatch .RemoveOperation ({'path' : '/target' })
770
+ self .assertEqual (add , add2 )
771
+ self .assertNotEqual (add , rm )
772
+
773
+ def test_add_operation_structure (self ):
774
+ with self .assertRaises (jsonpatch .InvalidJsonPatch ):
775
+ jsonpatch .AddOperation ({'path' : '/' }).apply ({})
776
+
777
+ def test_replace_operation_structure (self ):
778
+ with self .assertRaises (jsonpatch .InvalidJsonPatch ):
779
+ jsonpatch .ReplaceOperation ({'path' : '/' }).apply ({})
780
+
781
+ with self .assertRaises (jsonpatch .InvalidJsonPatch ):
782
+ jsonpatch .ReplaceOperation ({'path' : '/top/-' , 'value' : 'foo' }).apply ({'top' : {'inner' : 'value' }})
783
+
784
+ with self .assertRaises (jsonpatch .JsonPatchConflict ):
785
+ jsonpatch .ReplaceOperation ({'path' : '/top/missing' , 'value' : 'foo' }).apply ({'top' : {'inner' : 'value' }})
786
+
787
+ def test_move_operation_structure (self ):
788
+ with self .assertRaises (jsonpatch .InvalidJsonPatch ):
789
+ jsonpatch .MoveOperation ({'path' : '/target' }).apply ({})
790
+
791
+ with self .assertRaises (jsonpatch .JsonPatchConflict ):
792
+ jsonpatch .MoveOperation ({'from' : '/source' , 'path' : '/target' }).apply ({})
793
+
794
+ def test_test_operation_structure (self ):
795
+ with self .assertRaises (jsonpatch .JsonPatchTestFailed ):
796
+ jsonpatch .TestOperation ({'path' : '/target' }).apply ({})
797
+
798
+ with self .assertRaises (jsonpatch .InvalidJsonPatch ):
799
+ jsonpatch .TestOperation ({'path' : '/target' }).apply ({'target' : 'value' })
800
+
801
+ def test_copy_operation_structure (self ):
802
+ with self .assertRaises (jsonpatch .InvalidJsonPatch ):
803
+ jsonpatch .CopyOperation ({'path' : '/target' }).apply ({})
804
+
805
+ with self .assertRaises (jsonpatch .JsonPatchConflict ):
806
+ jsonpatch .CopyOperation ({'path' : '/target' , 'from' : '/source' }).apply ({})
807
+
808
+ with self .assertRaises (jsonpatch .JsonPatchConflict ):
809
+ jsonpatch .CopyOperation ({'path' : '/target' , 'from' : '/source' }).apply ({})
810
+
811
+
674
812
class CustomJsonPointer (jsonpointer .JsonPointer ):
675
813
pass
676
814
@@ -690,7 +828,7 @@ def test_json_patch_from_string(self):
690
828
self .assertEqual (res .pointer_cls , CustomJsonPointer )
691
829
692
830
def test_json_patch_from_object (self ):
693
- patch = [{'op' : 'add' , 'path' : '/baz' , 'value' : 'qux' }],
831
+ patch = [{'op' : 'add' , 'path' : '/baz' , 'value' : 'qux' }]
694
832
res = jsonpatch .JsonPatch (
695
833
patch , pointer_cls = CustomJsonPointer ,
696
834
)
@@ -815,6 +953,8 @@ def get_suite():
815
953
suite .addTest (unittest .makeSuite (ConflictTests ))
816
954
suite .addTest (unittest .makeSuite (OptimizationTests ))
817
955
suite .addTest (unittest .makeSuite (JsonPointerTests ))
956
+ suite .addTest (unittest .makeSuite (JsonPatchCreationTest ))
957
+ suite .addTest (unittest .makeSuite (UtilityMethodTests ))
818
958
suite .addTest (unittest .makeSuite (CustomJsonPointerTests ))
819
959
return suite
820
960
0 commit comments