Skip to content

Commit 7c9de48

Browse files
committed
Update tests
1 parent f19e681 commit 7c9de48

File tree

3 files changed

+141
-26
lines changed

3 files changed

+141
-26
lines changed

test_project/test_app/tests.py

Lines changed: 123 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,78 +8,156 @@
88
from test_app.utils import ErrorTriggers, render_response
99

1010

11-
@pytest.mark.django_db
12-
class TestErrors:
13-
def test_django_http404_ok(self, mocker):
14-
exc = Http404()
15-
response = exception_handler(exc, mocker.Mock())
16-
17-
expected_response = {"title": "Not found."}
18-
assert render_response(response.data) == expected_response
19-
20-
def test_django_permission_denied_ok(self, mocker):
21-
exc = PermissionDenied()
22-
response = exception_handler(exc, mocker.Mock())
23-
24-
expected_response = {
25-
"title": "You do not have permission to perform this action."
26-
}
27-
assert render_response(response.data) == expected_response
11+
class TestDjangoExceptions:
12+
"""Test the exception handler for various Django exceptions."""
2813

2914
@pytest.mark.parametrize(
30-
"error_message, expected_response",
15+
"error_message, code, params, expected_response",
3116
[
17+
# Raising a single error message
3218
(
3319
"Error message.",
34-
{"title": "Validation error.", "detail": ["Error message."]},
20+
None,
21+
None,
22+
{
23+
"title": "Validation error.",
24+
"detail": ["Error message."],
25+
"invalid_params": None,
26+
},
3527
),
3628
(
37-
[f"Error message {i}." for i in range(2)],
29+
"Error message: %(msg)s.",
30+
"invalid",
31+
{"msg": "ERROR"},
3832
{
3933
"title": "Validation error.",
40-
"detail": ["Error message 0.", "Error message 1."],
34+
"detail": ["Error message: ERROR."],
35+
"invalid_params": None,
36+
},
37+
),
38+
# Raising multiple error messages
39+
(
40+
[f"Error message {i+1}." for i in range(2)],
41+
None,
42+
None,
43+
{
44+
"title": "Validation error.",
45+
"detail": ["Error message 1.", "Error message 2."],
46+
"invalid_params": None,
47+
},
48+
),
49+
(
50+
[
51+
ValidationError(f"Error message {i+1}.", code=f"error {i+1}")
52+
for i in range(2)
53+
],
54+
None,
55+
None,
56+
{
57+
"title": "Validation error.",
58+
"detail": ["Error message 1.", "Error message 2."],
59+
"invalid_params": None,
4160
},
4261
),
62+
# Raising a dictionary of error messages
4363
(
4464
{"field": "Error message."},
65+
None,
66+
None,
4567
{
4668
"title": "Validation error.",
69+
"detail": None,
4770
"invalid_params": [{"name": "field", "reason": ["Error message."]}],
4871
},
4972
),
73+
(
74+
{"field": [f"Error message {i+1}." for i in range(2)]},
75+
None,
76+
None,
77+
{
78+
"title": "Validation error.",
79+
"detail": None,
80+
"invalid_params": [
81+
{
82+
"name": "field",
83+
"reason": ["Error message 1.", "Error message 2."],
84+
},
85+
],
86+
},
87+
),
5088
],
5189
)
52-
def test_django_validation_error_ok(self, error_message, expected_response, mocker):
53-
exc = ValidationError(error_message)
90+
def test_django_validation_error_ok(
91+
self, error_message, code, params, expected_response, mocker
92+
):
93+
"""
94+
Test the exception handler for ValidationError exceptions.
95+
"""
96+
exc = ValidationError(error_message, code, params)
97+
response = exception_handler(exc, mocker.Mock())
98+
99+
assert render_response(response.data) == expected_response
100+
101+
def test_django_http404_ok(self, mocker):
102+
"""Test the exception handler for Http404 exceptions."""
103+
exc = Http404()
104+
response = exception_handler(exc, mocker.Mock())
105+
106+
expected_response = {
107+
"title": "Not found.",
108+
"detail": None,
109+
"invalid_params": None,
110+
}
111+
assert render_response(response.data) == expected_response
112+
113+
def test_django_permission_denied_ok(self, mocker):
114+
"""Test the exception handler for PermissionDenied exceptions."""
115+
exc = PermissionDenied()
54116
response = exception_handler(exc, mocker.Mock())
55117

