Skip to content

Commit b818ae7

Browse files
chore(ci): add the Event Handler feature to nox tests (#4581)
1 parent 6d31ed4 commit b818ae7

31 files changed

+182
-82
lines changed

noxfile.py

+18
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,15 @@ def test_with_only_required_packages(session: nox.Session):
5454
# Metrics - Base provider
5555
# Middleware factory without tracer
5656
# Typing
57+
# Event Handler without OpenAPI
5758
build_and_run_test(
5859
session,
5960
folders=[
6061
f"{PREFIX_TESTS_FUNCTIONAL}/logger/required_dependencies/",
6162
f"{PREFIX_TESTS_FUNCTIONAL}/metrics/required_dependencies/",
6263
f"{PREFIX_TESTS_FUNCTIONAL}/middleware_factory/required_dependencies/",
6364
f"{PREFIX_TESTS_FUNCTIONAL}/typing/required_dependencies/",
65+
f"{PREFIX_TESTS_FUNCTIONAL}/event_handler/required_dependencies/",
6466
],
6567
)
6668

@@ -134,3 +136,19 @@ def test_with_aws_encryption_sdk_as_required_package(session: nox.Session):
134136
],
135137
extras="datamasking",
136138
)
139+
140+
141+
@nox.session()
142+
@nox.parametrize("pydantic", ["1.10", "2.0"])
143+
def test_with_pydantic_required_package(session: nox.Session, pydantic: str):
144+
"""Tests that only depends for required libraries"""
145+
# Event Handler OpenAPI
146+
147+
session.install(f"pydantic>={pydantic}")
148+
149+
build_and_run_test(
150+
session,
151+
folders=[
152+
f"{PREFIX_TESTS_FUNCTIONAL}/event_handler/_pydantic/",
153+
],
154+
)

tests/__init__.py

Whitespace-only changes.

tests/functional/event_handler/_pydantic/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from pydantic import BaseModel
2+
3+
from aws_lambda_powertools.event_handler import content_types
4+
from aws_lambda_powertools.event_handler.api_gateway import (
5+
ApiGatewayResolver,
6+
Response,
7+
)
8+
from aws_lambda_powertools.event_handler.openapi.exceptions import RequestValidationError
9+
from tests.functional.utils import load_event
10+
11+
LOAD_GW_EVENT = load_event("apiGatewayProxyEvent.json")
12+
13+
14+
def test_exception_handler_with_data_validation():
15+
# GIVEN a resolver with an exception handler defined for RequestValidationError
16+
app = ApiGatewayResolver(enable_validation=True)
17+
18+
@app.exception_handler(RequestValidationError)
19+
def handle_validation_error(ex: RequestValidationError):
20+
return Response(
21+
status_code=422,
22+
content_type=content_types.TEXT_PLAIN,
23+
body=f"Invalid data. Number of errors: {len(ex.errors())}",
24+
)
25+
26+
@app.get("/my/path")
27+
def get_lambda(param: int): ...
28+
29+
# WHEN calling the event handler
30+
# AND a RequestValidationError is raised
31+
result = app(LOAD_GW_EVENT, {})
32+
33+
# THEN call the exception_handler
34+
assert result["statusCode"] == 422
35+
assert result["multiValueHeaders"]["Content-Type"] == [content_types.TEXT_PLAIN]
36+
assert result["body"] == "Invalid data. Number of errors: 1"
37+
38+
39+
def test_exception_handler_with_data_validation_pydantic_response():
40+
# GIVEN a resolver with an exception handler defined for RequestValidationError
41+
app = ApiGatewayResolver(enable_validation=True)
42+
43+
class Err(BaseModel):
44+
msg: str
45+
46+
@app.exception_handler(RequestValidationError)
47+
def handle_validation_error(ex: RequestValidationError):
48+
return Response(
49+
status_code=422,
50+
content_type=content_types.APPLICATION_JSON,
51+
body=Err(msg=f"Invalid data. Number of errors: {len(ex.errors())}"),
52+
)
53+
54+
@app.get("/my/path")
55+
def get_lambda(param: int): ...
56+
57+
# WHEN calling the event handler
58+
# AND a RequestValidationError is raised
59+
result = app(LOAD_GW_EVENT, {})
60+
61+
# THEN exception handler's pydantic response should be serialized correctly
62+
assert result["statusCode"] == 422
63+
assert result["body"] == '{"msg":"Invalid data. Number of errors: 1"}'
64+
65+
66+
def test_data_validation_error():
67+
# GIVEN a resolver without an exception handler
68+
app = ApiGatewayResolver(enable_validation=True)
69+
70+
@app.get("/my/path")
71+
def get_lambda(param: int): ...
72+
73+
# WHEN calling the event handler
74+
# AND a RequestValidationError is raised
75+
result = app(LOAD_GW_EVENT, {})
76+
77+
# THEN call the exception_handler
78+
assert result["statusCode"] == 422
79+
assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON]
80+
assert "missing" in result["body"]

