diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index 4e83dab5a..f3b4e3be1 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -463,6 +463,24 @@ class RecipeService(RecipeServiceBase): return recipe + def _remove_non_existent_ingredient_references(self, update_data: Recipe) -> Recipe: + """Removes the references of ingredients from steps that no longer exist.""" + + current_ingredient_reference_ids = set() # set of current ingredient(s) reference id's + for ingredient in update_data.recipe_ingredient: + current_ingredient_reference_ids.add(ingredient.reference_id) + + recipe_instructions = update_data.recipe_instructions + if recipe_instructions is not None: + for instruction in recipe_instructions: + instruction.ingredient_references = [ + ref + for ref in instruction.ingredient_references + if ref.reference_id in current_ingredient_reference_ids + ] + + return update_data + def _resolve_ingredient_sub_recipes(self, update_data: Recipe) -> Recipe: """Resolve all referenced_recipe slugs to IDs within the current group.""" if not update_data.recipe_ingredient: @@ -488,7 +506,7 @@ class RecipeService(RecipeServiceBase): def update_one(self, slug_or_id: str | UUID, update_data: Recipe) -> Recipe: recipe = self._pre_update_check(slug_or_id, update_data) - # Resolve sub-recipe references before passing to repository + update_data = self._remove_non_existent_ingredient_references(update_data) update_data = self._resolve_ingredient_sub_recipes(update_data) new_data = self.group_recipes.update(recipe.slug, update_data) diff --git a/tests/integration_tests/user_recipe_tests/test_recipe_crud.py b/tests/integration_tests/user_recipe_tests/test_recipe_crud.py index fc5f636a1..d8d61340e 100644 --- a/tests/integration_tests/user_recipe_tests/test_recipe_crud.py +++ b/tests/integration_tests/user_recipe_tests/test_recipe_crud.py @@ -1127,6 +1127,66 @@ def test_sub_recipe_not_found_in_other_group(api_client: TestClient, unique_user assert response.json()["detail"]["message"] == "No Entry Found" +def test_update_with_non_existent_ingredient_references(api_client: TestClient, unique_user: TestUser): + """Test that the non-existent ingredient references are removed""" + + database = unique_user.repos + + # Create a food + food = database.ingredient_foods.create( + SaveIngredientFood( + name=random_string(10), + group_id=unique_user.group_id, + ) + ) + + # Create a recipe_a + recipe_a: Recipe = database.recipes.create( + Recipe( + name=random_string(10), + user_id=unique_user.user_id, + group_id=unique_user.group_id, + recipe_ingredient=[ + RecipeIngredient(note="", food=food), + ], + ) + ) + + recipe_url = api_routes.recipes_slug(recipe_a.slug) + response = api_client.get(recipe_url, headers=unique_user.token) + assert response.status_code == 200 + recipe_data = json.loads(response.text) + + """ + Updating the recipe with some references of non-exisitent ingredient(s), + mimicking the behaviour observed from the screen + """ + food_ingredient_ref_id = recipe_data["recipeIngredient"][0]["referenceId"] + recipe_data["recipeInstructions"].append( + { + "title": "Step One", + "text": "Mash the Peas to form a paste.", + "ingredientReferences": [ + {"referenceId": food_ingredient_ref_id}, + {"referenceId": "5fb334fe-00b7-43d6-bb75-355977852543"}, + ], + } + ) + + # Call the put API end point + response = api_client.put(recipe_url, json=recipe_data, headers=unique_user.token) + assert response.status_code == 200 + recipe_data = json.loads(response.text) + + """ + Assert the result to verify that the resulting recipe instruction has reference + to only valid id i.e. food_ingredient_ref_id + """ + step_one_ingr_refs = recipe_data["recipeInstructions"][0]["ingredientReferences"] + assert len(step_one_ingr_refs) == 1 + assert step_one_ingr_refs[0]["referenceId"] == food_ingredient_ref_id + + def test_duplicate(api_client: TestClient, unique_user: TestUser): recipe_data = recipe_test_data[0]