Add Mealie service to get recipe (#121462)
This commit is contained in:
parent
93cf8c1311
commit
5f088e0501
7 changed files with 584 additions and 19 deletions
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
266
tests/components/mealie/fixtures/get_recipe.json
Normal file
266
tests/components/mealie/fixtures/get_recipe.json
Normal file
|
@ -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": []
|
||||
}
|
|
@ -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',
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Reference in a new issue