Add Mealie service to set a random mealplan (#122313)
* Add Mealie service to set a random mealplan * Fix coverage * Fix coveragepull/122316/head
parent
39068bb786
commit
6f4a8a4a14
|
@ -14,5 +14,6 @@ ATTR_END_DATE = "end_date"
|
|||
ATTR_RECIPE_ID = "recipe_id"
|
||||
ATTR_URL = "url"
|
||||
ATTR_INCLUDE_TAGS = "include_tags"
|
||||
ATTR_ENTRY_TYPE = "entry_type"
|
||||
|
||||
MIN_REQUIRED_MEALIE_VERSION = AwesomeVersion("v1.0.0")
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
"services": {
|
||||
"get_mealplan": "mdi:food",
|
||||
"get_recipe": "mdi:map",
|
||||
"import_recipe": "mdi:map-search"
|
||||
"import_recipe": "mdi:map-search",
|
||||
"set_random_mealplan": "mdi:dice-multiple"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,16 @@ from dataclasses import asdict
|
|||
from datetime import date
|
||||
from typing import cast
|
||||
|
||||
from aiomealie import MealieConnectionError, MealieNotFoundError, MealieValidationError
|
||||
from aiomealie import (
|
||||
MealieConnectionError,
|
||||
MealieNotFoundError,
|
||||
MealieValidationError,
|
||||
MealplanEntryType,
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import ATTR_DATE
|
||||
from homeassistant.core import (
|
||||
HomeAssistant,
|
||||
ServiceCall,
|
||||
|
@ -20,6 +26,7 @@ from homeassistant.helpers import config_validation as cv
|
|||
from .const import (
|
||||
ATTR_CONFIG_ENTRY_ID,
|
||||
ATTR_END_DATE,
|
||||
ATTR_ENTRY_TYPE,
|
||||
ATTR_INCLUDE_TAGS,
|
||||
ATTR_RECIPE_ID,
|
||||
ATTR_START_DATE,
|
||||
|
@ -54,6 +61,15 @@ SERVICE_IMPORT_RECIPE_SCHEMA = vol.Schema(
|
|||
}
|
||||
)
|
||||
|
||||
SERVICE_SET_RANDOM_MEALPLAN = "set_random_mealplan"
|
||||
SERVICE_SET_RANDOM_MEALPLAN_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_CONFIG_ENTRY_ID): str,
|
||||
vol.Required(ATTR_DATE): cv.date,
|
||||
vol.Required(ATTR_ENTRY_TYPE): vol.In([x.lower() for x in MealplanEntryType]),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def async_get_entry(hass: HomeAssistant, config_entry_id: str) -> MealieConfigEntry:
|
||||
"""Get the Mealie config entry."""
|
||||
|
@ -137,6 +153,23 @@ def setup_services(hass: HomeAssistant) -> None:
|
|||
return {"recipe": asdict(recipe)}
|
||||
return None
|
||||
|
||||
async def async_set_random_mealplan(call: ServiceCall) -> ServiceResponse:
|
||||
"""Set a random mealplan."""
|
||||
entry = async_get_entry(hass, call.data[ATTR_CONFIG_ENTRY_ID])
|
||||
mealplan_date = call.data[ATTR_DATE]
|
||||
entry_type = MealplanEntryType(call.data[ATTR_ENTRY_TYPE])
|
||||
client = entry.runtime_data.client
|
||||
try:
|
||||
mealplan = await client.random_mealplan(mealplan_date, entry_type)
|
||||
except MealieConnectionError as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="connection_error",
|
||||
) from err
|
||||
if call.return_response:
|
||||
return {"mealplan": asdict(mealplan)}
|
||||
return None
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_GET_MEALPLAN,
|
||||
|
@ -158,3 +191,10 @@ def setup_services(hass: HomeAssistant) -> None:
|
|||
schema=SERVICE_IMPORT_RECIPE_SCHEMA,
|
||||
supports_response=SupportsResponse.OPTIONAL,
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_SET_RANDOM_MEALPLAN,
|
||||
async_set_random_mealplan,
|
||||
schema=SERVICE_SET_RANDOM_MEALPLAN_SCHEMA,
|
||||
supports_response=SupportsResponse.OPTIONAL,
|
||||
)
|
||||
|
|
|
@ -11,6 +11,7 @@ get_mealplan:
|
|||
end_date:
|
||||
selector:
|
||||
date:
|
||||
|
||||
get_recipe:
|
||||
fields:
|
||||
config_entry_id:
|
||||
|
@ -22,6 +23,7 @@ get_recipe:
|
|||
required: true
|
||||
selector:
|
||||
text:
|
||||
|
||||
import_recipe:
|
||||
fields:
|
||||
config_entry_id:
|
||||
|
@ -36,3 +38,23 @@ import_recipe:
|
|||
include_tags:
|
||||
selector:
|
||||
boolean:
|
||||
|
||||
set_random_mealplan:
|
||||
fields:
|
||||
config_entry_id:
|
||||
required: true
|
||||
selector:
|
||||
config_entry:
|
||||
integration: mealie
|
||||
date:
|
||||
selector:
|
||||
date:
|
||||
entry_type:
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- breakfast
|
||||
- lunch
|
||||
- dinner
|
||||
- side
|
||||
translation_key: mealplan_entry_type
|
||||
|
|
|
@ -157,6 +157,34 @@
|
|||
"description": "Include tags from the website to the recipe."
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_random_mealplan": {
|
||||
"name": "Set random mealplan",
|
||||
"description": "Set a random mealplan for a specific date",
|
||||
"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%]"
|
||||
},
|
||||
"date": {
|
||||
"name": "Date",
|
||||
"description": "The date to set the mealplan for."
|
||||
},
|
||||
"entry_type": {
|
||||
"name": "Entry type",
|
||||
"description": "The type of dish to randomize."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"mealplan_entry_type": {
|
||||
"options": {
|
||||
"breakfast": "[%key:component::mealie::entity::calendar::breakfast::name%]",
|
||||
"lunch": "[%key:component::mealie::entity::calendar::lunch::name%]",
|
||||
"dinner": "[%key:component::mealie::entity::calendar::dinner::name%]",
|
||||
"side": "[%key:component::mealie::entity::calendar::side::name%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,6 +74,9 @@ def mock_mealie_client() -> Generator[AsyncMock]:
|
|||
client.get_statistics.return_value = Statistics.from_json(
|
||||
load_fixture("statistics.json", DOMAIN)
|
||||
)
|
||||
client.random_mealplan.return_value = Mealplan.from_json(
|
||||
load_fixture("mealplan.json", DOMAIN)
|
||||
)
|
||||
yield client
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"date": "2024-01-22",
|
||||
"entryType": "dinner",
|
||||
"title": "",
|
||||
"text": "",
|
||||
"recipeId": "c5f00a93-71a2-4e48-900f-d9ad0bb9de93",
|
||||
"id": 230,
|
||||
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
|
||||
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
|
||||
"recipe": {
|
||||
"id": "c5f00a93-71a2-4e48-900f-d9ad0bb9de93",
|
||||
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
|
||||
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
|
||||
"name": "Zoete aardappel curry traybake",
|
||||
"slug": "zoete-aardappel-curry-traybake",
|
||||
"image": "AiIo",
|
||||
"recipeYield": "2 servings",
|
||||
"totalTime": "40 Minutes",
|
||||
"prepTime": null,
|
||||
"cookTime": null,
|
||||
"performTime": null,
|
||||
"description": "Een traybake is eigenlijk altijd een goed idee. Deze zoete aardappel curry traybake dus ook. Waarom? Omdat je alleen maar wat groenten - en in dit geval kip - op een bakplaat (traybake dus) legt, hier wat kruiden aan toevoegt en deze in de oven schuift. Ideaal dus als je geen zin hebt om lang in de keuken te staan. Maar gewoon lekker op de bank wil ploffen om te wachten tot de oven klaar is. Joe! That\\'s what we like. Deze zoete aardappel curry traybake bevat behalve zoete aardappel en curry ook kikkererwten, kippendijfilet en bloemkoolroosjes. Je gebruikt yoghurt en limoen als een soort dressing. En je serveert deze heerlijke traybake met naanbrood. Je kunt natuurljk ook voor deze traybake met chipolataworstjes gaan. Wil je graag meer ovengerechten? Dan moet je eigenlijk even kijken naar onze Ovenbijbel. Onmisbaar in je keuken! We willen je deze zoete aardappelstamppot met prei ook niet onthouden. Megalekker bordje comfortfood als je \\'t ons vraagt.",
|
||||
"recipeCategory": [],
|
||||
"tags": [],
|
||||
"tools": [],
|
||||
"rating": null,
|
||||
"orgURL": "https://chickslovefood.com/recept/zoete-aardappel-curry-traybake/",
|
||||
"dateAdded": "2024-01-22",
|
||||
"dateUpdated": "2024-01-22T00:27:46.324512",
|
||||
"createdAt": "2024-01-22T00:27:46.327546",
|
||||
"updateAt": "2024-01-22T00:27:46.327548",
|
||||
"lastMade": null
|
||||
}
|
||||
}
|
|
@ -675,3 +675,27 @@
|
|||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_service_set_random_mealplan
|
||||
dict({
|
||||
'mealplan': dict({
|
||||
'description': None,
|
||||
'entry_type': <MealplanEntryType.DINNER: 'dinner'>,
|
||||
'group_id': '0bf60b2e-ca89-42a9-94d4-8f67ca72b157',
|
||||
'mealplan_date': datetime.date(2024, 1, 22),
|
||||
'mealplan_id': 230,
|
||||
'recipe': dict({
|
||||
'description': "Een traybake is eigenlijk altijd een goed idee. Deze zoete aardappel curry traybake dus ook. Waarom? Omdat je alleen maar wat groenten - en in dit geval kip - op een bakplaat (traybake dus) legt, hier wat kruiden aan toevoegt en deze in de oven schuift. Ideaal dus als je geen zin hebt om lang in de keuken te staan. Maar gewoon lekker op de bank wil ploffen om te wachten tot de oven klaar is. Joe! That\\'s what we like. Deze zoete aardappel curry traybake bevat behalve zoete aardappel en curry ook kikkererwten, kippendijfilet en bloemkoolroosjes. Je gebruikt yoghurt en limoen als een soort dressing. En je serveert deze heerlijke traybake met naanbrood. Je kunt natuurljk ook voor deze traybake met chipolataworstjes gaan. Wil je graag meer ovengerechten? Dan moet je eigenlijk even kijken naar onze Ovenbijbel. Onmisbaar in je keuken! We willen je deze zoete aardappelstamppot met prei ook niet onthouden. Megalekker bordje comfortfood als je \\'t ons vraagt.",
|
||||
'group_id': '0bf60b2e-ca89-42a9-94d4-8f67ca72b157',
|
||||
'image': 'AiIo',
|
||||
'name': 'Zoete aardappel curry traybake',
|
||||
'original_url': 'https://chickslovefood.com/recept/zoete-aardappel-curry-traybake/',
|
||||
'recipe_id': 'c5f00a93-71a2-4e48-900f-d9ad0bb9de93',
|
||||
'recipe_yield': '2 servings',
|
||||
'slug': 'zoete-aardappel-curry-traybake',
|
||||
'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d',
|
||||
}),
|
||||
'title': None,
|
||||
'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d',
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
|
|
|
@ -3,7 +3,12 @@
|
|||
from datetime import date
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from aiomealie import MealieConnectionError, MealieNotFoundError, MealieValidationError
|
||||
from aiomealie import (
|
||||
MealieConnectionError,
|
||||
MealieNotFoundError,
|
||||
MealieValidationError,
|
||||
MealplanEntryType,
|
||||
)
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
@ -11,6 +16,7 @@ from syrupy import SnapshotAssertion
|
|||
from homeassistant.components.mealie.const import (
|
||||
ATTR_CONFIG_ENTRY_ID,
|
||||
ATTR_END_DATE,
|
||||
ATTR_ENTRY_TYPE,
|
||||
ATTR_INCLUDE_TAGS,
|
||||
ATTR_RECIPE_ID,
|
||||
ATTR_START_DATE,
|
||||
|
@ -21,7 +27,9 @@ from homeassistant.components.mealie.services import (
|
|||
SERVICE_GET_MEALPLAN,
|
||||
SERVICE_GET_RECIPE,
|
||||
SERVICE_IMPORT_RECIPE,
|
||||
SERVICE_SET_RANDOM_MEALPLAN,
|
||||
)
|
||||
from homeassistant.const import ATTR_DATE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
|
||||
|
@ -246,6 +254,74 @@ async def test_service_import_recipe_exceptions(
|
|||
)
|
||||
|
||||
|
||||
async def test_service_set_random_mealplan(
|
||||
hass: HomeAssistant,
|
||||
mock_mealie_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test the set_random_mealplan service."""
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
response = await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_RANDOM_MEALPLAN,
|
||||
{
|
||||
ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
|
||||
ATTR_DATE: "2023-10-21",
|
||||
ATTR_ENTRY_TYPE: "lunch",
|
||||
},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
assert response == snapshot
|
||||
mock_mealie_client.random_mealplan.assert_called_with(
|
||||
date(2023, 10, 21), MealplanEntryType.LUNCH
|
||||
)
|
||||
|
||||
mock_mealie_client.random_mealplan.reset_mock()
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_RANDOM_MEALPLAN,
|
||||
{
|
||||
ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
|
||||
ATTR_DATE: "2023-10-21",
|
||||
ATTR_ENTRY_TYPE: "lunch",
|
||||
},
|
||||
blocking=True,
|
||||
return_response=False,
|
||||
)
|
||||
mock_mealie_client.random_mealplan.assert_called_with(
|
||||
date(2023, 10, 21), MealplanEntryType.LUNCH
|
||||
)
|
||||
|
||||
|
||||
async def test_service_set_random_mealplan_exceptions(
|
||||
hass: HomeAssistant,
|
||||
mock_mealie_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the exceptions of the set_random_mealplan service."""
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
mock_mealie_client.random_mealplan.side_effect = MealieConnectionError
|
||||
|
||||
with pytest.raises(HomeAssistantError, match="Error connecting to Mealie instance"):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_RANDOM_MEALPLAN,
|
||||
{
|
||||
ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
|
||||
ATTR_DATE: "2023-10-21",
|
||||
ATTR_ENTRY_TYPE: "lunch",
|
||||
},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_service_mealplan_connection_error(
|
||||
hass: HomeAssistant,
|
||||
mock_mealie_client: AsyncMock,
|
||||
|
|
Loading…
Reference in New Issue