Skip to content

Commit 72f2c4f

Browse files
committed
tests(event_handler): add first e2e test
1 parent 90228c0 commit 72f2c4f

File tree

8 files changed

+117
-2
lines changed

8 files changed

+117
-2
lines changed

tests/e2e/event_handler/__init__.py

Whitespace-only changes.

tests/e2e/event_handler/conftest.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import pytest
2+
3+
from tests.e2e.event_handler.infrastructure import EventHandlerStack
4+
from tests.e2e.utils.infrastructure import deploy_once
5+
6+
7+
@pytest.fixture(autouse=True, scope="module")
8+
def infrastructure(request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory, worker_id: str):
9+
"""Setup and teardown logic for E2E test infrastructure
10+
11+
Parameters
12+
----------
13+
request : pytest.FixtureRequest
14+
pytest request fixture to introspect absolute path to test being executed
15+
tmp_path_factory : pytest.TempPathFactory
16+
pytest temporary path factory to discover shared tmp when multiple CPU processes are spun up
17+
worker_id : str
18+
pytest-xdist worker identification to detect whether parallelization is enabled
19+
20+
Yields
21+
------
22+
Dict[str, str]
23+
CloudFormation Outputs from deployed infrastructure
24+
"""
25+
yield from deploy_once(
26+
stack=EventHandlerStack, request=request, tmp_path_factory=tmp_path_factory, worker_id=worker_id
27+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from aws_lambda_powertools.event_handler import ALBResolver, Response, content_types
2+
3+
app = ALBResolver()
4+
5+
6+
@app.route("/todos", method=["GET"])
7+
def hello():
8+
return Response(
9+
status_code=200,
10+
content_type=content_types.TEXT_PLAIN,
11+
body="Hello world",
12+
cookies=["CookieMonster; Secure", "MonsterCookie"],
13+
headers={"Foo": ["bar", "zbr"]},
14+
)
15+
16+
17+
def lambda_handler(event, context):
18+
return app.resolve(event, context)
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from pathlib import Path
2+
3+
from aws_cdk import CfnOutput
4+
from aws_cdk import aws_ec2 as ec2
5+
from aws_cdk import aws_elasticloadbalancingv2 as elbv2
6+
from aws_cdk import aws_elasticloadbalancingv2_targets as targets
7+
8+
from tests.e2e.utils.infrastructure import BaseInfrastructureV2
9+
10+
11+
class EventHandlerStack(BaseInfrastructureV2):
12+
def __init__(self, handlers_dir: Path, feature_name: str = "event-handlers") -> None:
13+
super().__init__(feature_name, handlers_dir)
14+
15+
def create_resources(self):
16+
functions = self.create_lambda_functions()
17+
api_gateway_http_handler_function = functions["AlbHandler"]
18+
19+
vpc = ec2.Vpc(self.stack, "EventHandlerVPC", max_azs=2)
20+
21+
alb = elbv2.ApplicationLoadBalancer(self.stack, "ALB", vpc=vpc, internet_facing=True)
22+
listener = alb.add_listener("HTTPListener", port=80)
23+
listener.add_targets("HTTPListenerTarget", targets=[targets.LambdaTarget(api_gateway_http_handler_function)])
24+
25+
CfnOutput(self.stack, "ALBDnsName", value=alb.load_balancer_dns_name)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import pytest
2+
from requests import Request
3+
4+
from tests.e2e.utils import data_fetcher
5+
6+
7+
@pytest.fixture
8+
def alb_endpoint(infrastructure: dict) -> str:
9+
dns_name = infrastructure.get("ALBDnsName")
10+
return f"http://{dns_name}"
11+
12+
13+
def test_alb_headers_serializer(alb_endpoint):
14+
# GIVEN
15+
url = f"{alb_endpoint}/todos"
16+
17+
# WHEN
18+
response = data_fetcher.get_http_response(Request(method="GET", url=url))
19+
20+
# THEN
21+
assert response.status_code == 200
22+
assert response.content == "Hello world"
23+
assert response.headers["content-type"] == "text/plain"
24+
25+
assert response.headers["Foo"] == "zbr"
26+
27+
assert "MonsterCookie" in response.cookies
28+
assert "CookieMonster; Secure" not in response.cookies
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from tests.e2e.utils.data_fetcher.common import get_lambda_response
1+
from tests.e2e.utils.data_fetcher.common import get_http_response, get_lambda_response
22
from tests.e2e.utils.data_fetcher.logs import get_logs
33
from tests.e2e.utils.data_fetcher.metrics import get_metrics
44
from tests.e2e.utils.data_fetcher.traces import get_traces

tests/e2e/utils/data_fetcher/common.py

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Optional, Tuple
33

44
import boto3
5+
import requests as requests
56
from mypy_boto3_lambda import LambdaClient
67
from mypy_boto3_lambda.type_defs import InvocationResponseTypeDef
78

@@ -13,3 +14,8 @@ def get_lambda_response(
1314
payload = payload or ""
1415
execution_time = datetime.utcnow()
1516
return client.invoke(FunctionName=lambda_arn, InvocationType="RequestResponse", Payload=payload), execution_time
17+
18+
19+
def get_http_response(request: requests.Request) -> requests.Response:
20+
session = requests.Session()
21+
return session.send(request.prepare())

tests/e2e/utils/infrastructure.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ def __init__(self, feature_name: str, handlers_dir: Path) -> None:
232232
self.account_id = self.session.client("sts").get_caller_identity()["Account"]
233233
self.region = self.session.region_name
234234

235-
def create_lambda_functions(self, function_props: Optional[Dict] = None):
235+
def create_lambda_functions(self, function_props: Optional[Dict] = None) -> Dict[str, Function]:
236236
"""Create Lambda functions available under handlers_dir
237237
238238
It creates CloudFormation Outputs for every function found in PascalCase. For example,
@@ -244,6 +244,11 @@ def create_lambda_functions(self, function_props: Optional[Dict] = None):
244244
function_props: Optional[Dict]
245245
CDK Lambda FunctionProps as dictionary to override defaults
246246
247+
Returns
248+
-------
249+
output: Dict[str, Function]
250+
A dict with PascalCased function names and the corresponding CDK Function object
251+
247252
Examples
248253
--------
249254
@@ -265,6 +270,8 @@ def create_lambda_functions(self, function_props: Optional[Dict] = None):
265270
source = Code.from_asset(f"{self.handlers_dir}")
266271
props_override = function_props or {}
267272

273+
output: Dict[str, Function] = {}
274+
268275
for fn in handlers:
269276
fn_name = fn.stem
270277
function_settings = {
@@ -299,6 +306,10 @@ def create_lambda_functions(self, function_props: Optional[Dict] = None):
299306
name=fn_name_pascal_case, value=function_python.function_name, arn=function_python.function_arn
300307
)
301308

309+
output[fn_name_pascal_case] = function_python
310+
311+
return output
312+
302313
def deploy(self) -> Dict[str, str]:
303314
"""Creates CloudFormation Stack and return stack outputs as dict
304315

0 commit comments

Comments
 (0)