Add update and delete mealplan actions to Mealie (#177539)

This commit is contained in:
Andrew Jackson
2026-08-25 19:07:32 +02:00
committed by GitHub
parent 9e778e15d0
commit 1c0440821c
8 changed files with 577 additions and 9 deletions
+1
View File
@@ -10,6 +10,7 @@ LOGGER = logging.getLogger(__package__)
ATTR_START_DATE = "start_date"
ATTR_END_DATE = "end_date"
ATTR_MEALPLAN_ID = "mealplan_id"
ATTR_RECIPE_ID = "recipe_id"
ATTR_URL = "url"
ATTR_INCLUDE_TAGS = "include_tags"
@@ -24,6 +24,9 @@
}
},
"services": {
"delete_mealplan": {
"service": "mdi:food-off"
},
"get_mealplan": {
"service": "mdi:food"
},
@@ -44,6 +47,9 @@
},
"set_random_mealplan": {
"service": "mdi:dice-multiple"
},
"update_mealplan": {
"service": "mdi:food"
}
}
}
+110
View File
@@ -28,6 +28,7 @@ from .const import (
ATTR_END_DATE,
ATTR_ENTRY_TYPE,
ATTR_INCLUDE_TAGS,
ATTR_MEALPLAN_ID,
ATTR_NOTE_TEXT,
ATTR_NOTE_TITLE,
ATTR_RECIPE_ID,
@@ -109,6 +110,39 @@ SERVICE_SET_MEALPLAN_SCHEMA = vol.Any(
}
),
)
SERVICE_DELETE_MEALPLAN = "delete_mealplan"
SERVICE_DELETE_MEALPLAN_SCHEMA = vol.Schema(
{
vol.Required(ATTR_CONFIG_ENTRY_ID): str,
vol.Required(ATTR_MEALPLAN_ID): str,
}
)
SERVICE_UPDATE_MEALPLAN = "update_mealplan"
SERVICE_UPDATE_MEALPLAN_SCHEMA = vol.Any(
vol.Schema(
{
vol.Required(ATTR_CONFIG_ENTRY_ID): str,
vol.Required(ATTR_MEALPLAN_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_MEALPLAN_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.Optional(ATTR_NOTE_TEXT): str,
}
),
)
def _validate_mealplan_type(version: AwesomeVersion, entry_type: str) -> None:
@@ -278,6 +312,69 @@ async def _async_set_mealplan(call: ServiceCall) -> ServiceResponse:
return None
async def _async_delete_mealplan(call: ServiceCall) -> ServiceResponse:
"""Delete a mealplan."""
entry: MealieConfigEntry = service.async_get_config_entry(
call.hass, DOMAIN, call.data[ATTR_CONFIG_ENTRY_ID]
)
mealplan_id = call.data[ATTR_MEALPLAN_ID]
client = entry.runtime_data.client
try:
await client.delete_mealplan(
mealplan_id,
)
except MealieConnectionError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="connection_error",
) from err
except MealieNotFoundError as err:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="mealplan_not_found",
translation_placeholders={"mealplan_id": mealplan_id},
) from err
return None
async def _async_update_mealplan(call: ServiceCall) -> ServiceResponse:
"""Update a mealplan."""
entry: MealieConfigEntry = service.async_get_config_entry(
call.hass, DOMAIN, call.data[ATTR_CONFIG_ENTRY_ID]
)
mealplan_id = call.data[ATTR_MEALPLAN_ID]
mealplan_date = call.data[ATTR_DATE]
entry_type = MealplanEntryType(call.data[ATTR_ENTRY_TYPE])
client = entry.runtime_data.client
_validate_mealplan_type(entry.runtime_data.version, entry_type.value)
try:
mealplan = await client.update_mealplan(
mealplan_id,
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
except MealieNotFoundError as err:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="mealplan_not_found",
translation_placeholders={"mealplan_id": mealplan_id},
) from err
if call.return_response:
return {"mealplan": asdict(mealplan)}
return None
@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Set up the services for the Mealie integration."""
@@ -324,6 +421,19 @@ def async_setup_services(hass: HomeAssistant) -> None:
schema=SERVICE_SET_MEALPLAN_SCHEMA,
supports_response=SupportsResponse.OPTIONAL,
)
hass.services.async_register(
DOMAIN,
SERVICE_DELETE_MEALPLAN,
_async_delete_mealplan,
schema=SERVICE_DELETE_MEALPLAN_SCHEMA,
)
hass.services.async_register(
DOMAIN,
SERVICE_UPDATE_MEALPLAN,
_async_update_mealplan,
schema=SERVICE_UPDATE_MEALPLAN_SCHEMA,
supports_response=SupportsResponse.OPTIONAL,
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
@@ -1,3 +1,15 @@
delete_mealplan:
fields:
config_entry_id:
required: true
selector:
config_entry:
integration: mealie
mealplan_id:
required: true
selector:
text:
get_mealplan:
fields:
config_entry_id:
@@ -120,3 +132,39 @@ set_mealplan:
note_text:
selector:
text:
update_mealplan:
fields:
config_entry_id:
required: true
selector:
config_entry:
integration: mealie
mealplan_id:
required: true
selector:
text:
date:
selector:
date:
entry_type:
selector:
select:
options:
- breakfast
- lunch
- dinner
- side
- dessert
- snack
- drink
translation_key: mealplan_entry_type
recipe_id:
selector:
text:
note_title:
selector:
text:
note_text:
selector:
text:
@@ -138,6 +138,9 @@
"item_not_found_error": {
"message": "Item {shopping_list_item} not found."
},
"mealplan_not_found": {
"message": "Mealplan with ID `{mealplan_id}` not found."
},
"no_recipes_found": {
"message": "No recipes found matching your search."
},
@@ -180,6 +183,20 @@
}
},
"services": {
"delete_mealplan": {
"description": "Deletes a mealplan",
"fields": {
"config_entry_id": {
"description": "[%key:component::mealie::services::get_mealplan::fields::config_entry_id::description%]",
"name": "[%key:component::mealie::services::get_mealplan::fields::config_entry_id::name%]"
},
"mealplan_id": {
"description": "The mealplan ID to delete.",
"name": "Mealplan ID"
}
},
"name": "Delete a mealplan"
},
"get_mealplan": {
"description": "Gets a mealplan from Mealie",
"fields": {
@@ -299,6 +316,40 @@
}
},
"name": "Set random mealplan"
},
"update_mealplan": {
"description": "Updates a mealplan",
"fields": {
"config_entry_id": {
"description": "[%key:component::mealie::services::get_mealplan::fields::config_entry_id::description%]",
"name": "[%key:component::mealie::services::get_mealplan::fields::config_entry_id::name%]"
},
"date": {
"description": "[%key:component::mealie::services::set_random_mealplan::fields::date::description%]",
"name": "[%key:component::mealie::services::set_random_mealplan::fields::date::name%]"
},
"entry_type": {
"description": "The type of dish to update the recipe to.",
"name": "[%key:component::mealie::services::set_random_mealplan::fields::entry_type::name%]"
},
"mealplan_id": {
"description": "The mealplan ID to update.",
"name": "Mealplan ID"
},
"note_text": {
"description": "Meal note text for when planning without recipe.",
"name": "Note text"
},
"note_title": {
"description": "Meal note title for when planning without recipe.",
"name": "Meal note title"
},
"recipe_id": {
"description": "The recipe ID or the slug of the recipe to set.",
"name": "Recipe ID"
}
},
"name": "Update a mealplan"
}
}
}
+2
View File
@@ -79,6 +79,8 @@ def mock_mealie_client() -> Generator[AsyncMock]:
mealplan = Mealplan.from_json(load_fixture("mealplan.json", DOMAIN))
client.random_mealplan.return_value = mealplan
client.set_mealplan.return_value = mealplan
client.update_mealplan.return_value = mealplan
client.delete_mealplan.return_value = mealplan
yield client
@@ -5022,3 +5022,123 @@
}),
})
# ---
# name: test_service_update_mealplan[payload0-kwargs0]
dict({
'mealplan': dict({
'description': None,
'entry_type': <MealplanEntryType.DINNER: 'dinner'>,
'group_id': '0bf60b2e-ca89-42a9-94d4-8f67ca72b157',
'household_id': None,
'mealplan_date': datetime.date(2024, 1, 22),
'mealplan_id': 230,
'recipe': dict({
'categories': list([
]),
'date_added': datetime.date(2024, 1, 22),
'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',
'household_id': None,
'image': 'AiIo',
'last_made': None,
'name': 'Zoete aardappel curry traybake',
'original_url': 'https://chickslovefood.com/recept/zoete-aardappel-curry-traybake/',
'perform_time': None,
'prep_time': None,
'rating': None,
'recipe_id': 'c5f00a93-71a2-4e48-900f-d9ad0bb9de93',
'recipe_servings': None,
'recipe_yield': '2 servings',
'recipe_yield_quantity': None,
'slug': 'zoete-aardappel-curry-traybake',
'tags': list([
]),
'tools': list([
]),
'total_time': '40 Minutes',
'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d',
}),
'title': None,
'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d',
}),
})
# ---
# name: test_service_update_mealplan[payload1-kwargs1]
dict({
'mealplan': dict({
'description': None,
'entry_type': <MealplanEntryType.DINNER: 'dinner'>,
'group_id': '0bf60b2e-ca89-42a9-94d4-8f67ca72b157',
'household_id': None,
'mealplan_date': datetime.date(2024, 1, 22),
'mealplan_id': 230,
'recipe': dict({
'categories': list([
]),
'date_added': datetime.date(2024, 1, 22),
'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',
'household_id': None,
'image': 'AiIo',
'last_made': None,
'name': 'Zoete aardappel curry traybake',
'original_url': 'https://chickslovefood.com/recept/zoete-aardappel-curry-traybake/',
'perform_time': None,
'prep_time': None,
'rating': None,
'recipe_id': 'c5f00a93-71a2-4e48-900f-d9ad0bb9de93',
'recipe_servings': None,
'recipe_yield': '2 servings',
'recipe_yield_quantity': None,
'slug': 'zoete-aardappel-curry-traybake',
'tags': list([
]),
'tools': list([
]),
'total_time': '40 Minutes',
'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d',
}),
'title': None,
'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d',
}),
})
# ---
# name: test_service_update_mealplan[payload2-kwargs2]
dict({
'mealplan': dict({
'description': None,
'entry_type': <MealplanEntryType.DINNER: 'dinner'>,
'group_id': '0bf60b2e-ca89-42a9-94d4-8f67ca72b157',
'household_id': None,
'mealplan_date': datetime.date(2024, 1, 22),
'mealplan_id': 230,
'recipe': dict({
'categories': list([
]),
'date_added': datetime.date(2024, 1, 22),
'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',
'household_id': None,
'image': 'AiIo',
'last_made': None,
'name': 'Zoete aardappel curry traybake',
'original_url': 'https://chickslovefood.com/recept/zoete-aardappel-curry-traybake/',
'perform_time': None,
'prep_time': None,
'rating': None,
'recipe_id': 'c5f00a93-71a2-4e48-900f-d9ad0bb9de93',
'recipe_servings': None,
'recipe_yield': '2 servings',
'recipe_yield_quantity': None,
'slug': 'zoete-aardappel-curry-traybake',
'tags': list([
]),
'tools': list([
]),
'total_time': '40 Minutes',
'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d',
}),
'title': None,
'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d',
}),
})
# ---
+239 -9
View File
@@ -18,6 +18,7 @@ from homeassistant.components.mealie.const import (
ATTR_END_DATE,
ATTR_ENTRY_TYPE,
ATTR_INCLUDE_TAGS,
ATTR_MEALPLAN_ID,
ATTR_NOTE_TEXT,
ATTR_NOTE_TITLE,
ATTR_RECIPE_ID,
@@ -28,6 +29,7 @@ from homeassistant.components.mealie.const import (
DOMAIN,
)
from homeassistant.components.mealie.services import (
SERVICE_DELETE_MEALPLAN,
SERVICE_GET_MEALPLAN,
SERVICE_GET_RECIPE,
SERVICE_GET_RECIPES,
@@ -35,6 +37,7 @@ from homeassistant.components.mealie.services import (
SERVICE_IMPORT_RECIPE,
SERVICE_SET_MEALPLAN,
SERVICE_SET_RANDOM_MEALPLAN,
SERVICE_UPDATE_MEALPLAN,
)
from homeassistant.const import ATTR_CONFIG_ENTRY_ID, ATTR_DATE
from homeassistant.core import HomeAssistant
@@ -396,6 +399,177 @@ async def test_service_set_mealplan_invalid_entry_type(
mock_mealie_client.set_mealplan.assert_not_called()
async def test_service_delete_mealplan(
hass: HomeAssistant,
mock_mealie_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the delete_mealplan service."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
DOMAIN,
SERVICE_DELETE_MEALPLAN,
{
ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
ATTR_MEALPLAN_ID: "mealplan_id",
},
blocking=True,
)
mock_mealie_client.delete_mealplan.assert_called_with("mealplan_id")
async def test_service_delete_mealplan_not_found(
hass: HomeAssistant,
mock_mealie_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the delete_mealplan service with invalid mealplan ID."""
await setup_integration(hass, mock_config_entry)
mock_mealie_client.delete_mealplan.side_effect = MealieNotFoundError
with pytest.raises(ServiceValidationError, match="Mealplan with ID"):
await hass.services.async_call(
DOMAIN,
SERVICE_DELETE_MEALPLAN,
{
ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
ATTR_MEALPLAN_ID: "invalid_mealplan_id",
},
blocking=True,
)
mock_mealie_client.delete_mealplan.assert_called_once()
@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"},
),
(
{
ATTR_NOTE_TITLE: "Note Title",
},
{"recipe_id": None, "note_title": "Note Title", "note_text": None},
),
],
)
async def test_service_update_mealplan(
hass: HomeAssistant,
mock_mealie_client: AsyncMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
payload: dict[str, str],
kwargs: dict[str, str],
) -> None:
"""Test the update_mealplan service."""
await setup_integration(hass, mock_config_entry)
response = await hass.services.async_call(
DOMAIN,
SERVICE_UPDATE_MEALPLAN,
{
ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
ATTR_MEALPLAN_ID: "mealplan_id",
ATTR_DATE: "2023-10-21",
ATTR_ENTRY_TYPE: "lunch",
}
| payload,
blocking=True,
return_response=True,
)
assert response == snapshot
mock_mealie_client.update_mealplan.assert_called_with(
"mealplan_id", date(2023, 10, 21), MealplanEntryType.LUNCH, **kwargs
)
mock_mealie_client.update_mealplan.reset_mock()
await hass.services.async_call(
DOMAIN,
SERVICE_UPDATE_MEALPLAN,
{
ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
ATTR_MEALPLAN_ID: "mealplan_id",
ATTR_DATE: "2023-10-21",
ATTR_ENTRY_TYPE: "lunch",
}
| payload,
blocking=True,
return_response=False,
)
mock_mealie_client.update_mealplan.assert_called_with(
"mealplan_id", date(2023, 10, 21), MealplanEntryType.LUNCH, **kwargs
)
async def test_service_update_mealplan_invalid_entry_type(
hass: HomeAssistant,
mock_mealie_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the update_mealplan service with invalid entry types for version."""
mock_mealie_client.get_about.return_value = About(version="v3.6.0")
await setup_integration(hass, mock_config_entry)
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
DOMAIN,
SERVICE_UPDATE_MEALPLAN,
{
ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
ATTR_MEALPLAN_ID: "mealplan_id",
ATTR_DATE: "2023-10-21",
ATTR_ENTRY_TYPE: "dessert",
ATTR_NOTE_TITLE: "Note Title",
},
blocking=True,
return_response=True,
)
mock_mealie_client.update_mealplan.assert_not_called()
async def test_service_update_mealplan_not_found(
hass: HomeAssistant,
mock_mealie_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the update_mealplan service with invalid mealplan ID."""
await setup_integration(hass, mock_config_entry)
mock_mealie_client.update_mealplan.side_effect = MealieNotFoundError
with pytest.raises(ServiceValidationError, match="Mealplan with ID"):
await hass.services.async_call(
DOMAIN,
SERVICE_UPDATE_MEALPLAN,
{
ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
ATTR_MEALPLAN_ID: "invalid_mealplan_id",
ATTR_DATE: "2023-10-21",
ATTR_ENTRY_TYPE: "lunch",
ATTR_RECIPE_ID: "recipe_id",
},
blocking=True,
return_response=True,
)
mock_mealie_client.update_mealplan.assert_called_once()
async def test_service_get_shopping_list_items(
hass: HomeAssistant,
mock_mealie_client: AsyncMock,
@@ -438,7 +612,15 @@ async def test_service_get_shopping_list_items_connection_error(
@pytest.mark.parametrize(
("service", "payload", "function", "exception", "raised_exception", "message"),
(
"service",
"payload",
"function",
"exception",
"raised_exception",
"message",
"return_response",
),
[
(
SERVICE_GET_MEALPLAN,
@@ -447,6 +629,7 @@ async def test_service_get_shopping_list_items_connection_error(
MealieConnectionError,
HomeAssistantError,
"Error connecting to Mealie instance",
True,
),
(
SERVICE_GET_RECIPE,
@@ -455,6 +638,7 @@ async def test_service_get_shopping_list_items_connection_error(
MealieConnectionError,
HomeAssistantError,
"Error connecting to Mealie instance",
True,
),
(
SERVICE_GET_RECIPE,
@@ -463,6 +647,7 @@ async def test_service_get_shopping_list_items_connection_error(
MealieNotFoundError,
ServiceValidationError,
"Recipe with ID or slug `recipe_id` not found",
True,
),
(
SERVICE_GET_RECIPES,
@@ -471,6 +656,7 @@ async def test_service_get_shopping_list_items_connection_error(
MealieConnectionError,
HomeAssistantError,
"Error connecting to Mealie instance",
True,
),
(
SERVICE_GET_RECIPES,
@@ -479,6 +665,7 @@ async def test_service_get_shopping_list_items_connection_error(
MealieNotFoundError,
ServiceValidationError,
"No recipes found matching your search",
True,
),
(
SERVICE_IMPORT_RECIPE,
@@ -487,6 +674,7 @@ async def test_service_get_shopping_list_items_connection_error(
MealieConnectionError,
HomeAssistantError,
"Error connecting to Mealie instance",
True,
),
(
SERVICE_IMPORT_RECIPE,
@@ -495,6 +683,7 @@ async def test_service_get_shopping_list_items_connection_error(
MealieValidationError,
ServiceValidationError,
"Mealie could not import the recipe from the URL",
True,
),
(
SERVICE_SET_RANDOM_MEALPLAN,
@@ -503,6 +692,7 @@ async def test_service_get_shopping_list_items_connection_error(
MealieConnectionError,
HomeAssistantError,
"Error connecting to Mealie instance",
True,
),
(
SERVICE_SET_MEALPLAN,
@@ -515,6 +705,30 @@ async def test_service_get_shopping_list_items_connection_error(
MealieConnectionError,
HomeAssistantError,
"Error connecting to Mealie instance",
True,
),
(
SERVICE_DELETE_MEALPLAN,
{ATTR_MEALPLAN_ID: "mealplan_id"},
"delete_mealplan",
MealieConnectionError,
HomeAssistantError,
"Error connecting to Mealie instance",
False,
),
(
SERVICE_UPDATE_MEALPLAN,
{
ATTR_MEALPLAN_ID: "mealplan_id",
ATTR_DATE: "2023-10-21",
ATTR_ENTRY_TYPE: "lunch",
ATTR_RECIPE_ID: "recipe_id",
},
"update_mealplan",
MealieConnectionError,
HomeAssistantError,
"Error connecting to Mealie instance",
True,
),
],
)
@@ -528,6 +742,7 @@ async def test_services_connection_error(
exception: Exception,
raised_exception: type[Exception],
message: str,
return_response: bool,
) -> None:
"""Test a connection error in the services."""
@@ -541,24 +756,26 @@ async def test_services_connection_error(
service,
{ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id} | payload,
blocking=True,
return_response=True,
return_response=return_response,
)
@pytest.mark.parametrize(
("service", "payload"),
("service", "payload", "return_response"),
[
(SERVICE_GET_MEALPLAN, {}),
(SERVICE_GET_RECIPE, {ATTR_RECIPE_ID: "recipe_id"}),
(SERVICE_GET_RECIPES, {}),
(SERVICE_GET_MEALPLAN, {}, True),
(SERVICE_GET_RECIPE, {ATTR_RECIPE_ID: "recipe_id"}, True),
(SERVICE_GET_RECIPES, {}, True),
(
SERVICE_GET_RECIPES,
{ATTR_SEARCH_TERMS: "pasta", ATTR_RESULT_LIMIT: 5},
True,
),
(SERVICE_IMPORT_RECIPE, {ATTR_URL: "http://example.com"}),
(SERVICE_IMPORT_RECIPE, {ATTR_URL: "http://example.com"}, True),
(
SERVICE_SET_RANDOM_MEALPLAN,
{ATTR_DATE: "2023-10-21", ATTR_ENTRY_TYPE: "lunch"},
True,
),
(
SERVICE_SET_MEALPLAN,
@@ -567,6 +784,18 @@ async def test_services_connection_error(
ATTR_ENTRY_TYPE: "lunch",
ATTR_RECIPE_ID: "recipe_id",
},
True,
),
(SERVICE_DELETE_MEALPLAN, {ATTR_MEALPLAN_ID: "mealplan_id"}, False),
(
SERVICE_UPDATE_MEALPLAN,
{
ATTR_MEALPLAN_ID: "mealplan_id",
ATTR_DATE: "2023-10-21",
ATTR_ENTRY_TYPE: "lunch",
ATTR_RECIPE_ID: "recipe_id",
},
True,
),
],
)
@@ -576,6 +805,7 @@ async def test_service_entry_availability(
mock_config_entry: MockConfigEntry,
service: str,
payload: dict[str, str],
return_response: bool,
) -> None:
"""Test the services without valid entry."""
mock_config_entry.add_to_hass(hass)
@@ -590,7 +820,7 @@ async def test_service_entry_availability(
service,
{ATTR_CONFIG_ENTRY_ID: mock_config_entry2.entry_id} | payload,
blocking=True,
return_response=True,
return_response=return_response,
)
assert err.value.translation_key == "service_config_entry_not_loaded"
@@ -600,6 +830,6 @@ async def test_service_entry_availability(
service,
{ATTR_CONFIG_ENTRY_ID: "bad-config_id"} | payload,
blocking=True,
return_response=True,
return_response=return_response,
)
assert err.value.translation_key == "service_config_entry_not_found"