Skip to content

Commit 69c23f3

Browse files
committed
12 | Increase test coverage for InputFilter
1 parent 92f42bd commit 69c23f3

File tree

3 files changed

+171
-37
lines changed

3 files changed

+171
-37
lines changed

CHAGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ All notable changes to this project will be documented in this file.
99

1010
- Workflow to run tests on all supported python versions. [Check it out](.github/workflows/test_env.yaml)
1111
- Added more test coverage for validators and filters.
12-
- Added tracking of coverage in tests.
12+
- Added tracking of coverage in tests. [Check it out](https://coveralls.io/github/LeanderCS/flask-inputfilter)
13+
- New functionality for global filters and validators in InputFilters.
1314

1415
### Validator
1516

flask_inputfilter/InputFilter.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,6 @@ def _applyFilters(self, field_name: str, value: Any) -> Any:
8282

8383
field = self.fields.get(field_name)
8484

85-
if not field:
86-
return value
87-
8885
for filter_ in field["filters"]:
8986
value = filter_.apply(value)
9087

@@ -100,9 +97,6 @@ def _validateField(self, field_name: str, value: Any) -> None:
10097

10198
field = self.fields.get(field_name)
10299

103-
if not field:
104-
return
105-
106100
for validator in field["validators"]:
107101
validator.validate(value)
108102

test/test_input_filter.py

Lines changed: 169 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import unittest
22
from unittest.mock import Mock, patch
33

4+
from flask import Flask, g, jsonify
5+
46
from flask_inputfilter import InputFilter
57
from flask_inputfilter.Condition import BaseCondition
68
from 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

Comments
 (0)