Skip to content

Commit 35defe2

Browse files
Amin Farjadiamin-farjadi
authored andcommitted
feat(unit-test): Add tests for custom response validation error.
1 parent b0605fa commit 35defe2

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
from __future__ import annotations
2+
3+
import json
4+
from http import HTTPStatus
5+
6+
import pytest
7+
from pydantic import BaseModel, Field
8+
9+
from aws_lambda_powertools.event_handler.api_gateway import APIGatewayRestResolver, Response
10+
from aws_lambda_powertools.event_handler.openapi.exceptions import ResponseValidationError
11+
12+
app = APIGatewayRestResolver(enable_validation=True)
13+
app_with_custom_response_validation_error = APIGatewayRestResolver(
14+
enable_validation=True,
15+
response_validation_error_http_status=HTTPStatus.INTERNAL_SERVER_ERROR,
16+
)
17+
18+
19+
class Todo(BaseModel):
20+
userId: int
21+
id_: int | None = Field(alias="id", default=None)
22+
title: str
23+
completed: bool
24+
25+
26+
TODO_OBJECT = Todo(userId="1234", id="1", title="Write tests.", completed=True)
27+
28+
29+
@app_with_custom_response_validation_error.get("/string_not_todo")
30+
@app.get("/string_not_todo")
31+
def return_string_not_todo() -> Todo:
32+
return "hello"
33+
34+
35+
@app_with_custom_response_validation_error.get("/incomplete_todo")
36+
@app.get("/incomplete_todo")
37+
def return_incomplete_todo() -> Todo:
38+
return {"title": "fix_response_validation"}
39+
40+
41+
@app_with_custom_response_validation_error.get("/todo")
42+
@app.get("/todo")
43+
def return_todo() -> Todo:
44+
return TODO_OBJECT
45+
46+
47+
# --- Tests below ---
48+
49+
50+
@pytest.fixture()
51+
def event_factory():
52+
def _factory(path: str):
53+
return {
54+
"httpMethod": "GET",
55+
"path": path,
56+
}
57+
58+
yield _factory
59+
60+
61+
@pytest.fixture()
62+
def response_validation_error_factory():
63+
def _factory(loc: list[str], type_: str):
64+
if not loc:
65+
return [{"loc": ["response"], "type": type_}]
66+
return [{"loc": ["response", location], "type": type_} for location in loc]
67+
68+
yield _factory
69+
70+
71+
class TestDefaultResponseValidation:
72+
73+
def test_valid_response(self, event_factory):
74+
event = event_factory("/todo")
75+
76+
response = app.resolve(event, None)
77+
body = json.loads(response["body"])
78+
79+
assert response["statusCode"] == HTTPStatus.OK
80+
assert body == TODO_OBJECT.model_dump(by_alias=True)
81+
82+
@pytest.mark.parametrize(
83+
(
84+
"path",
85+
"error_location",
86+
"error_type",
87+
),
88+
[
89+
("/string_not_todo", [], "model_attributes_type"),
90+
("/incomplete_todo", ["userId", "completed"], "missing"),
91+
],
92+
ids=["string_not_todo", "incomplete_todo"],
93+
)
94+
def test_default_serialization_failure(
95+
self,
96+
path,
97+
error_location,
98+
error_type,
99+
event_factory,
100+
response_validation_error_factory,
101+
):
102+
"""Tests to demonstrate cases when response serialization fails, as expected."""
103+
event = event_factory(path)
104+
error_detail = response_validation_error_factory(error_location, error_type)
105+
106+
response = app.resolve(event, None)
107+
body = json.loads(response["body"])
108+
109+
assert response["statusCode"] == HTTPStatus.UNPROCESSABLE_ENTITY
110+
assert body == {"statusCode": 422, "detail": error_detail}
111+
112+
113+
class TestCustomResponseValidation:
114+
115+
def test_valid_response(self, event_factory):
116+
117+
event = event_factory("/todo")
118+
119+
response = app_with_custom_response_validation_error.resolve(event, None)
120+
body = json.loads(response["body"])
121+
122+
assert response["statusCode"] == HTTPStatus.OK
123+
assert body == TODO_OBJECT.model_dump(by_alias=True)
124+
125+
@pytest.mark.parametrize(
126+
(
127+
"path",
128+
"error_location",
129+
"error_type",
130+
),
131+
[
132+
("/string_not_todo", [], "model_attributes_type"),
133+
("/incomplete_todo", ["userId", "completed"], "missing"),
134+
],
135+
ids=["string_not_todo", "incomplete_todo"],
136+
)
137+
def test_custom_serialization_failure(
138+
self,
139+
path,
140+
error_location,
141+
error_type,
142+
event_factory,
143+
response_validation_error_factory,
144+
):
145+
"""Tests to demonstrate cases when response serialization fails, as expected."""
146+
147+
event = event_factory(path)
148+
error_detail = response_validation_error_factory(error_location, error_type)
149+
150+
response = app_with_custom_response_validation_error.resolve(event, None)
151+
body = json.loads(response["body"])
152+
153+
assert response["statusCode"] == HTTPStatus.INTERNAL_SERVER_ERROR
154+
assert body == {"statusCode": 500, "detail": error_detail}
155+
156+
@pytest.mark.parametrize(
157+
"path",
158+
[
159+
("/string_not_todo"),
160+
("/incomplete_todo"),
161+
],
162+
ids=["string_not_todo", "incomplete_todo"],
163+
)
164+
def test_sanitized_error_response(
165+
self,
166+
path,
167+
event_factory,
168+
):
169+
event = event_factory(path)
170+
171+
@app_with_custom_response_validation_error.exception_handler(ResponseValidationError)
172+
def handle_response_validation_error(ex: ResponseValidationError):
173+
return Response(
174+
status_code=500,
175+
content_type="application/json",
176+
body="Unexpected response.",
177+
)
178+
179+
response = app_with_custom_response_validation_error.resolve(event, None)
180+
181+
assert response["statusCode"] == HTTPStatus.INTERNAL_SERVER_ERROR
182+
assert response["body"] == "Unexpected response."

0 commit comments

Comments
 (0)