118+
expected_response = {
119+
"title": "You do not have permission to perform this action.",
120+
"detail": None,
121+
"invalid_params": None,
122+
}
56123
assert render_response(response.data) == expected_response
57124

125+
126+
class TestAPIExceptions:
127+
"""Test the exception handler for various API exceptions."""
128+
58129
@pytest.mark.parametrize(
59130
"error_message, expected_response",
60131
[
61132
(
62133
"Error message.",
63-
{"title": "A server error occurred.", "detail": ["Error message."]},
134+
{
135+
"title": "A server error occurred.",
136+
"detail": ["Error message."],
137+
"invalid_params": None,
138+
},
64139
),
65140
(
66141
[f"Error message {i}." for i in range(2)],
67142
{
68143
"title": "A server error occurred.",
69144
"detail": ["Error message 0.", "Error message 1."],
145+
"invalid_params": None,
70146
},
71147
),
72148
(
73149
{"field": "Error message."},
74150
{
75151
"title": "A server error occurred.",
152+
"detail": None,
76153
"invalid_params": [{"name": "field", "reason": ["Error message."]}],
77154
},
78155
),
79156
(
80157
{"field1": {"field2": "Error message."}},
81158
{
82159
"title": "A server error occurred.",
160+
"detail": None,
83161
"invalid_params": [
84162
{"name": "field1.field2", "reason": ["Error message."]}
85163
],
@@ -98,26 +176,33 @@ def test_drf_api_exception_ok(self, error_message, expected_response, mocker):
98176
[
99177
(
100178
"Error message.",
101-
{"title": "Validation error.", "detail": ["Error message."]},
179+
{
180+
"title": "Validation error.",
181+
"detail": ["Error message."],
182+
"invalid_params": None,
183+
},
102184
),
103185
(
104186
[f"Error message {i}." for i in range(2)],
105187
{
106188
"title": "Validation error.",
107189
"detail": ["Error message 0.", "Error message 1."],
190+
"invalid_params": None,
108191
},
109192
),
110193
(
111194
{"field": "Error message."},
112195
{
113196
"title": "Validation error.",
197+
"detail": None,
114198
"invalid_params": [{"name": "field", "reason": ["Error message."]}],
115199
},
116200
),
117201
(
118202
{"field1": {"field2": "Error message."}},
119203
{
120204
"title": "Validation error.",
205+
"detail": None,
121206
"invalid_params": [
122207
{"name": "field1.field2", "reason": ["Error message."]}
123208
],
@@ -131,6 +216,7 @@ def test_drf_api_exception_ok(self, error_message, expected_response, mocker):
131216
},
132217
{
133218
"title": "Validation error.",
219+
"detail": None,
134220
"invalid_params": [
135221
{
136222
"name": "field1.field2.field3.field4.field5",
@@ -146,6 +232,7 @@ def test_drf_api_exception_ok(self, error_message, expected_response, mocker):
146232
},
147233
{
148234
"title": "Validation error.",
235+
"detail": None,
149236
"invalid_params": [
150237
{"name": "field1.field2", "reason": ["Error message."]},
151238
{"name": "field3.field4", "reason": ["Error message."]},
@@ -159,6 +246,7 @@ def test_drf_api_exception_ok(self, error_message, expected_response, mocker):
159246
},
160247
{
161248
"title": "Validation error.",
249+
"detail": None,
162250
"invalid_params": [
163251
{"name": "field1.field2", "reason": ["Error message."]},
164252
{"name": "field3.field4.field5", "reason": ["Error message."]},
@@ -184,6 +272,7 @@ def test_field_required_error_ok(self, book_serializer, mocker):
184272

185273
expected_response = {
186274
"title": "Validation error.",
275+
"detail": None,
187276
"invalid_params": [
188277
{
189278
"name": "title",
@@ -215,6 +304,7 @@ def test_field_validation_error_ok(self, book, book_serializer, faker, mocker):
215304

216305
expected_response = {
217306
"title": "Validation error.",
307+
"detail": None,
218308
"invalid_params": [
219309
{
220310
"name": "isbn10",
@@ -239,6 +329,7 @@ def test_validation_error_ok(self, book_serializer, faker, mocker):
239329
expected_response = {
240330
"title": "Validation error.",
241331
"detail": [f"Title cannot be {ErrorTriggers.SERIALIZER_VALIDATION}"],
332+
"invalid_params": None,
242333
}
243334
assert render_response(response.data) == expected_response
244335

@@ -262,6 +353,7 @@ def test_bad_choice_error_ok(self, book_model_serializer, faker, mocker, user):
262353

263354
expected_response = {
264355
"title": "Validation error.",
356+
"detail": None,
265357
"invalid_params": [
266358
{
267359
"name": "edition",
@@ -290,6 +382,7 @@ def test_bad_one_to_one_relationship_error_ok(
290382

291383
expected_response = {
292384
"title": "Validation error.",
385+
"detail": None,
293386
"invalid_params": [
294387
{
295388
"name": "author",
@@ -319,6 +412,7 @@ def test_bad_many_to_many_relationship_error_ok(
319412

320413
expected_response = {
321414
"title": "Validation error.",
415+
"detail": None,
322416
"invalid_params": [
323417
{
324418
"name": "libraries",
@@ -345,6 +439,7 @@ def test_constraint_error_ok(self, book_model_serializer, faker, mocker, user):
345439
expected_response = {
346440
"title": "Validation error.",
347441
"detail": ["Pages cannot be more than 360."],
442+
"invalid_params": None,
348443
}
349444
assert render_response(response.data) == expected_response
350445

@@ -356,6 +451,7 @@ def test_field_required_error_ok(self, book_model_serializer, mocker):
356451

357452
expected_response = {
358453
"title": "Validation error.",
454+
"detail": None,
359455
"invalid_params": [
360456
{
361457
"name": "author",
@@ -394,6 +490,7 @@ def test_method_error_ok(self, book_model_serializer, faker, mocker, user):
394490
expected_response = {
395491
"title": "Validation error.",
396492
"detail": [ErrorTriggers.SERIALIZER_METHOD.value],
493+
"invalid_params": None,
397494
}
398495
assert render_response(response.data) == expected_response
399496

@@ -413,6 +510,7 @@ def test_validation_error_ok(self, book_model_serializer, faker, mocker, user):
413510
expected_response = {
414511
"title": "Validation error.",
415512
"detail": [f"Title cannot be {ErrorTriggers.SERIALIZER_VALIDATION}"],
513+
"invalid_params": None,
416514
}
417515
assert render_response(response.data) == expected_response
418516

test_project/test_app/utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
@unique
77
class ErrorTriggers(Enum):
8+
"""Enum for error triggers used in tests and examples."""
9+
810
MODEL_CONSTRAINT = 361
911
MODEL_VALIDATION = "Model validation error!"
1012
SERIALIZER_METHOD = "Serializer method error!"
@@ -15,5 +17,17 @@ def __str__(self) -> str:
1517

1618

1719
def render_response(data: dict) -> dict:
20+
"""
21+
Renders the response data from a `dict` to a DRF Response object.
22+
23+
Args:
24+
data (dict): The data to be rendered.
25+
26+
Returns:
27+
response.data (dict): The rendered response data from the DRF `Response` object.
28+
"""
29+
# This is needed in tests to ensure that
30+
# the response data is in the same format as the DRF Response given by
31+
# the exception handler.
1832
response = Response(data=data)
1933
return response.data

tox.ini

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
[tox]
2-
envlist = py38, py39, py310
2+
envlist = py38, py39, py310, py311, py312, py313
33
isolated_build = true
44

55
[gh-actions]
66
python =
77
3.8: py38
88
3.9: py39
99
3.10: py310
10+
3.11: py311
11+
3.12: py312
12+
3.13: py313
1013

1114
[testenv]
1215
allowlist_externals =

0 commit comments

Comments
 (0)