tests/functional/event_handler/required_dependencies/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import json
2+
3+
import pytest
4+
5+
from tests.functional.utils import load_event
6+
7+
8+
@pytest.fixture
9+
def json_dump():
10+
# our serializers reduce length to save on costs; fixture to replicate separators
11+
return lambda obj: json.dumps(obj, separators=(",", ":"))
12+
13+
14+
@pytest.fixture
15+
def validation_schema():
16+
return {
17+
"$schema": "https://json-schema.org/draft-07/schema",
18+
"$id": "https://example.com/example.json",
19+
"type": "object",
20+
"title": "Sample schema",
21+
"description": "The root schema comprises the entire JSON document.",
22+
"examples": [{"message": "hello world", "username": "lessa"}],
23+
"required": ["message", "username"],
24+
"properties": {
25+
"message": {
26+
"$id": "#/properties/message",
27+
"type": "string",
28+
"title": "The message",
29+
"examples": ["hello world"],
30+
},
31+
"username": {
32+
"$id": "#/properties/username",
33+
"type": "string",
34+
"title": "The username",
35+
"examples": ["lessa"],
36+
},
37+
},
38+
}
39+
40+
41+
@pytest.fixture
42+
def raw_event():
43+
return {"message": "hello hello", "username": "blah blah"}
44+
45+
46+
@pytest.fixture
47+
def gw_event():
48+
return load_event("apiGatewayProxyEvent.json")
49+
50+
51+
@pytest.fixture
52+
def gw_event_http():
53+
return load_event("apiGatewayProxyV2Event.json")
54+
55+
56+
@pytest.fixture
57+
def gw_event_alb():
58+
return load_event("albMultiValueQueryStringEvent.json")
59+
60+
61+
@pytest.fixture
62+
def gw_event_lambda_url():
63+
return load_event("lambdaFunctionUrlEventWithHeaders.json")
64+
65+
66+
@pytest.fixture
67+
def gw_event_vpc_lattice():
68+
return load_event("vpcLatticeV2EventWithHeaders.json")
69+
70+
71+
@pytest.fixture
72+
def gw_event_vpc_lattice_v1():
73+
return load_event("vpcLatticeEvent.json")

tests/functional/event_handler/test_api_gateway.py renamed to tests/functional/event_handler/required_dependencies/test_api_gateway.py

+1-72
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from typing import Dict
1111

1212
import pytest
13-
from pydantic import BaseModel
1413

1514
from aws_lambda_powertools.event_handler import content_types
1615
from aws_lambda_powertools.event_handler.api_gateway import (
@@ -31,7 +30,6 @@
3130
ServiceError,
3231
UnauthorizedError,
3332
)
34-
from aws_lambda_powertools.event_handler.openapi.exceptions import RequestValidationError
3533
from aws_lambda_powertools.shared import constants
3634
from aws_lambda_powertools.shared.cookies import Cookie
3735
from aws_lambda_powertools.shared.json_encoder import Encoder
@@ -45,7 +43,7 @@
4543

4644

4745
def read_media(file_name: str) -> bytes:
48-
path = Path(str(Path(__file__).parent.parent.parent.parent) + "/docs/media/" + file_name)
46+
path = Path(str(Path(__file__).parent.parent.parent.parent) + "/../docs/media/" + file_name)
4947
return path.read_bytes()
5048

5149

@@ -1458,58 +1456,6 @@ def get_lambda() -> Response:
14581456
assert result["body"] == "Foo!"
14591457

14601458

1461-
def test_exception_handler_with_data_validation():
1462-
# GIVEN a resolver with an exception handler defined for RequestValidationError
1463-
app = ApiGatewayResolver(enable_validation=True)
1464-
1465-
@app.exception_handler(RequestValidationError)
1466-
def handle_validation_error(ex: RequestValidationError):
1467-
return Response(
1468-
status_code=422,
1469-
content_type=content_types.TEXT_PLAIN,
1470-
body=f"Invalid data. Number of errors: {len(ex.errors())}",
1471-
)
1472-
1473-
@app.get("/my/path")
1474-
def get_lambda(param: int): ...
1475-
1476-
# WHEN calling the event handler
1477-
# AND a RequestValidationError is raised
1478-
result = app(LOAD_GW_EVENT, {})
1479-
1480-
# THEN call the exception_handler
1481-
assert result["statusCode"] == 422
1482-
assert result["multiValueHeaders"]["Content-Type"] == [content_types.TEXT_PLAIN]
1483-
assert result["body"] == "Invalid data. Number of errors: 1"
1484-
1485-
1486-
def test_exception_handler_with_data_validation_pydantic_response():
1487-
# GIVEN a resolver with an exception handler defined for RequestValidationError
1488-
app = ApiGatewayResolver(enable_validation=True)
1489-
1490-
class Err(BaseModel):
1491-
msg: str
1492-
1493-
@app.exception_handler(RequestValidationError)
1494-
def handle_validation_error(ex: RequestValidationError):
1495-
return Response(
1496-
status_code=422,
1497-
content_type=content_types.APPLICATION_JSON,
1498-
body=Err(msg=f"Invalid data. Number of errors: {len(ex.errors())}"),
1499-
)
1500-
1501-
@app.get("/my/path")
1502-
def get_lambda(param: int): ...
1503-
1504-
# WHEN calling the event handler
1505-
# AND a RequestValidationError is raised
1506-
result = app(LOAD_GW_EVENT, {})
1507-
1508-
# THEN exception handler's pydantic response should be serialized correctly
1509-
assert result["statusCode"] == 422
1510-
assert result["body"] == '{"msg":"Invalid data. Number of errors: 1"}'
1511-
1512-
15131459
def test_exception_handler_with_route():
15141460
app = ApiGatewayResolver()
15151461
# GIVEN a Router object with an exception handler defined for ValueError
@@ -1540,23 +1486,6 @@ def get_lambda() -> Response:
15401486
assert result["body"] == "Foo!"
15411487

