From 5f088e0501403a546df70167bcfd082852e94b50 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 8 Jul 2024 17:11:35 +0200 Subject: [PATCH] Add Mealie service to get recipe (#121462) --- homeassistant/components/mealie/const.py | 1 + homeassistant/components/mealie/services.py | 73 +++-- homeassistant/components/mealie/strings.json | 17 ++ tests/components/mealie/conftest.py | 5 +- .../mealie/fixtures/get_recipe.json | 266 ++++++++++++++++++ .../mealie/snapshots/test_services.ambr | 190 +++++++++++++ tests/components/mealie/test_services.py | 51 +++- 7 files changed, 584 insertions(+), 19 deletions(-) create mode 100644 tests/components/mealie/fixtures/get_recipe.json diff --git a/homeassistant/components/mealie/const.py b/homeassistant/components/mealie/const.py index 1c4eccb56a1..4de0863ada7 100644 --- a/homeassistant/components/mealie/const.py +++ b/homeassistant/components/mealie/const.py @@ -9,3 +9,4 @@ LOGGER = logging.getLogger(__package__) ATTR_CONFIG_ENTRY_ID = "config_entry_id" ATTR_START_DATE = "start_date" ATTR_END_DATE = "end_date" +ATTR_RECIPE_ID = "recipe_id" diff --git a/homeassistant/components/mealie/services.py b/homeassistant/components/mealie/services.py index f91a1de7632..9b999690067 100644 --- a/homeassistant/components/mealie/services.py +++ b/homeassistant/components/mealie/services.py @@ -4,6 +4,7 @@ from dataclasses import asdict from datetime import date from typing import cast +from aiomealie.exceptions import MealieNotFoundError import voluptuous as vol from homeassistant.config_entries import ConfigEntryState @@ -15,7 +16,13 @@ from homeassistant.core import ( ) from homeassistant.exceptions import ServiceValidationError -from .const import ATTR_CONFIG_ENTRY_ID, ATTR_END_DATE, ATTR_START_DATE, DOMAIN +from .const import ( + ATTR_CONFIG_ENTRY_ID, + ATTR_END_DATE, + ATTR_RECIPE_ID, + ATTR_START_DATE, + DOMAIN, +) from .coordinator import MealieConfigEntry SERVICE_GET_MEALPLAN = "get_mealplan" @@ -27,28 +34,38 @@ SERVICE_GET_MEALPLAN_SCHEMA = vol.Schema( } ) +SERVICE_GET_RECIPE = "get_recipe" +SERVICE_GET_RECIPE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_CONFIG_ENTRY_ID): str, + vol.Required(ATTR_RECIPE_ID): str, + } +) + + +def async_get_entry(hass: HomeAssistant, config_entry_id: str) -> MealieConfigEntry: + """Get the Mealie config entry.""" + if not (entry := hass.config_entries.async_get_entry(config_entry_id)): + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="integration_not_found", + translation_placeholders={"target": DOMAIN}, + ) + if entry.state is not ConfigEntryState.LOADED: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="not_loaded", + translation_placeholders={"target": entry.title}, + ) + return cast(MealieConfigEntry, entry) + def setup_services(hass: HomeAssistant) -> None: """Set up the services for the Mealie integration.""" async def async_get_mealplan(call: ServiceCall) -> ServiceResponse: """Get the mealplan for a specific range.""" - if not ( - entry := hass.config_entries.async_get_entry( - call.data[ATTR_CONFIG_ENTRY_ID] - ) - ): - raise ServiceValidationError( - translation_domain=DOMAIN, - translation_key="integration_not_found", - translation_placeholders={"target": DOMAIN}, - ) - if entry.state is not ConfigEntryState.LOADED: - raise ServiceValidationError( - translation_domain=DOMAIN, - translation_key="not_loaded", - translation_placeholders={"target": entry.title}, - ) + entry = async_get_entry(hass, call.data[ATTR_CONFIG_ENTRY_ID]) start_date = call.data.get(ATTR_START_DATE, date.today()) end_date = call.data.get(ATTR_END_DATE, date.today()) if end_date < start_date: @@ -60,6 +77,21 @@ def setup_services(hass: HomeAssistant) -> None: mealplans = await client.get_mealplans(start_date, end_date) return {"mealplan": [asdict(x) for x in mealplans.items]} + async def async_get_recipe(call: ServiceCall) -> ServiceResponse: + """Get a recipe.""" + entry = async_get_entry(hass, call.data[ATTR_CONFIG_ENTRY_ID]) + recipe_id = call.data[ATTR_RECIPE_ID] + client = entry.runtime_data.client + try: + recipe = await client.get_recipe(recipe_id) + except MealieNotFoundError as err: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="recipe_not_found", + translation_placeholders={"recipe_id": recipe_id}, + ) from err + return {"recipe": asdict(recipe)} + hass.services.async_register( DOMAIN, SERVICE_GET_MEALPLAN, @@ -67,3 +99,10 @@ def setup_services(hass: HomeAssistant) -> None: schema=SERVICE_GET_MEALPLAN_SCHEMA, supports_response=SupportsResponse.ONLY, ) + hass.services.async_register( + DOMAIN, + SERVICE_GET_RECIPE, + async_get_recipe, + schema=SERVICE_GET_RECIPE_SCHEMA, + supports_response=SupportsResponse.ONLY, + ) diff --git a/homeassistant/components/mealie/strings.json b/homeassistant/components/mealie/strings.json index 75b83f16b0f..4759d12b95c 100644 --- a/homeassistant/components/mealie/strings.json +++ b/homeassistant/components/mealie/strings.json @@ -45,6 +45,9 @@ }, "end_date_before_start_date": { "message": "End date must be after start date." + }, + "recipe_not_found": { + "message": "Recipe with ID or slug `{recipe_id}` not found." } }, "services": { @@ -65,6 +68,20 @@ "description": "The enddate of the data to get (default: today)." } } + }, + "get_recipe": { + "name": "Get recipe", + "description": "Get recipe from Mealie", + "fields": { + "config_entry_id": { + "name": "[%key:component::mealie::services::get_mealplan::fields::config_entry_id::name%]", + "description": "[%key:component::mealie::services::get_mealplan::fields::config_entry_id::description%]" + }, + "recipe_id": { + "name": "Recipe ID or slug", + "description": "The recipe ID or the slug of the recipe to get." + } + } } } } diff --git a/tests/components/mealie/conftest.py b/tests/components/mealie/conftest.py index be92c7bf9b0..6a1db534811 100644 --- a/tests/components/mealie/conftest.py +++ b/tests/components/mealie/conftest.py @@ -3,7 +3,7 @@ from collections.abc import Generator from unittest.mock import patch -from aiomealie import About, Mealplan, MealplanResponse, UserInfo +from aiomealie import About, Mealplan, MealplanResponse, Recipe, UserInfo from mashumaro.codecs.orjson import ORJSONDecoder import pytest @@ -50,6 +50,9 @@ def mock_mealie_client() -> Generator[AsyncMock]: client.get_about.return_value = About.from_json( load_fixture("about.json", DOMAIN) ) + client.get_recipe.return_value = Recipe.from_json( + load_fixture("get_recipe.json", DOMAIN) + ) yield client diff --git a/tests/components/mealie/fixtures/get_recipe.json b/tests/components/mealie/fixtures/get_recipe.json new file mode 100644 index 00000000000..a5ccd1876e5 --- /dev/null +++ b/tests/components/mealie/fixtures/get_recipe.json @@ -0,0 +1,266 @@ +{ + "id": "fada9582-709b-46aa-b384-d5952123ad93", + "userId": "bf1c62fe-4941-4332-9886-e54e88dbdba0", + "groupId": "24477569-f6af-4b53-9e3f-6d04b0ca6916", + "name": "Original Sacher-Torte (2)", + "slug": "original-sacher-torte-2", + "image": "SuPW", + "recipeYield": "4 servings", + "totalTime": "2 hours 30 minutes", + "prepTime": "1 hour 30 minutes", + "cookTime": null, + "performTime": "1 hour", + "description": "The world’s most famous cake, the Original Sacher-Torte, is the consequence of several lucky twists of fate. The first was in 1832, when the Austrian State Chancellor, Prince Klemens Wenzel von Metternich, tasked his kitchen staff with concocting an extraordinary dessert to impress his special guests. As fortune had it, the chef had fallen ill that evening, leaving the apprentice chef, the then-16-year-old Franz Sacher, to perform this culinary magic trick. Metternich’s parting words to the talented teenager: “I hope you won’t disgrace me tonight.”", + "recipeCategory": [], + "tags": [ + { + "id": "1b5789b9-3af6-412e-8c77-8a01caa0aac9", + "name": "Sacher", + "slug": "sacher" + }, + { + "id": "1cf17f96-58b5-4bd3-b1e8-1606a64b413d", + "name": "Cake", + "slug": "cake" + }, + { + "id": "3f5f0a3d-728f-440d-a6c7-5a68612e8c67", + "name": "Torte", + "slug": "torte" + }, + { + "id": "525f388d-6ee0-4ebe-91fc-dd320a7583f0", + "name": "Sachertorte", + "slug": "sachertorte" + }, + { + "id": "544a6e08-a899-4f63-9c72-bb2924df70cb", + "name": "Sacher Torte Cake", + "slug": "sacher-torte-cake" + }, + { + "id": "576c0a82-84ee-4e50-a14e-aa7a675b6352", + "name": "Sacher Torte", + "slug": "sacher-torte" + }, + { + "id": "d530b8e4-275a-4093-804b-6d0de154c206", + "name": "Original Sachertorte", + "slug": "original-sachertorte" + } + ], + "tools": [], + "rating": null, + "orgURL": "https://www.sacher.com/en/original-sacher-torte/recipe/", + "dateAdded": "2024-06-29", + "dateUpdated": "2024-06-29T06:10:34.412665", + "createdAt": "2024-06-29T06:10:34.414927", + "updateAt": "2024-06-29T06:10:34.414928", + "lastMade": null, + "recipeIngredient": [ + { + "quantity": 1.0, + "unit": null, + "food": null, + "note": "130g dark couverture chocolate (min. 55% cocoa content)", + "isFood": true, + "disableAmount": false, + "display": "1 130g dark couverture chocolate (min. 55% cocoa content)", + "title": null, + "originalText": null, + "referenceId": "a3adfe78-d157-44d8-98be-9c133e45bb4e" + }, + { + "quantity": 1.0, + "unit": null, + "food": null, + "note": "1 Vanilla Pod", + "isFood": true, + "disableAmount": false, + "display": "1 1 Vanilla Pod", + "title": null, + "originalText": null, + "referenceId": "41d234d7-c040-48f9-91e6-f4636aebb77b" + }, + { + "quantity": 1.0, + "unit": null, + "food": null, + "note": "150g softened butter", + "isFood": true, + "disableAmount": false, + "display": "1 150g softened butter", + "title": null, + "originalText": null, + "referenceId": "f6ce06bf-8b02-43e6-8316-0dc3fb0da0fc" + }, + { + "quantity": 1.0, + "unit": null, + "food": null, + "note": "100g Icing sugar", + "isFood": true, + "disableAmount": false, + "display": "1 100g Icing sugar", + "title": null, + "originalText": null, + "referenceId": "f7fcd86e-b04b-4e07-b69c-513925811491" + }, + { + "quantity": 1.0, + "unit": null, + "food": null, + "note": "6 Eggs", + "isFood": true, + "disableAmount": false, + "display": "1 6 Eggs", + "title": null, + "originalText": null, + "referenceId": "a831fbc3-e2f5-452e-a745-450be8b4a130" + }, + { + "quantity": 1.0, + "unit": null, + "food": null, + "note": "100g Castor sugar", + "isFood": true, + "disableAmount": false, + "display": "1 100g Castor sugar", + "title": null, + "originalText": null, + "referenceId": "b5ee4bdc-0047-4de7-968b-f3360bbcb31e" + }, + { + "quantity": 1.0, + "unit": null, + "food": null, + "note": "140g Plain wheat flour", + "isFood": true, + "disableAmount": false, + "display": "1 140g Plain wheat flour", + "title": null, + "originalText": null, + "referenceId": "a67db09d-429c-4e77-919d-cfed3da675ad" + }, + { + "quantity": 1.0, + "unit": null, + "food": null, + "note": "200g apricot jam", + "isFood": true, + "disableAmount": false, + "display": "1 200g apricot jam", + "title": null, + "originalText": null, + "referenceId": "55479752-c062-4b25-aae3-2b210999d7b9" + }, + { + "quantity": 1.0, + "unit": null, + "food": null, + "note": "200g castor sugar", + "isFood": true, + "disableAmount": false, + "display": "1 200g castor sugar", + "title": null, + "originalText": null, + "referenceId": "ff9cd404-24ec-4d38-b0aa-0120ce1df679" + }, + { + "quantity": 1.0, + "unit": null, + "food": null, + "note": "150g dark couverture chocolate (min. 55% cocoa content)", + "isFood": true, + "disableAmount": false, + "display": "1 150g dark couverture chocolate (min. 55% cocoa content)", + "title": null, + "originalText": null, + "referenceId": "c7fca92e-971e-4728-a227-8b04783583ed" + }, + { + "quantity": 1.0, + "unit": null, + "food": null, + "note": "Unsweetend whipped cream to garnish", + "isFood": true, + "disableAmount": false, + "display": "1 Unsweetend whipped cream to garnish", + "title": null, + "originalText": null, + "referenceId": "ef023f23-7816-4871-87f6-4d29f9a283f7" + } + ], + "recipeInstructions": [ + { + "id": "2d558dbf-5361-4ef2-9d86-4161f5eb6146", + "title": "", + "text": "Preheat oven to 170°C. Line the base of a springform with baking paper, grease the sides, and dust with a little flour. Melt couverture over boiling water. Let cool slightly.", + "ingredientReferences": [] + }, + { + "id": "dbcc1c37-3cbf-4045-9902-8f7fd1e68f0a", + "title": "", + "text": "Slit vanilla pod lengthwise and scrape out seeds. Using a hand mixer with whisks, beat the softened butter with the icing sugar and vanilla seeds until bubbles appear.", + "ingredientReferences": [] + }, + { + "id": "2265bd14-a691-40b1-9fe6-7b5dfeac8401", + "title": "", + "text": "Separate the eggs. Whisk the egg yolks into the butter mixture one by one. Now gradually add melted couverture chocolate. Beat the egg whites with the castor sugar until stiff, then place on top of the butter and chocolate mixture. Sift the flour over the mixture, then fold in the flour and beaten egg whites.", + "ingredientReferences": [] + }, + { + "id": "0aade447-dfac-4aae-8e67-ac250ad13ae2", + "title": "", + "text": "Transfer the mixture to the springform, smooth the top, and bake in the oven (middle rack) for 10–15 minutes, leaving the oven door a finger's width ajar. Then close the oven and bake for approximately 50 minutes. (The cake is done when it yields slightly to the touch.)", + "ingredientReferences": [] + }, + { + "id": "5fdcb703-7103-468d-a65d-a92460b92eb3", + "title": "", + "text": "Remove the cake from the oven and loosen the sides of the springform. Carefully tip the cake onto a cake rack lined with baking paper and let cool for approximately 20 minutes. Then pull off the baking paper, turn the cake over, and leave on rack to cool completely.", + "ingredientReferences": [] + }, + { + "id": "81474afc-b44e-49b3-bb67-5d7dab8f832a", + "title": "", + "text": "Cut the cake in half horizontally. Warm the jam and stir until smooth. Brush the top of both cake halves with the jam and place one on top of the other. Brush the sides with the jam as well.", + "ingredientReferences": [] + }, + { + "id": "8fac8aee-0d3c-4f78-9ff8-56d20472e5f1", + "title": "", + "text": "To make the glaze, put the castor sugar into a saucepan with 125 ml water and boil over high heat for approximately 5 minutes. Take the sugar syrup off the stove and leave to cool a little. Coarsely chop the couverture, gradually adding it to the syrup, and stir until it forms a thick liquid (see tip below).", + "ingredientReferences": [] + }, + { + "id": "7162e099-d651-4656-902a-a09a9b40c4e1", + "title": "", + "text": "Pour all the lukewarm glaze liquid at once over the top of the cake and quickly spread using a palette knife. Leave the glaze to set for a few hours. Serve garnished with whipped cream.", + "ingredientReferences": [] + } + ], + "nutrition": { + "calories": "400", + "fatContent": "17", + "proteinContent": null, + "carbohydrateContent": null, + "fiberContent": null, + "sodiumContent": null, + "sugarContent": null + }, + "settings": { + "public": true, + "showNutrition": true, + "showAssets": true, + "landscapeView": false, + "disableComments": false, + "disableAmount": false, + "locked": false + }, + "assets": [], + "notes": [], + "extras": {}, + "comments": [] +} diff --git a/tests/components/mealie/snapshots/test_services.ambr b/tests/components/mealie/snapshots/test_services.ambr index 7f0070711ef..0dc7e7ab1c3 100644 --- a/tests/components/mealie/snapshots/test_services.ambr +++ b/tests/components/mealie/snapshots/test_services.ambr @@ -295,3 +295,193 @@ ]), }) # --- +# name: test_service_recipe + dict({ + 'recipe': dict({ + 'date_added': datetime.date(2024, 6, 29), + 'description': 'The world’s most famous cake, the Original Sacher-Torte, is the consequence of several lucky twists of fate. The first was in 1832, when the Austrian State Chancellor, Prince Klemens Wenzel von Metternich, tasked his kitchen staff with concocting an extraordinary dessert to impress his special guests. As fortune had it, the chef had fallen ill that evening, leaving the apprentice chef, the then-16-year-old Franz Sacher, to perform this culinary magic trick. Metternich’s parting words to the talented teenager: “I hope you won’t disgrace me tonight.”', + 'group_id': '24477569-f6af-4b53-9e3f-6d04b0ca6916', + 'image': 'SuPW', + 'ingredients': list([ + dict({ + 'is_food': True, + 'note': '130g dark couverture chocolate (min. 55% cocoa content)', + 'quantity': 1.0, + 'reference_id': 'a3adfe78-d157-44d8-98be-9c133e45bb4e', + 'unit': None, + }), + dict({ + 'is_food': True, + 'note': '1 Vanilla Pod', + 'quantity': 1.0, + 'reference_id': '41d234d7-c040-48f9-91e6-f4636aebb77b', + 'unit': None, + }), + dict({ + 'is_food': True, + 'note': '150g softened butter', + 'quantity': 1.0, + 'reference_id': 'f6ce06bf-8b02-43e6-8316-0dc3fb0da0fc', + 'unit': None, + }), + dict({ + 'is_food': True, + 'note': '100g Icing sugar', + 'quantity': 1.0, + 'reference_id': 'f7fcd86e-b04b-4e07-b69c-513925811491', + 'unit': None, + }), + dict({ + 'is_food': True, + 'note': '6 Eggs', + 'quantity': 1.0, + 'reference_id': 'a831fbc3-e2f5-452e-a745-450be8b4a130', + 'unit': None, + }), + dict({ + 'is_food': True, + 'note': '100g Castor sugar', + 'quantity': 1.0, + 'reference_id': 'b5ee4bdc-0047-4de7-968b-f3360bbcb31e', + 'unit': None, + }), + dict({ + 'is_food': True, + 'note': '140g Plain wheat flour', + 'quantity': 1.0, + 'reference_id': 'a67db09d-429c-4e77-919d-cfed3da675ad', + 'unit': None, + }), + dict({ + 'is_food': True, + 'note': '200g apricot jam', + 'quantity': 1.0, + 'reference_id': '55479752-c062-4b25-aae3-2b210999d7b9', + 'unit': None, + }), + dict({ + 'is_food': True, + 'note': '200g castor sugar', + 'quantity': 1.0, + 'reference_id': 'ff9cd404-24ec-4d38-b0aa-0120ce1df679', + 'unit': None, + }), + dict({ + 'is_food': True, + 'note': '150g dark couverture chocolate (min. 55% cocoa content)', + 'quantity': 1.0, + 'reference_id': 'c7fca92e-971e-4728-a227-8b04783583ed', + 'unit': None, + }), + dict({ + 'is_food': True, + 'note': 'Unsweetend whipped cream to garnish', + 'quantity': 1.0, + 'reference_id': 'ef023f23-7816-4871-87f6-4d29f9a283f7', + 'unit': None, + }), + ]), + 'instructions': list([ + dict({ + 'ingredient_references': list([ + ]), + 'instruction_id': '2d558dbf-5361-4ef2-9d86-4161f5eb6146', + 'text': 'Preheat oven to 170°C. Line the base of a springform with baking paper, grease the sides, and dust with a little flour. Melt couverture over boiling water. Let cool slightly.', + 'title': None, + }), + dict({ + 'ingredient_references': list([ + ]), + 'instruction_id': 'dbcc1c37-3cbf-4045-9902-8f7fd1e68f0a', + 'text': 'Slit vanilla pod lengthwise and scrape out seeds. Using a hand mixer with whisks, beat the softened butter with the icing sugar and vanilla seeds until bubbles appear.', + 'title': None, + }), + dict({ + 'ingredient_references': list([ + ]), + 'instruction_id': '2265bd14-a691-40b1-9fe6-7b5dfeac8401', + 'text': 'Separate the eggs. Whisk the egg yolks into the butter mixture one by one. Now gradually add melted couverture chocolate. Beat the egg whites with the castor sugar until stiff, then place on top of the butter and chocolate mixture. Sift the flour over the mixture, then fold in the flour and beaten egg whites.', + 'title': None, + }), + dict({ + 'ingredient_references': list([ + ]), + 'instruction_id': '0aade447-dfac-4aae-8e67-ac250ad13ae2', + 'text': "Transfer the mixture to the springform, smooth the top, and bake in the oven (middle rack) for 10–15 minutes, leaving the oven door a finger's width ajar. Then close the oven and bake for approximately 50 minutes. (The cake is done when it yields slightly to the touch.)", + 'title': None, + }), + dict({ + 'ingredient_references': list([ + ]), + 'instruction_id': '5fdcb703-7103-468d-a65d-a92460b92eb3', + 'text': 'Remove the cake from the oven and loosen the sides of the springform. Carefully tip the cake onto a cake rack lined with baking paper and let cool for approximately 20 minutes. Then pull off the baking paper, turn the cake over, and leave on rack to cool completely.', + 'title': None, + }), + dict({ + 'ingredient_references': list([ + ]), + 'instruction_id': '81474afc-b44e-49b3-bb67-5d7dab8f832a', + 'text': 'Cut the cake in half horizontally. Warm the jam and stir until smooth. Brush the top of both cake halves with the jam and place one on top of the other. Brush the sides with the jam as well.', + 'title': None, + }), + dict({ + 'ingredient_references': list([ + ]), + 'instruction_id': '8fac8aee-0d3c-4f78-9ff8-56d20472e5f1', + 'text': 'To make the glaze, put the castor sugar into a saucepan with 125 ml water and boil over high heat for approximately 5 minutes. Take the sugar syrup off the stove and leave to cool a little. Coarsely chop the couverture, gradually adding it to the syrup, and stir until it forms a thick liquid (see tip below).', + 'title': None, + }), + dict({ + 'ingredient_references': list([ + ]), + 'instruction_id': '7162e099-d651-4656-902a-a09a9b40c4e1', + 'text': 'Pour all the lukewarm glaze liquid at once over the top of the cake and quickly spread using a palette knife. Leave the glaze to set for a few hours. Serve garnished with whipped cream.', + 'title': None, + }), + ]), + 'name': 'Original Sacher-Torte (2)', + 'original_url': 'https://www.sacher.com/en/original-sacher-torte/recipe/', + 'recipe_id': 'fada9582-709b-46aa-b384-d5952123ad93', + 'recipe_yield': '4 servings', + 'slug': 'original-sacher-torte-2', + 'tags': list([ + dict({ + 'name': 'Sacher', + 'slug': 'sacher', + 'tag_id': '1b5789b9-3af6-412e-8c77-8a01caa0aac9', + }), + dict({ + 'name': 'Cake', + 'slug': 'cake', + 'tag_id': '1cf17f96-58b5-4bd3-b1e8-1606a64b413d', + }), + dict({ + 'name': 'Torte', + 'slug': 'torte', + 'tag_id': '3f5f0a3d-728f-440d-a6c7-5a68612e8c67', + }), + dict({ + 'name': 'Sachertorte', + 'slug': 'sachertorte', + 'tag_id': '525f388d-6ee0-4ebe-91fc-dd320a7583f0', + }), + dict({ + 'name': 'Sacher Torte Cake', + 'slug': 'sacher-torte-cake', + 'tag_id': '544a6e08-a899-4f63-9c72-bb2924df70cb', + }), + dict({ + 'name': 'Sacher Torte', + 'slug': 'sacher-torte', + 'tag_id': '576c0a82-84ee-4e50-a14e-aa7a675b6352', + }), + dict({ + 'name': 'Original Sachertorte', + 'slug': 'original-sachertorte', + 'tag_id': 'd530b8e4-275a-4093-804b-6d0de154c206', + }), + ]), + 'user_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0', + }), + }) +# --- diff --git a/tests/components/mealie/test_services.py b/tests/components/mealie/test_services.py index c5e31a168f1..fbc56eff9d7 100644 --- a/tests/components/mealie/test_services.py +++ b/tests/components/mealie/test_services.py @@ -3,6 +3,7 @@ from datetime import date from unittest.mock import AsyncMock +from aiomealie.exceptions import MealieNotFoundError from freezegun.api import FrozenDateTimeFactory import pytest from syrupy import SnapshotAssertion @@ -10,10 +11,14 @@ from syrupy import SnapshotAssertion from homeassistant.components.mealie.const import ( ATTR_CONFIG_ENTRY_ID, ATTR_END_DATE, + ATTR_RECIPE_ID, ATTR_START_DATE, DOMAIN, ) -from homeassistant.components.mealie.services import SERVICE_GET_MEALPLAN +from homeassistant.components.mealie.services import ( + SERVICE_GET_MEALPLAN, + SERVICE_GET_RECIPE, +) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceValidationError @@ -111,6 +116,50 @@ async def test_service_mealplan( ) +async def test_service_recipe( + hass: HomeAssistant, + mock_mealie_client: AsyncMock, + mock_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, +) -> None: + """Test the get_recipe service.""" + + await setup_integration(hass, mock_config_entry) + + response = await hass.services.async_call( + DOMAIN, + SERVICE_GET_RECIPE, + {ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id, ATTR_RECIPE_ID: "recipe_id"}, + blocking=True, + return_response=True, + ) + assert response == snapshot + + +async def test_service_recipe_not_found( + hass: HomeAssistant, + mock_mealie_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the get_recipe service.""" + + await setup_integration(hass, mock_config_entry) + + mock_mealie_client.get_recipe.side_effect = MealieNotFoundError + + with pytest.raises(ServiceValidationError): + await hass.services.async_call( + DOMAIN, + SERVICE_GET_RECIPE, + { + ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id, + ATTR_RECIPE_ID: "recipe_id", + }, + blocking=True, + return_response=True, + ) + + async def test_service_mealplan_without_entry( hass: HomeAssistant, mock_config_entry: MockConfigEntry,