generated from ran-isenberg/aws-lambda-handler-cookbook
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature: add delete product impl. and tests (#73)
- Loading branch information
1 parent
f79d2fa
commit 0f96a22
Showing
14 changed files
with
165 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from product.crud.dal import get_dal_handler | ||
from product.crud.dal.db_handler import DalHandler | ||
from product.crud.handlers.utils.observability import logger, tracer | ||
|
||
|
||
@tracer.capture_method(capture_response=False) | ||
def handle_delete_request(product_id: str, table_name: str) -> None: | ||
logger.info('handling get product request', extra={'product_id': product_id}) | ||
|
||
dal_handler: DalHandler = get_dal_handler(table_name) | ||
dal_handler.delete_product(product_id=product_id) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from product.crud.dal import get_dal_handler | ||
from product.crud.dal.db_handler import DalHandler | ||
from product.crud.dal.schemas.db import ProductEntry | ||
from product.crud.handlers.utils.observability import logger, tracer | ||
from product.crud.schemas.output import GetProductOutput | ||
|
||
|
||
@tracer.capture_method(capture_response=False) | ||
def handle_get_request(product_id: str, table_name: str) -> GetProductOutput: | ||
logger.info('handling get product request', extra={'product_id': product_id}) | ||
|
||
dal_handler: DalHandler = get_dal_handler(table_name) | ||
product: ProductEntry = dal_handler.get_product(product_id=product_id) | ||
# convert from db entry to output, they won't always be the same | ||
return GetProductOutput(id=product.id, price=product.price, name=product.name) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,45 @@ | ||
from http import HTTPStatus | ||
from typing import Any, Dict | ||
|
||
from aws_lambda_env_modeler import get_environment_variables, init_environment_variables | ||
from aws_lambda_powertools.metrics import MetricUnit | ||
from aws_lambda_powertools.utilities.parser import ValidationError, parse | ||
from aws_lambda_powertools.utilities.typing import LambdaContext | ||
|
||
from product.crud.domain_logic.handle_delete_request import handle_delete_request | ||
from product.crud.handlers.schemas.env_vars import DeleteVars | ||
from product.crud.handlers.utils.http_responses import build_response | ||
from product.crud.handlers.utils.observability import logger, metrics, tracer | ||
from product.crud.schemas.exceptions import InternalServerException | ||
from product.crud.schemas.input import DeleteProductRequest | ||
|
||
|
||
@init_environment_variables(model=DeleteVars) | ||
@metrics.log_metrics | ||
@tracer.capture_lambda_handler(capture_response=False) | ||
def delete_product(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]: | ||
logger.set_correlation_id(context.aws_request_id) | ||
env_vars: DeleteVars = get_environment_variables(model=DeleteVars) | ||
logger.debug('environment variables', extra=env_vars.model_dump()) | ||
|
||
try: | ||
# we want to extract and parse the HTTP body from the api gw envelope | ||
delete_input: DeleteProductRequest = parse(event=event, model=DeleteProductRequest) | ||
logger.info('got a delete product request', extra={'product': delete_input.model_dump()}) | ||
except (ValidationError, TypeError) as exc: # pragma: no cover | ||
logger.exception('event failed input validation', extra={'error': str(exc)}) | ||
return build_response(http_status=HTTPStatus.BAD_REQUEST, body={}) | ||
|
||
metrics.add_metric(name='DeleteProductEvents', unit=MetricUnit.Count, value=1) | ||
|
||
try: | ||
handle_delete_request( | ||
product_id=delete_input.pathParameters.product, | ||
table_name=env_vars.TABLE_NAME, | ||
) | ||
except InternalServerException: # pragma: no cover | ||
logger.exception('finished handling delete product request with internal error') | ||
return build_response(http_status=HTTPStatus.INTERNAL_SERVER_ERROR, body={}) | ||
|
||
logger.info('finished handling delete product request') | ||
return build_response(http_status=HTTPStatus.NOT_IMPLEMENTED, body={}) | ||
return build_response(http_status=HTTPStatus.NO_CONTENT, body={}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,57 @@ | ||
import json | ||
from http import HTTPStatus | ||
from typing import Any, Dict | ||
from typing import Any, Dict, Generator | ||
|
||
from product.crud.handlers.delete_product import delete_product | ||
from tests.crud_utils import generate_api_gw_event, generate_create_product_request_body, generate_product_id | ||
import boto3 | ||
import pytest | ||
from botocore.stub import Stubber | ||
|
||
from product.crud.dal.dynamo_dal_handler import DynamoDalHandler | ||
from product.crud.dal.schemas.db import ProductEntry | ||
from tests.crud_utils import generate_api_gw_event, generate_product_id | ||
from tests.utils import generate_context | ||
|
||
|
||
def call_delete_product(body: Dict[str, Any]) -> Dict[str, Any]: | ||
return delete_product(body, generate_context()) | ||
@pytest.fixture() | ||
def add_product_entry_to_db(table_name: str) -> Generator[ProductEntry, None, None]: | ||
product = ProductEntry(id=generate_product_id(), price=1, name='test') | ||
table = boto3.resource('dynamodb').Table(table_name) | ||
table.put_item(Item=product.model_dump()) | ||
yield product | ||
table.delete_item(Key={'id': product.id}) | ||
|
||
|
||
def call_delete_product(event: Dict[str, Any]) -> Dict[str, Any]: | ||
# important is done here since idempotency decorator requires an env. variable during import time | ||
# conf.test sets that env. variable (table name) but it runs after imports | ||
# this way, idempotency import runs after conftest sets the values already | ||
from product.crud.handlers.delete_product import delete_product | ||
return delete_product(event, generate_context()) | ||
|
||
|
||
def test_handler_200_ok(): | ||
body = generate_create_product_request_body() | ||
product_id = generate_product_id() | ||
response = call_delete_product(generate_api_gw_event(body=body.model_dump(), path_params={'product': product_id})) | ||
def test_handler_204_success_delete(add_product_entry_to_db: ProductEntry): | ||
product_id = add_product_entry_to_db.id | ||
event = generate_api_gw_event(path_params={'product': product_id}) | ||
response = call_delete_product(event) | ||
# assert response | ||
assert response['statusCode'] == HTTPStatus.NOT_IMPLEMENTED | ||
assert response['statusCode'] == HTTPStatus.NO_CONTENT | ||
|
||
|
||
def test_internal_server_error(table_name): | ||
db_handler: DynamoDalHandler = DynamoDalHandler(table_name) | ||
table = db_handler._get_db_handler(table_name) | ||
|
||
with Stubber(table.meta.client) as stubber: | ||
stubber.add_client_error(method='delete_item', service_error_code='ValidationException') | ||
event = generate_api_gw_event(path_params={'product': generate_product_id()}) | ||
response = call_delete_product(event) | ||
|
||
assert response['statusCode'] == HTTPStatus.INTERNAL_SERVER_ERROR | ||
|
||
|
||
def test_handler_bad_request_invalid_path_params(): | ||
event = generate_api_gw_event(path_params={'dummy': generate_product_id()}) | ||
response = call_delete_product(event) | ||
assert response['statusCode'] == HTTPStatus.BAD_REQUEST | ||
body_dict = json.loads(response['body']) | ||
assert body_dict == {} |
12 changes: 6 additions & 6 deletions
12
tests/unit/crud/test_get_product_input.py → ...nit/crud/test_product_path_parms_input.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,28 @@ | ||
import pytest | ||
from aws_lambda_powertools.utilities.parser import ValidationError | ||
|
||
from product.crud.schemas.input import GetPathParams | ||
from product.crud.schemas.input import ProductPathParams | ||
|
||
|
||
def test_invalid_product_id_invalid_string(): | ||
with pytest.raises(ValidationError): | ||
GetPathParams.model_validate({'product': 'aa'}) | ||
ProductPathParams.model_validate({'product': 'aa'}) | ||
|
||
|
||
def test_invalid_product_empty(): | ||
with pytest.raises(ValidationError): | ||
GetPathParams.model_validate({}) | ||
ProductPathParams.model_validate({}) | ||
|
||
|
||
def test_invalid_product_type_mismatch(): | ||
with pytest.raises(ValidationError): | ||
GetPathParams.model_validate({'product': 6}) | ||
ProductPathParams.model_validate({'product': 6}) | ||
|
||
|
||
def test_invalid_json_key_but_valid_uuid(product_id): | ||
with pytest.raises(ValidationError): | ||
GetPathParams.model_validate({'order': product_id}) | ||
ProductPathParams.model_validate({'order': product_id}) | ||
|
||
|
||
def test_valid_uuid_input(product_id): | ||
GetPathParams.model_validate({'product': product_id}) | ||
ProductPathParams.model_validate({'product': product_id}) |
you don't need the extra=, the
logger.exception
will include theexception_name
field andexception
field with the traceback as a string :)