15421488

1543-
def test_data_validation_error():
1544-
# GIVEN a resolver without an exception handler
1545-
app = ApiGatewayResolver(enable_validation=True)
1546-
1547-
@app.get("/my/path")
1548-
def get_lambda(param: int): ...
1549-
1550-
# WHEN calling the event handler
1551-
# AND a RequestValidationError is raised
1552-
result = app(LOAD_GW_EVENT, {})
1553-
1554-
# THEN call the exception_handler
1555-
assert result["statusCode"] == 422
1556-
assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON]
1557-
assert "missing" in result["body"]
1558-
1559-
15601489
def test_exception_handler_service_error():
15611490
# GIVEN
15621491
app = ApiGatewayResolver()

tests/functional/event_handler/test_base_path.py renamed to tests/functional/event_handler/required_dependencies/test_base_path.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111

1212
def test_base_path_api_gateway_rest():
13-
app = APIGatewayRestResolver(enable_validation=True)
13+
app = APIGatewayRestResolver()
1414

1515
@app.get("/")
1616
def handle():
@@ -25,7 +25,7 @@ def handle():
2525

2626

2727
def test_base_path_api_gateway_http():
28-
app = APIGatewayHttpResolver(enable_validation=True)
28+
app = APIGatewayHttpResolver()
2929

3030
@app.get("/")
3131
def handle():
@@ -42,7 +42,7 @@ def handle():
4242

4343

4444
def test_base_path_alb():
45-
app = ALBResolver(enable_validation=True)
45+
app = ALBResolver()
4646

4747
@app.get("/")
4848
def handle():
@@ -57,7 +57,7 @@ def handle():
5757

5858

5959
def test_base_path_lambda_function_url():
60-
app = LambdaFunctionUrlResolver(enable_validation=True)
60+
app = LambdaFunctionUrlResolver()
6161

6262
@app.get("/")
6363
def handle():
@@ -74,7 +74,7 @@ def handle():
7474

7575

7676
def test_vpc_lattice():
77-
app = VPCLatticeResolver(enable_validation=True)
77+
app = VPCLatticeResolver()
7878

7979
@app.get("/")
8080
def handle():
@@ -89,7 +89,7 @@ def handle():
8989

9090

9191
def test_vpc_latticev2():
92-
app = VPCLatticeV2Resolver(enable_validation=True)
92+
app = VPCLatticeV2Resolver()
9393

9494
@app.get("/")
9595
def handle():

tests/functional/idempotency/test_idempotency.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
from tests.functional.utils import json_serialize, load_event
6262

6363
TABLE_NAME = "TEST_TABLE"
64-
TESTS_MODULE_PREFIX = "test-func.functional.idempotency.test_idempotency"
64+
TESTS_MODULE_PREFIX = "test-func.tests.functional.idempotency.test_idempotency"
6565

6666

6767
def get_dataclasses_lib():

tests/functional/idempotency/utils.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def build_idempotency_put_item_stub(
1919
data: Dict,
2020
function_name: str = "test-func",
2121
function_qualified_name: str = "test_idempotent_lambda_first_execution_event_mutation.<locals>",
22-
module_name: str = "functional.idempotency.test_idempotency",
22+
module_name: str = "tests.functional.idempotency.test_idempotency",
2323
handler_name: str = "lambda_handler",
2424
) -> Dict:
2525
idempotency_key_hash = (
@@ -57,7 +57,7 @@ def build_idempotency_update_item_stub(
5757
handler_response: Dict,
5858
function_name: str = "test-func",
5959
function_qualified_name: str = "test_idempotent_lambda_first_execution_event_mutation.<locals>",
60-
module_name: str = "functional.idempotency.test_idempotency",
60+
module_name: str = "tests.functional.idempotency.test_idempotency",
6161
handler_name: str = "lambda_handler",
6262
) -> Dict:
6363
idempotency_key_hash = (

tests/unit/test_tracing.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
# Maintenance: This should move to Functional tests and use Fake over mocks.
1111

12-
MODULE_PREFIX = "unit.test_tracing"
12+
MODULE_PREFIX = "tests.unit.test_tracing"
1313

1414

1515
@pytest.fixture

0 commit comments

Comments
 (0)