11import unittest
22from unittest .mock import Mock , patch
33
4+ from flask import Flask , g , jsonify
5+
46from flask_inputfilter import InputFilter
57from flask_inputfilter .Condition import BaseCondition
68from flask_inputfilter .Exception import ValidationError
@@ -23,6 +25,50 @@ def setUp(self) -> None:
2325
2426 self .inputFilter = InputFilter ()
2527
28+ @patch ("flask_inputfilter.InputFilter.validateData" )
29+ def test_validate_decorator (self , mock_validate_data ) -> None :
30+ mock_validate_data .return_value = {"username" : "test_user" , "age" : 25 }
31+
32+ app = Flask (__name__ )
33+
34+ class MyInputFilter (InputFilter ):
35+ def __init__ (self ):
36+ super ().__init__ ()
37+ self .add (
38+ name = "username" ,
39+ required = True ,
40+ filters = [],
41+ validators = [],
42+ )
43+ self .add (
44+ name = "age" ,
45+ required = False ,
46+ default = 18 ,
47+ filters = [],
48+ validators = [],
49+ )
50+
51+ @app .route ("/test" , methods = ["GET" , "POST" ])
52+ @MyInputFilter .validate ()
53+ def test_route ():
54+ validated_data = g .validated_data
55+ return jsonify (validated_data )
56+
57+ with app .test_client () as client :
58+ response = client .get (
59+ "/test" , query_string = {"username" : "test_user" }
60+ )
61+ self .assertEqual (response .status_code , 200 )
62+ self .assertEqual (
63+ response .json , {"username" : "test_user" , "age" : 25 }
64+ )
65+
66+ response = client .post ("/test" , json = {"username" : "test_user" })
67+ self .assertEqual (response .status_code , 200 )
68+ self .assertEqual (
69+ response .json , {"username" : "test_user" , "age" : 25 }
70+ )
71+
2672 def test_optional (self ) -> None :
2773 """
2874 Test that optional field validation works.
@@ -60,24 +106,47 @@ def test_fallback(self) -> None:
60106 self .inputFilter .add ("available" , required = True , fallback = True )
61107 self .inputFilter .add (
62108 "color" ,
109+ required = True ,
63110 fallback = "red" ,
64111 validators = [InArrayValidator (["red" , "green" , "blue" ])],
65112 )
66113
67- # Fallback case triggert
68114 validated_data = self .inputFilter .validateData ({"color" : "yellow" })
69115
70116 self .assertEqual (validated_data ["available" ], True )
71117 self .assertEqual (validated_data ["color" ], "red" )
72118
73- # Override fallback case
74119 validated_data = self .inputFilter .validateData (
75120 {"available" : False , "color" : "green" }
76121 )
77122
78123 self .assertEqual (validated_data ["available" ], False )
79124 self .assertEqual (validated_data ["color" ], "green" )
80125
126+ def test_fallback_with_default (self ) -> None :
127+ """
128+ Test that fallback field works.
129+ """
130+
131+ self .inputFilter .add (
132+ "available" , required = True , default = True , fallback = False
133+ )
134+ self .inputFilter .add (
135+ "color" ,
136+ default = "red" ,
137+ fallback = "blue" ,
138+ validators = [InArrayValidator (["red" , "green" , "blue" ])],
139+ )
140+
141+ validated_data = self .inputFilter .validateData ({})
142+
143+ self .assertEqual (validated_data ["available" ], True )
144+ self .assertEqual (validated_data ["color" ], "red" )
145+
146+ validated_data = self .inputFilter .validateData ({"available" : False })
147+
148+ self .assertEqual (validated_data ["available" ], False )
149+
81150 @patch ("requests.request" )
82151 def test_external_api (self , mock_request : Mock ) -> None :
83152 """
@@ -127,26 +196,23 @@ def test_external_api_params(self, mock_request: Mock) -> None:
127196 mock_response .json .return_value = {"is_valid" : True }
128197 mock_request .return_value = mock_response
129198
130- # Add fields where the external API receives its values
131199 self .inputFilter .add ("name" )
132200
133201 self .inputFilter .add ("hash" )
134202
135- # Add a field with external API configuration
136203 self .inputFilter .add (
137204 "is_valid" ,
138205 required = True ,
139206 external_api = ExternalApiConfig (
140207 url = "https://api.example.com/validate_user/{{name}}" ,
141208 method = "GET" ,
142- params = {"hash" : "{{hash}}" },
209+ params = {"hash" : "{{hash}}" , "id" : 123 },
143210 data_key = "is_valid" ,
144211 headers = {"custom_header" : "value" },
145212 api_key = "1234" ,
146213 ),
147214 )
148215
149- # API returns valid result
150216 validated_data = self .inputFilter .validateData (
151217 {"name" : "test_user" , "hash" : "1234" }
152218 )
@@ -157,18 +223,16 @@ def test_external_api_params(self, mock_request: Mock) -> None:
157223 headers = {"Authorization" : "Bearer 1234" , "custom_header" : "value" },
158224 method = "GET" ,
159225 url = expected_url ,
160- params = {"hash" : "1234" },
226+ params = {"hash" : "1234" , "id" : 123 },
161227 )
162228
163- # API returns invalid status code
164229 mock_response .status_code = 500
165230 mock_response .json .return_value = {"is_valid" : False }
166231 with self .assertRaises (ValidationError ):
167232 self .inputFilter .validateData (
168233 {"name" : "invalid_user" , "hash" : "1234" }
169234 )
170235
171- # API returns invalid result
172236 mock_response .json .return_value = {}
173237 with self .assertRaises (ValidationError ):
174238 self .inputFilter .validateData (
@@ -182,10 +246,62 @@ def test_external_api_fallback(self, mock_request: Mock) -> None:
182246 mock_response .json .return_value = {"name" : True }
183247 mock_request .return_value = mock_response
184248
249+ self .inputFilter .add (
250+ "username_with_fallback" ,
251+ required = True ,
252+ fallback = "fallback_user" ,
253+ external_api = ExternalApiConfig (
254+ url = "https://api.example.com/validate_user" ,
255+ method = "GET" ,
256+ params = {"user" : "{{value}}" },
257+ data_key = "name" ,
258+ ),
259+ )
260+
261+ validated_data = self .inputFilter .validateData (
262+ {"username_with_fallback" : None }
263+ )
264+ self .assertEqual (
265+ validated_data ["username_with_fallback" ], "fallback_user"
266+ )
267+
268+ @patch ("requests.request" )
269+ def test_external_api_default (self , mock_request : Mock ) -> None :
270+ mock_response = Mock ()
271+ mock_response .status_code = 200
272+ mock_response .json .return_value = {}
273+ mock_request .return_value = mock_response
274+
185275 # API call with fallback
276+ self .inputFilter .add (
277+ "username_with_default" ,
278+ default = "default_user" ,
279+ external_api = ExternalApiConfig (
280+ url = "https://api.example.com/validate_user" ,
281+ method = "GET" ,
282+ params = {"user" : "{{value}}" },
283+ data_key = "name" ,
284+ ),
285+ )
286+
287+ validated_data = self .inputFilter .validateData ({})
288+ self .assertEqual (
289+ validated_data ["username_with_default" ], "default_user"
290+ )
291+
292+ @patch ("requests.request" )
293+ def test_external_api_fallback_with_default (
294+ self , mock_request : Mock
295+ ) -> None :
296+ mock_response = Mock ()
297+ mock_response .status_code = 400
298+ mock_response .json .return_value = {"name" : True }
299+ mock_request .return_value = mock_response
300+
186301 self .inputFilter .add (
187302 "username_with_fallback" ,
188303 required = True ,
304+ default = "default_user" ,
189305 fallback = "fallback_user" ,
190306 external_api = ExternalApiConfig (
191307 url = "https://api.example.com/validate_user" ,
@@ -202,6 +318,50 @@ def test_external_api_fallback(self, mock_request: Mock) -> None:
202318 validated_data ["username_with_fallback" ], "fallback_user"
203319 )
204320
321+ @patch ("requests.request" )
322+ def test_external_invalid_api_response (self , mock_request : Mock ) -> None :
323+ """
324+ Test that a non-JSON API response raises a ValidationError.
325+ """
326+ mock_response = Mock ()
327+ mock_response .status_code = 200
328+ mock_response .json .side_effect = ValueError ("Invalid JSON" )
329+ mock_request .return_value = mock_response
330+
331+ self .inputFilter .add (
332+ "is_valid" ,
333+ external_api = ExternalApiConfig (
334+ url = "https://api.example.com/validate" ,
335+ method = "GET" ,
336+ ),
337+ )
338+
339+ with self .assertRaises (ValidationError ):
340+ self .inputFilter .validateData ({})
341+
342+ @patch ("requests.request" )
343+ def test_external_api_response_with_no_data_key (
344+ self , mock_request : Mock
345+ ) -> None :
346+ """
347+ Test that an API response with no data key raises a ValidationError.
348+ """
349+ mock_response = Mock ()
350+ mock_response .status_code = 400
351+ mock_response .json .return_value = {}
352+ mock_request .return_value = mock_response
353+
354+ self .inputFilter .add (
355+ "is_valid" ,
356+ external_api = ExternalApiConfig (
357+ url = "https://api.example.com/validate" ,
358+ method = "GET" ,
359+ ),
360+ )
361+
362+ with self .assertRaises (ValidationError ):
363+ self .inputFilter .validateData ({})
364+
205365 def test_multiple_validators (self ) -> None :
206366 """
207367 Test that multiple validators are applied correctly.
@@ -241,27 +401,6 @@ def check(self, data: dict) -> bool:
241401 with self .assertRaises (ValidationError ):
242402 self .inputFilter .validateData ({"age" : 17 })
243403
244- @patch ("requests.request" )
245- def test_invalid_api_response (self , mock_request : Mock ) -> None :
246- """
247- Test that a non-JSON API response raises a ValidationError.
248- """
249- mock_response = Mock ()
250- mock_response .status_code = 200
251- mock_response .json .side_effect = ValueError ("Invalid JSON" )
252- mock_request .return_value = mock_response
253-
254- self .inputFilter .add (
255- "is_valid" ,
256- external_api = ExternalApiConfig (
257- url = "https://api.example.com/validate" ,
258- method = "GET" ,
259- ),
260- )
261-
262- with self .assertRaises (ValidationError ):
263- self .inputFilter .validateData ({})
264-
265404 def test_global_filter_applied_to_all_fields (self ) -> None :
266405 self .inputFilter .add ("field1" )
267406 self .inputFilter .add ("field2" )
0 commit comments