1
+ import unittest
2
+ from jsonschema import ValidationError
3
+ from jsonschema .custom_validators import custom_validate
4
+
5
+
6
+ class TestCustomValidator (unittest .TestCase ):
7
+
8
+ def setUp (self ):
9
+ # Sample schema for various test cases
10
+ self .schema = {
11
+ "type" : "object" ,
12
+ "properties" : {
13
+ "Sodium" : {
14
+ "type" : "integer" ,
15
+ "description" : "Sodium level in mg."
16
+ },
17
+ "Carbohydrate" : {
18
+ "type" : "string" ,
19
+ "enum" : ["Low" , "High" ]
20
+ },
21
+ "FluidRestriction" : {
22
+ "type" : "integer" ,
23
+ "description" : "Fluid restriction in cc/24 hours."
24
+ },
25
+ "Diet" : {
26
+ "type" : "object" ,
27
+ "properties" : {
28
+ "HighProtein" : {
29
+ "type" : "integer"
30
+ },
31
+ "LowProtein" : {
32
+ "type" : "integer"
33
+ },
34
+ "DietType" : {
35
+ "type" : "string" ,
36
+ "enum" : ["Vegetarian" , "Non-Vegetarian" , "Vegan" ]
37
+ }
38
+ },
39
+ "additionalProperties" : False
40
+ }
41
+ },
42
+ "required" : ["Sodium" ],
43
+ "additionalProperties" : False
44
+ }
45
+
46
+ def test_valid_instance (self ):
47
+ instance = {
48
+ "Sodium" : 140 ,
49
+ "Carbohydrate" : "Low" ,
50
+ "FluidRestriction" : 1500 ,
51
+ "Diet" : {
52
+ "HighProtein" : 100 ,
53
+ "DietType" : "Vegan"
54
+ }
55
+ }
56
+ try :
57
+ custom_validate (instance , self .schema )
58
+ except ValidationError :
59
+ self .fail ("custom_validate raised ValidationError unexpectedly!" )
60
+
61
+ def test_missing_required_property (self ):
62
+ instance = {
63
+ "Carbohydrate" : "Low" ,
64
+ "FluidRestriction" : 1500
65
+ }
66
+ with self .assertRaises (ValidationError ):
67
+ custom_validate (instance , self .schema )
68
+
69
+ def test_enum_with_nullable_valid (self ):
70
+ instance = {
71
+ "Sodium" : 140 ,
72
+ "Carbohydrate" : None # Enum property is None
73
+ }
74
+ try :
75
+ custom_validate (instance , self .schema )
76
+ except ValidationError :
77
+ self .fail ("custom_validate raised ValidationError unexpectedly!" )
78
+
79
+ def test_enum_with_nullable_invalid (self ):
80
+ instance = {
81
+ "Sodium" : 140 ,
82
+ "Carbohydrate" : "Medium" # Not in the enum
83
+ }
84
+ with self .assertRaises (ValidationError ):
85
+ custom_validate (instance , self .schema )
86
+
87
+ def test_enum_subproperty_with_nullable_valid (self ):
88
+ instance = {
89
+ "Sodium" : 140 ,
90
+ "Diet" : {
91
+ "DietType" : None # Enum subproperty is None
92
+ }
93
+ }
94
+ try :
95
+ custom_validate (instance , self .schema )
96
+ except ValidationError :
97
+ self .fail ("custom_validate raised ValidationError unexpectedly!" )
98
+
99
+ def test_enum_subproperty_with_nullable_invalid (self ):
100
+ instance = {
101
+ "Sodium" : 140 ,
102
+ "Diet" : {
103
+ "DietType" : "Keto" # Not in the enum for DietType
104
+ }
105
+ }
106
+ with self .assertRaises (ValidationError ):
107
+ custom_validate (instance , self .schema )
108
+
109
+ def test_ignore_none_for_missing_properties (self ):
110
+ instance = {
111
+ "Sodium" : 140 ,
112
+ "Carbohydrate" : None
113
+ }
114
+ try :
115
+ custom_validate (instance , self .schema )
116
+ except ValidationError :
117
+ self .fail ("custom_validate raised ValidationError unexpectedly!" )
118
+
119
+ def test_reject_additional_properties (self ):
120
+ instance = {
121
+ "Sodium" : 140 ,
122
+ "Carbohydrate" : "Low" ,
123
+ "ExtraField" : "NotAllowed" # Extra field not in the schema
124
+ }
125
+ with self .assertRaises (ValidationError ):
126
+ custom_validate (instance , self .schema )
127
+
128
+ def test_allow_missing_non_required_fields (self ):
129
+ instance = {
130
+ "Sodium" : 140 # Only the required field is present
131
+ }
132
+ try :
133
+ custom_validate (instance , self .schema )
134
+ except ValidationError :
135
+ self .fail ("custom_validate raised ValidationError unexpectedly!" )
136
+
137
+ def test_allow_none_type_handling (self ):
138
+ # Test with None as the entire instance (should pass)
139
+ instance = None
140
+ try :
141
+ custom_validate (instance , self .schema )
142
+ except ValidationError :
143
+ self .fail ("custom_validate raised ValidationError unexpectedly!" )
144
+
145
+ def test_nested_object_with_additional_properties (self ):
146
+ # Nested schema with additionalProperties = False
147
+ nested_schema = {
148
+ "type" : "object" ,
149
+ "properties" : {
150
+ "Diet" : {
151
+ "type" : "object" ,
152
+ "properties" : {
153
+ "Sodium" : {"type" : "integer" },
154
+ "FluidRestriction" : {"type" : "integer" }
155
+ },
156
+ "additionalProperties" : False
157
+ }
158
+ }
159
+ }
160
+
161
+ valid_instance = {
162
+ "Diet" : {
163
+ "Sodium" : 140 ,
164
+ "FluidRestriction" : 1500
165
+ }
166
+ }
167
+
168
+ invalid_instance = {
169
+ "Diet" : {
170
+ "Sodium" : 140 ,
171
+ "ExtraField" : "NotAllowed" # Additional field in nested object
172
+ }
173
+ }
174
+
175
+ try :
176
+ custom_validate (valid_instance , nested_schema )
177
+ except ValidationError :
178
+ self .fail ("custom_validate raised ValidationError unexpectedly for valid instance!" )
179
+
180
+ with self .assertRaises (ValidationError ):
181
+ custom_validate (invalid_instance , nested_schema )
182
+
183
+ def test_nested_object_none_valid (self ):
184
+ # Test with None as a nested object
185
+ instance = {
186
+ "Sodium" : 140 ,
187
+ "Diet" : None # Should be valid since Diet is not required
188
+ }
189
+ try :
190
+ custom_validate (instance , self .schema )
191
+ except ValidationError :
192
+ self .fail ("custom_validate raised ValidationError unexpectedly!" )
193
+
194
+ def test_nested_object_missing_valid (self ):
195
+ # Test with missing nested object
196
+ instance = {
197
+ "Sodium" : 140 # Diet object is missing but should be valid
198
+ }
199
+ try :
200
+ custom_validate (instance , self .schema )
201
+ except ValidationError :
202
+ self .fail ("custom_validate raised ValidationError unexpectedly!" )
203
+
204
+ def test_exclude_any_field (self ):
205
+ # Test that any non-required field can be excluded without raising an error
206
+ instance = {
207
+ "Sodium" : 140 # Only the required field is present
208
+ }
209
+ try :
210
+ custom_validate (instance , self .schema )
211
+ except ValidationError :
212
+ self .fail ("custom_validate raised ValidationError unexpectedly!" )
213
+
214
+ def test_enum_field_nullable_and_missing (self ):
215
+ # Test that a nullable enum field can be missing or None
216
+ instance = {
217
+ "Sodium" : 140 # Carbohydrate is missing
218
+ }
219
+ try :
220
+ custom_validate (instance , self .schema )
221
+ except ValidationError :
222
+ self .fail ("custom_validate raised ValidationError unexpectedly!" )
223
+
224
+ instance_with_none = {
225
+ "Sodium" : 140 ,
226
+ "Carbohydrate" : None # Carbohydrate is explicitly set to None
227
+ }
228
+ try :
229
+ custom_validate (instance_with_none , self .schema )
230
+ except ValidationError :
231
+ self .fail ("custom_validate raised ValidationError unexpectedly!" )
232
+
233
+
234
+ if __name__ == "__main__" :
235
+ unittest .main ()
0 commit comments