From 26c6bacdfda22a31233b7ae01f287ecd01922ad9 Mon Sep 17 00:00:00 2001 From: BVMiko Date: Sun, 22 Aug 2021 19:08:09 -0500 Subject: [PATCH 1/9] Update api_gateway.py Add Blueprint class to ApiGatewayResolver --- .../event_handler/api_gateway.py | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index 754cc24710d..63558e8e855 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -6,9 +6,9 @@ import traceback import zlib from enum import Enum -from functools import partial +from functools import partial, wraps from http import HTTPStatus -from typing import Any, Callable, Dict, List, Optional, Set, Union +from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union from aws_lambda_powertools.event_handler import content_types from aws_lambda_powertools.event_handler.exceptions import ServiceError @@ -630,3 +630,42 @@ def _to_response(self, result: Union[Dict, Response]) -> Response: def _json_dump(self, obj: Any) -> str: return self._serializer(obj) + +class Blueprint(): + """Blueprint helper class to allow splitting ApiGatewayResolver into multiple files""" + def __init__(self): + self._api: Dict[tuple, Callable] = {} + + def __call__(self, rule: str, method: Union[str, Tuple[str], List[str]], cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None,): + def actual_decorator(func: Callable): + @wraps(func) + def wrapper(app: ApiGatewayResolver): + def inner_wrapper(): + return func(app) + return inner_wrapper + if isinstance(method, (list, tuple)): + for item in method: + self._api[(rule, item, cors, compress, cache_control)] = wrapper + else: + self._api[(rule, method, cors, compress, cache_control)] = wrapper + return actual_decorator + + def get(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None): + return self.__call__(rule, "GET", cors, compress, cache_control) + + def post(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None): + return self.__call__(rule, "POST", cors, compress, cache_control) + + def put(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None): + return self.__call__(rule, "PUT", cors, compress, cache_control) + + def delete(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None): + return self.__call__(rule, "DELETE", cors, compress, cache_control) + + def patch(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None): + return self.__call__(rule, "PATCH", cors, compress, cache_control) + + def register_to_app(self, app:ApiGatewayResolver): + """Bind a blueprint object to an existing ApiGatewayResolver instance""" + for route, func in self._api.items(): + app.route(*route)(func(app=app)) From 6db69bc326200e6ab193363ccff07215d7d0d03f Mon Sep 17 00:00:00 2001 From: Brian Villemarette Date: Fri, 1 Oct 2021 14:44:59 +0000 Subject: [PATCH 2/9] Convert from callable to use route; add test; fix up formatting --- .../event_handler/api_gateway.py | 36 +++++++++++++------ .../event_handler/test_api_gateway.py | 19 ++++++++++ 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index 63558e8e855..8326457768f 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -631,41 +631,57 @@ def _to_response(self, result: Union[Dict, Response]) -> Response: def _json_dump(self, obj: Any) -> str: return self._serializer(obj) -class Blueprint(): + +class Blueprint: """Blueprint helper class to allow splitting ApiGatewayResolver into multiple files""" + def __init__(self): self._api: Dict[tuple, Callable] = {} - def __call__(self, rule: str, method: Union[str, Tuple[str], List[str]], cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None,): + def route( + self, + rule: str, + method: Union[str, Tuple[str], List[str]], + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + ): def actual_decorator(func: Callable): @wraps(func) def wrapper(app: ApiGatewayResolver): def inner_wrapper(): return func(app) + return inner_wrapper + if isinstance(method, (list, tuple)): for item in method: self._api[(rule, item, cors, compress, cache_control)] = wrapper else: self._api[(rule, method, cors, compress, cache_control)] = wrapper + return actual_decorator def get(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None): - return self.__call__(rule, "GET", cors, compress, cache_control) + return self.route(rule, "GET", cors, compress, cache_control) def post(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None): - return self.__call__(rule, "POST", cors, compress, cache_control) + return self.route(rule, "POST", cors, compress, cache_control) def put(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None): - return self.__call__(rule, "PUT", cors, compress, cache_control) + return self.route(rule, "PUT", cors, compress, cache_control) - def delete(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None): - return self.__call__(rule, "DELETE", cors, compress, cache_control) + def delete( + self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None + ): + return self.route(rule, "DELETE", cors, compress, cache_control) - def patch(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None): - return self.__call__(rule, "PATCH", cors, compress, cache_control) + def patch( + self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None + ): + return self.route(rule, "PATCH", cors, compress, cache_control) - def register_to_app(self, app:ApiGatewayResolver): + def register_to_app(self, app: ApiGatewayResolver): """Bind a blueprint object to an existing ApiGatewayResolver instance""" for route, func in self._api.items(): app.route(*route)(func(app=app)) diff --git a/tests/functional/event_handler/test_api_gateway.py b/tests/functional/event_handler/test_api_gateway.py index 21700ec09dd..0e224e9d31a 100644 --- a/tests/functional/event_handler/test_api_gateway.py +++ b/tests/functional/event_handler/test_api_gateway.py @@ -13,6 +13,7 @@ from aws_lambda_powertools.event_handler import content_types from aws_lambda_powertools.event_handler.api_gateway import ( ApiGatewayResolver, + Blueprint, CORSConfig, ProxyEventType, Response, @@ -860,3 +861,21 @@ def base(): # THEN process event correctly assert result["statusCode"] == 200 assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + + +def test_api_gateway_app_proxy(): + # GIVEN a Blueprint with registered routes + app = ApiGatewayResolver() + blueprint = Blueprint() + + @blueprint.get("/my/path") + def foo(app): + return {} + + blueprint.register_to_app(app) + # WHEN calling the event handler after applying routes from blueprint object + result = app(LOAD_GW_EVENT, {}) + + # THEN process event correctly + assert result["statusCode"] == 200 + assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON From c0adab06884f65f06b613fe03617b088b4e3648f Mon Sep 17 00:00:00 2001 From: Brian Villemarette Date: Tue, 31 Aug 2021 15:31:18 +0000 Subject: [PATCH 3/9] Swap registration from a function of blueprints to a function of the app --- .../event_handler/api_gateway.py | 16 ++++++++-------- .../functional/event_handler/test_api_gateway.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index 8326457768f..7de2583cefb 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -631,12 +631,17 @@ def _to_response(self, result: Union[Dict, Response]) -> Response: def _json_dump(self, obj: Any) -> str: return self._serializer(obj) + def register_blueprint(self, blueprint: "Blueprint") -> None: + """Adds all routes defined in a blueprint""" + for route, func in blueprint.api.items(): + self.route(*route)(func(app=self)) + class Blueprint: """Blueprint helper class to allow splitting ApiGatewayResolver into multiple files""" def __init__(self): - self._api: Dict[tuple, Callable] = {} + self.api: Dict[tuple, Callable] = {} def route( self, @@ -656,9 +661,9 @@ def inner_wrapper(): if isinstance(method, (list, tuple)): for item in method: - self._api[(rule, item, cors, compress, cache_control)] = wrapper + self.api[(rule, item, cors, compress, cache_control)] = wrapper else: - self._api[(rule, method, cors, compress, cache_control)] = wrapper + self.api[(rule, method, cors, compress, cache_control)] = wrapper return actual_decorator @@ -680,8 +685,3 @@ def patch( self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None ): return self.route(rule, "PATCH", cors, compress, cache_control) - - def register_to_app(self, app: ApiGatewayResolver): - """Bind a blueprint object to an existing ApiGatewayResolver instance""" - for route, func in self._api.items(): - app.route(*route)(func(app=app)) diff --git a/tests/functional/event_handler/test_api_gateway.py b/tests/functional/event_handler/test_api_gateway.py index 0e224e9d31a..9920001147e 100644 --- a/tests/functional/event_handler/test_api_gateway.py +++ b/tests/functional/event_handler/test_api_gateway.py @@ -872,7 +872,7 @@ def test_api_gateway_app_proxy(): def foo(app): return {} - blueprint.register_to_app(app) + app.register_blueprint(blueprint) # WHEN calling the event handler after applying routes from blueprint object result = app(LOAD_GW_EVENT, {}) From f2be849bb7ccbebadb0a2c49758aea183b1549ff Mon Sep 17 00:00:00 2001 From: Brian Villemarette Date: Fri, 1 Oct 2021 14:30:13 +0000 Subject: [PATCH 4/9] Add support for named parameters --- aws_lambda_powertools/event_handler/api_gateway.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index 7de2583cefb..3e2545d8d05 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -654,8 +654,8 @@ def route( def actual_decorator(func: Callable): @wraps(func) def wrapper(app: ApiGatewayResolver): - def inner_wrapper(): - return func(app) + def inner_wrapper(**kwargs): + return func(app, **kwargs) return inner_wrapper From f2dcf90c36e63484bae03a47bf0765ba2af0f6c7 Mon Sep 17 00:00:00 2001 From: Brian Villemarette Date: Fri, 1 Oct 2021 14:45:41 +0000 Subject: [PATCH 5/9] Add test coverage for using a list of methods --- .../event_handler/test_api_gateway.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/functional/event_handler/test_api_gateway.py b/tests/functional/event_handler/test_api_gateway.py index 9920001147e..c15b6696852 100644 --- a/tests/functional/event_handler/test_api_gateway.py +++ b/tests/functional/event_handler/test_api_gateway.py @@ -879,3 +879,27 @@ def foo(app): # THEN process event correctly assert result["statusCode"] == 200 assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + + +def test_api_gateway_app_proxy_with_params(): + # GIVEN a Blueprint with registered routes + app = ApiGatewayResolver() + blueprint = Blueprint() + req = "foo" + event = deepcopy(LOAD_GW_EVENT) + event["resource"] = "/accounts/{account_id}" + event["path"] = f"/accounts/{req}" + + @blueprint.route(rule="/accounts/", method=["GET", "POST"]) + def foo(app: ApiGatewayResolver, account_id): + assert app.current_event.raw_event == event + assert account_id == f"{req}" + return {} + + app.register_blueprint(blueprint) + # WHEN calling the event handler after applying routes from blueprint object + result = app(event, {}) + + # THEN process event correctly + assert result["statusCode"] == 200 + assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON From 2b0ff5ed9a524042760035aa7867de58e54de4db Mon Sep 17 00:00:00 2001 From: Brian Villemarette Date: Fri, 1 Oct 2021 16:36:08 +0000 Subject: [PATCH 6/9] Add missing type definition --- tests/functional/event_handler/test_api_gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/event_handler/test_api_gateway.py b/tests/functional/event_handler/test_api_gateway.py index c15b6696852..d8e14b8ea8b 100644 --- a/tests/functional/event_handler/test_api_gateway.py +++ b/tests/functional/event_handler/test_api_gateway.py @@ -869,7 +869,7 @@ def test_api_gateway_app_proxy(): blueprint = Blueprint() @blueprint.get("/my/path") - def foo(app): + def foo(app: ApiGatewayResolver): return {} app.register_blueprint(blueprint) From dd22d5aa103b3187e12a8decdb2b4f7ee2c140cf Mon Sep 17 00:00:00 2001 From: Brian Villemarette Date: Fri, 1 Oct 2021 17:42:13 +0000 Subject: [PATCH 7/9] Support a prefix defined during the Blueprint registration --- .../event_handler/api_gateway.py | 6 ++- .../event_handler/test_api_gateway.py | 39 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index 3e2545d8d05..a989cbfb1e7 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -631,9 +631,13 @@ def _to_response(self, result: Union[Dict, Response]) -> Response: def _json_dump(self, obj: Any) -> str: return self._serializer(obj) - def register_blueprint(self, blueprint: "Blueprint") -> None: + def register_blueprint(self, blueprint: "Blueprint", prefix: Optional[str] = None) -> None: """Adds all routes defined in a blueprint""" for route, func in blueprint.api.items(): + if prefix and route[0] == "/": + route = (prefix, *route[1:]) + elif prefix: + route = (f"{prefix}{route[0]}" if prefix else route[0], *route[1:]) self.route(*route)(func(app=self)) diff --git a/tests/functional/event_handler/test_api_gateway.py b/tests/functional/event_handler/test_api_gateway.py index d8e14b8ea8b..8d8b5a6442f 100644 --- a/tests/functional/event_handler/test_api_gateway.py +++ b/tests/functional/event_handler/test_api_gateway.py @@ -903,3 +903,42 @@ def foo(app: ApiGatewayResolver, account_id): # THEN process event correctly assert result["statusCode"] == 200 assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + + +def test_api_gateway_app_proxy_with_prefix(): + # GIVEN a Blueprint with registered routes + # AND a prefix is defined during the registration + app = ApiGatewayResolver() + blueprint = Blueprint() + + @blueprint.get(rule="/path") + def foo(app: ApiGatewayResolver): + return {} + + app.register_blueprint(blueprint, prefix="/my") + # WHEN calling the event handler after applying routes from blueprint object + result = app(LOAD_GW_EVENT, {}) + + # THEN process event correctly + assert result["statusCode"] == 200 + assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + + +def test_api_gateway_app_proxy_with_prefix_equals_path(): + # GIVEN a Blueprint with registered routes + # AND a prefix is defined during the registration + app = ApiGatewayResolver() + blueprint = Blueprint() + + @blueprint.get(rule="/") + def foo(app: ApiGatewayResolver): + return {} + + app.register_blueprint(blueprint, prefix="/my/path") + # WHEN calling the event handler after applying routes from blueprint object + # WITH the request path matching the registration prefix + result = app(LOAD_GW_EVENT, {}) + + # THEN process event correctly + assert result["statusCode"] == 200 + assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON From 836cd71601ab572bc55be3b54ba57e2a431515f0 Mon Sep 17 00:00:00 2001 From: Brian Villemarette Date: Fri, 1 Oct 2021 20:14:43 +0000 Subject: [PATCH 8/9] Clean up duplicate check --- aws_lambda_powertools/event_handler/api_gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index a989cbfb1e7..a296874f1f7 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -637,7 +637,7 @@ def register_blueprint(self, blueprint: "Blueprint", prefix: Optional[str] = Non if prefix and route[0] == "/": route = (prefix, *route[1:]) elif prefix: - route = (f"{prefix}{route[0]}" if prefix else route[0], *route[1:]) + route = (f"{prefix}{route[0]}", *route[1:]) self.route(*route)(func(app=self)) From f16e32c276861f99017034a44bc4388fde6f7174 Mon Sep 17 00:00:00 2001 From: Brian Villemarette Date: Mon, 4 Oct 2021 22:32:07 +0000 Subject: [PATCH 9/9] Rename to Router; change the way to access the event and context --- .../event_handler/api_gateway.py | 27 +++-- .../event_handler/test_api_gateway.py | 114 +++++++++++++----- 2 files changed, 102 insertions(+), 39 deletions(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index a296874f1f7..d3a79761556 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -631,22 +631,33 @@ def _to_response(self, result: Union[Dict, Response]) -> Response: def _json_dump(self, obj: Any) -> str: return self._serializer(obj) - def register_blueprint(self, blueprint: "Blueprint", prefix: Optional[str] = None) -> None: - """Adds all routes defined in a blueprint""" - for route, func in blueprint.api.items(): + def include_router(self, router: "Router", prefix: Optional[str] = None) -> None: + """Adds all routes defined in a router""" + router._app = self + for route, func in router.api.items(): if prefix and route[0] == "/": route = (prefix, *route[1:]) elif prefix: route = (f"{prefix}{route[0]}", *route[1:]) - self.route(*route)(func(app=self)) + self.route(*route)(func()) -class Blueprint: - """Blueprint helper class to allow splitting ApiGatewayResolver into multiple files""" +class Router: + """Router helper class to allow splitting ApiGatewayResolver into multiple files""" + + _app: ApiGatewayResolver def __init__(self): self.api: Dict[tuple, Callable] = {} + @property + def current_event(self) -> BaseProxyEvent: + return self._app.current_event + + @property + def lambda_context(self) -> LambdaContext: + return self._app.lambda_context + def route( self, rule: str, @@ -657,9 +668,9 @@ def route( ): def actual_decorator(func: Callable): @wraps(func) - def wrapper(app: ApiGatewayResolver): + def wrapper(): def inner_wrapper(**kwargs): - return func(app, **kwargs) + return func(**kwargs) return inner_wrapper diff --git a/tests/functional/event_handler/test_api_gateway.py b/tests/functional/event_handler/test_api_gateway.py index 8d8b5a6442f..afc979065f8 100644 --- a/tests/functional/event_handler/test_api_gateway.py +++ b/tests/functional/event_handler/test_api_gateway.py @@ -13,11 +13,11 @@ from aws_lambda_powertools.event_handler import content_types from aws_lambda_powertools.event_handler.api_gateway import ( ApiGatewayResolver, - Blueprint, CORSConfig, ProxyEventType, Response, ResponseBuilder, + Router, ) from aws_lambda_powertools.event_handler.exceptions import ( BadRequestError, @@ -863,17 +863,17 @@ def base(): assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON -def test_api_gateway_app_proxy(): - # GIVEN a Blueprint with registered routes +def test_api_gateway_app_router(): + # GIVEN a Router with registered routes app = ApiGatewayResolver() - blueprint = Blueprint() + router = Router() - @blueprint.get("/my/path") - def foo(app: ApiGatewayResolver): + @router.get("/my/path") + def foo(): return {} - app.register_blueprint(blueprint) - # WHEN calling the event handler after applying routes from blueprint object + app.include_router(router) + # WHEN calling the event handler after applying routes from router object result = app(LOAD_GW_EVENT, {}) # THEN process event correctly @@ -881,42 +881,44 @@ def foo(app: ApiGatewayResolver): assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON -def test_api_gateway_app_proxy_with_params(): - # GIVEN a Blueprint with registered routes +def test_api_gateway_app_router_with_params(): + # GIVEN a Router with registered routes app = ApiGatewayResolver() - blueprint = Blueprint() + router = Router() req = "foo" event = deepcopy(LOAD_GW_EVENT) event["resource"] = "/accounts/{account_id}" event["path"] = f"/accounts/{req}" + lambda_context = {} - @blueprint.route(rule="/accounts/", method=["GET", "POST"]) - def foo(app: ApiGatewayResolver, account_id): - assert app.current_event.raw_event == event + @router.route(rule="/accounts/", method=["GET", "POST"]) + def foo(account_id): + assert router.current_event.raw_event == event + assert router.lambda_context == lambda_context assert account_id == f"{req}" return {} - app.register_blueprint(blueprint) - # WHEN calling the event handler after applying routes from blueprint object - result = app(event, {}) + app.include_router(router) + # WHEN calling the event handler after applying routes from router object + result = app(event, lambda_context) # THEN process event correctly assert result["statusCode"] == 200 assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON -def test_api_gateway_app_proxy_with_prefix(): - # GIVEN a Blueprint with registered routes +def test_api_gateway_app_router_with_prefix(): + # GIVEN a Router with registered routes # AND a prefix is defined during the registration app = ApiGatewayResolver() - blueprint = Blueprint() + router = Router() - @blueprint.get(rule="/path") - def foo(app: ApiGatewayResolver): + @router.get(rule="/path") + def foo(): return {} - app.register_blueprint(blueprint, prefix="/my") - # WHEN calling the event handler after applying routes from blueprint object + app.include_router(router, prefix="/my") + # WHEN calling the event handler after applying routes from router object result = app(LOAD_GW_EVENT, {}) # THEN process event correctly @@ -924,21 +926,71 @@ def foo(app: ApiGatewayResolver): assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON -def test_api_gateway_app_proxy_with_prefix_equals_path(): - # GIVEN a Blueprint with registered routes +def test_api_gateway_app_router_with_prefix_equals_path(): + # GIVEN a Router with registered routes # AND a prefix is defined during the registration app = ApiGatewayResolver() - blueprint = Blueprint() + router = Router() - @blueprint.get(rule="/") - def foo(app: ApiGatewayResolver): + @router.get(rule="/") + def foo(): return {} - app.register_blueprint(blueprint, prefix="/my/path") - # WHEN calling the event handler after applying routes from blueprint object + app.include_router(router, prefix="/my/path") + # WHEN calling the event handler after applying routes from router object # WITH the request path matching the registration prefix result = app(LOAD_GW_EVENT, {}) # THEN process event correctly assert result["statusCode"] == 200 assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON + + +def test_api_gateway_app_router_with_different_methods(): + # GIVEN a Router with all the possible HTTP methods + app = ApiGatewayResolver() + router = Router() + + @router.get("/not_matching_get") + def get_func(): + raise RuntimeError() + + @router.post("/no_matching_post") + def post_func(): + raise RuntimeError() + + @router.put("/no_matching_put") + def put_func(): + raise RuntimeError() + + @router.delete("/no_matching_delete") + def delete_func(): + raise RuntimeError() + + @router.patch("/no_matching_patch") + def patch_func(): + raise RuntimeError() + + app.include_router(router) + + # Also check check the route configurations + routes = app._routes + assert len(routes) == 5 + for route in routes: + if route.func == get_func: + assert route.method == "GET" + elif route.func == post_func: + assert route.method == "POST" + elif route.func == put_func: + assert route.method == "PUT" + elif route.func == delete_func: + assert route.method == "DELETE" + elif route.func == patch_func: + assert route.method == "PATCH" + + # WHEN calling the handler + # THEN return a 404 + result = app(LOAD_GW_EVENT, None) + assert result["statusCode"] == 404 + # AND cors headers are not returned + assert "Access-Control-Allow-Origin" not in result["headers"]