From 6382643c26fb33fb1c2975445874e9ba6280d6a9 Mon Sep 17 00:00:00 2001 From: Alex Whitehead-Smith Date: Mon, 25 Nov 2024 20:29:37 +0000 Subject: [PATCH] Add a handler to delete a recipe We'll pass the recipe ID through as a path parameter - similar to getting a single recipe --- backend/handlers/recipes/delete.py | 19 +++++++++++ backend/models/recipe.py | 6 ++++ backend/services/dynamodb.py | 3 ++ backend/tests/handlers/recipes/test_delete.py | 33 +++++++++++++++++++ backend/tests/models/test_recipe.py | 8 +++++ backend/tests/services/test_dynamodb.py | 10 ++++++ 6 files changed, 79 insertions(+) create mode 100644 backend/handlers/recipes/delete.py create mode 100644 backend/tests/handlers/recipes/test_delete.py diff --git a/backend/handlers/recipes/delete.py b/backend/handlers/recipes/delete.py new file mode 100644 index 0000000..f7e6717 --- /dev/null +++ b/backend/handlers/recipes/delete.py @@ -0,0 +1,19 @@ +from aws_lambda_powertools.utilities.data_classes import APIGatewayProxyEventV2 +from aws_lambda_powertools.utilities.typing import LambdaContext + + +from backend.models.recipe import Recipe +from backend.services.logger import get_logger +from backend.models.response import APIGatewayResponse + +logger = get_logger("recipes-delete") + + +@logger.inject_lambda_context +def handler( + event: APIGatewayProxyEventV2, context: LambdaContext +) -> APIGatewayResponse: + id = event["pathParameters"]["id"] + recipe = Recipe.find_one(id=id) + recipe.delete() + return APIGatewayResponse.build(body={}) diff --git a/backend/models/recipe.py b/backend/models/recipe.py index d72273e..9af749e 100644 --- a/backend/models/recipe.py +++ b/backend/models/recipe.py @@ -130,3 +130,9 @@ def save(self) -> None: self.client = Recipe._init_client() self.client.put_item("recipe", self.to_dict()) + + def delete(self) -> None: + if self.client is None: + self.client = Recipe._init_client() + + self.client.delete_item("recipe", self.id) diff --git a/backend/services/dynamodb.py b/backend/services/dynamodb.py index d75854b..5dbee1f 100644 --- a/backend/services/dynamodb.py +++ b/backend/services/dynamodb.py @@ -44,3 +44,6 @@ def get_item(self, partition_key: str, sort_key: str) -> Optional[dict[str, Any] return result["Item"] else: return None + + def delete_item(self, partition_key: str, sort_key: str) -> None: + self.table.delete_item(Key={"item_type": partition_key, "id": sort_key}) diff --git a/backend/tests/handlers/recipes/test_delete.py b/backend/tests/handlers/recipes/test_delete.py new file mode 100644 index 0000000..787f3f6 --- /dev/null +++ b/backend/tests/handlers/recipes/test_delete.py @@ -0,0 +1,33 @@ +from sys import path +import json +from unittest.mock import patch, MagicMock +from aws_lambda_powertools.utilities.data_classes import APIGatewayProxyEventV2 +from backend.tests.fixtures import LAMBDA_CONTEXT +from backend.handlers.recipes.delete import handler + +API_GATEWAY_EVENT_WITH_RECIPE_ID = APIGatewayProxyEventV2( + {"body": "", "pathParameters": {"id": "recipe_id"}} +) + + +class TestRecipesDeleteHandler: + + @patch("backend.handlers.recipes.delete.Recipe") + def test_handler_returns_a_dict_with_correct_fields(self, recipe_mock: MagicMock): + response = handler(API_GATEWAY_EVENT_WITH_RECIPE_ID, LAMBDA_CONTEXT) + for response_key in [ + "body", + "statusCode", + "cookies", + "headers", + "isBase64Encoded", + ]: + assert response_key in response.keys() + + @patch("backend.handlers.recipes.delete.Recipe") + def test_handler_calls_delete_on_the_recipe(self, recipe_mock: MagicMock): + found_recipe_mock = MagicMock() + recipe_mock.find_one.return_value = found_recipe_mock + handler(API_GATEWAY_EVENT_WITH_RECIPE_ID, LAMBDA_CONTEXT) + + found_recipe_mock.delete.assert_called_once diff --git a/backend/tests/models/test_recipe.py b/backend/tests/models/test_recipe.py index 0dfe019..4373569 100644 --- a/backend/tests/models/test_recipe.py +++ b/backend/tests/models/test_recipe.py @@ -171,3 +171,11 @@ def test_find_one_returns_none_if_no_match(self, dynamodb_mock: MagicMock): recipe = Recipe.find_one("test") assert recipe is None + + @patch("backend.models.recipe.DynamoDBClient") + def test_delete_calls_dynamo_client_with_id(self, dynamodb_mock: MagicMock): + recipe = Recipe.from_dict(RECIPE_DICT) + recipe.delete() + dynamodb_mock.return_value.delete_item.assert_called_once_with( + "recipe", recipe.id + ) diff --git a/backend/tests/services/test_dynamodb.py b/backend/tests/services/test_dynamodb.py index 6215c8f..03c348e 100644 --- a/backend/tests/services/test_dynamodb.py +++ b/backend/tests/services/test_dynamodb.py @@ -74,3 +74,13 @@ def test_get_item_returns_one_item(self): assert item == mock_item del environ["DYNAMODB_TABLE_NAME"] + + def test_delete_item_passes_through_the_id(self): + environ["DYNAMODB_TABLE_NAME"] = "TEST" + table_mock = Mock() + client = DynamoDBClient(table=table_mock) + client.delete_item("test_type", "test_id") + + table_mock.delete_item.assert_called_once_with( + Key={"item_type": "test_type", "id": "test_id"} + )