Add mealie service to set mealplan (#122317)

This commit is contained in:
Joost Lekkerkerker 2024-07-22 15:34:10 +02:00 committed by GitHub
parent debebcfd25
commit 7ec41275d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 259 additions and 4 deletions

View File

@ -15,5 +15,7 @@ ATTR_RECIPE_ID = "recipe_id"
ATTR_URL = "url"
ATTR_INCLUDE_TAGS = "include_tags"
ATTR_ENTRY_TYPE = "entry_type"
ATTR_NOTE_TITLE = "note_title"
ATTR_NOTE_TEXT = "note_text"
MIN_REQUIRED_MEALIE_VERSION = AwesomeVersion("v1.0.0")

View File

@ -27,6 +27,7 @@
"get_mealplan": "mdi:food",
"get_recipe": "mdi:map",
"import_recipe": "mdi:map-search",
"set_random_mealplan": "mdi:dice-multiple"
"set_random_mealplan": "mdi:dice-multiple",
"set_mealplan": "mdi:food"
}
}

View File

@ -28,6 +28,8 @@ from .const import (
ATTR_END_DATE,
ATTR_ENTRY_TYPE,
ATTR_INCLUDE_TAGS,
ATTR_NOTE_TEXT,
ATTR_NOTE_TITLE,
ATTR_RECIPE_ID,
ATTR_START_DATE,
ATTR_URL,
@ -70,6 +72,31 @@ SERVICE_SET_RANDOM_MEALPLAN_SCHEMA = vol.Schema(
}
)
SERVICE_SET_MEALPLAN = "set_mealplan"
SERVICE_SET_MEALPLAN_SCHEMA = vol.Any(
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]
),
vol.Required(ATTR_RECIPE_ID): str,
}
),
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]
),
vol.Required(ATTR_NOTE_TITLE): str,
vol.Required(ATTR_NOTE_TEXT): str,
}
),
)
def async_get_entry(hass: HomeAssistant, config_entry_id: str) -> MealieConfigEntry:
"""Get the Mealie config entry."""
@ -170,6 +197,29 @@ def setup_services(hass: HomeAssistant) -> None:
return {"mealplan": asdict(mealplan)}
return None
async def async_set_mealplan(call: ServiceCall) -> ServiceResponse:
"""Set a 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.set_mealplan(
mealplan_date,
entry_type,
recipe_id=call.data.get(ATTR_RECIPE_ID),
note_title=call.data.get(ATTR_NOTE_TITLE),
note_text=call.data.get(ATTR_NOTE_TEXT),
)
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,
@ -198,3 +248,10 @@ def setup_services(hass: HomeAssistant) -> None:
schema=SERVICE_SET_RANDOM_MEALPLAN_SCHEMA,
supports_response=SupportsResponse.OPTIONAL,
)
hass.services.async_register(
DOMAIN,
SERVICE_SET_MEALPLAN,
async_set_mealplan,
schema=SERVICE_SET_MEALPLAN_SCHEMA,
supports_response=SupportsResponse.OPTIONAL,
)

View File

@ -58,3 +58,32 @@ set_random_mealplan:
- dinner
- side
translation_key: mealplan_entry_type
set_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
recipe_id:
selector:
text:
note_title:
selector:
text:
note_text:
selector:
text:

View File

@ -175,6 +175,36 @@
"description": "The type of dish to randomize."
}
}
},
"set_mealplan": {
"name": "Set a mealplan",
"description": "Set a 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": "[%key:component::mealie::services::set_random_mealplan::fields::date::name%]",
"description": "[%key:component::mealie::services::set_random_mealplan::fields::date::description%]"
},
"entry_type": {
"name": "[%key:component::mealie::services::set_random_mealplan::fields::entry_type::name%]",
"description": "The type of dish to set the recipe to."
},
"recipe_id": {
"name": "[%key:component::mealie::services::get_recipe::fields::recipe_id::name%]",
"description": "[%key:component::mealie::services::get_recipe::fields::recipe_id::description%]"
},
"note_title": {
"name": "Meal note title",
"description": "Meal note title for when planning without recipe."
},
"note_text": {
"name": "Note text",
"description": "Meal note text for when planning without recipe."
}
}
}
},
"selector": {

View File

@ -74,9 +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)
)
mealplan = Mealplan.from_json(load_fixture("mealplan.json", DOMAIN))
client.random_mealplan.return_value = mealplan
client.set_mealplan.return_value = mealplan
yield client

View File

@ -675,6 +675,54 @@
}),
})
# ---
# name: test_service_set_mealplan[payload0-kwargs0]
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',
}),
})
# ---
# name: test_service_set_mealplan[payload1-kwargs1]
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',
}),
})
# ---
# name: test_service_set_random_mealplan
dict({
'mealplan': dict({

View File

@ -18,6 +18,8 @@ from homeassistant.components.mealie.const import (
ATTR_END_DATE,
ATTR_ENTRY_TYPE,
ATTR_INCLUDE_TAGS,
ATTR_NOTE_TEXT,
ATTR_NOTE_TITLE,
ATTR_RECIPE_ID,
ATTR_START_DATE,
ATTR_URL,
@ -27,6 +29,7 @@ from homeassistant.components.mealie.services import (
SERVICE_GET_MEALPLAN,
SERVICE_GET_RECIPE,
SERVICE_IMPORT_RECIPE,
SERVICE_SET_MEALPLAN,
SERVICE_SET_RANDOM_MEALPLAN,
)
from homeassistant.const import ATTR_DATE
@ -231,6 +234,71 @@ async def test_service_set_random_mealplan(
)
@pytest.mark.parametrize(
("payload", "kwargs"),
[
(
{
ATTR_RECIPE_ID: "recipe_id",
},
{"recipe_id": "recipe_id", "note_title": None, "note_text": None},
),
(
{
ATTR_NOTE_TITLE: "Note Title",
ATTR_NOTE_TEXT: "Note Text",
},
{"recipe_id": None, "note_title": "Note Title", "note_text": "Note Text"},
),
],
)
async def test_service_set_mealplan(
hass: HomeAssistant,
mock_mealie_client: AsyncMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
payload: dict[str, str],
kwargs: dict[str, str],
) -> None:
"""Test the set_mealplan service."""
await setup_integration(hass, mock_config_entry)
response = await hass.services.async_call(
DOMAIN,
SERVICE_SET_MEALPLAN,
{
ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
ATTR_DATE: "2023-10-21",
ATTR_ENTRY_TYPE: "lunch",
}
| payload,
blocking=True,
return_response=True,
)
assert response == snapshot
mock_mealie_client.set_mealplan.assert_called_with(
date(2023, 10, 21), MealplanEntryType.LUNCH, **kwargs
)
mock_mealie_client.random_mealplan.reset_mock()
await hass.services.async_call(
DOMAIN,
SERVICE_SET_MEALPLAN,
{
ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
ATTR_DATE: "2023-10-21",
ATTR_ENTRY_TYPE: "lunch",
}
| payload,
blocking=True,
return_response=False,
)
mock_mealie_client.set_mealplan.assert_called_with(
date(2023, 10, 21), MealplanEntryType.LUNCH, **kwargs
)
@pytest.mark.parametrize(
("service", "payload", "function", "exception", "raised_exception", "message"),
[
@ -282,6 +350,18 @@ async def test_service_set_random_mealplan(
HomeAssistantError,
"Error connecting to Mealie instance",
),
(
SERVICE_SET_MEALPLAN,
{
ATTR_DATE: "2023-10-21",
ATTR_ENTRY_TYPE: "lunch",
ATTR_RECIPE_ID: "recipe_id",
},
"set_mealplan",
MealieConnectionError,
HomeAssistantError,
"Error connecting to Mealie instance",
),
],
)
async def test_services_connection_error(
@ -321,6 +401,14 @@ async def test_services_connection_error(
SERVICE_SET_RANDOM_MEALPLAN,
{ATTR_DATE: "2023-10-21", ATTR_ENTRY_TYPE: "lunch"},
),
(
SERVICE_SET_MEALPLAN,
{
ATTR_DATE: "2023-10-21",
ATTR_ENTRY_TYPE: "lunch",
ATTR_RECIPE_ID: "recipe_id",
},
),
],
)
async def test_service_entry_availability(