diff --git a/homeassistant/components/habitica/button.py b/homeassistant/components/habitica/button.py index 14625b31c2b..450a5cdcf20 100644 --- a/homeassistant/components/habitica/button.py +++ b/homeassistant/components/habitica/button.py @@ -335,16 +335,24 @@ class HabiticaButton(HabiticaBase, ButtonEntity): raise HomeAssistantError( translation_domain=DOMAIN, translation_key="setup_rate_limit_exception", + translation_placeholders={"retry_after": str(e.retry_after)}, ) from e except NotAuthorizedError as e: raise ServiceValidationError( translation_domain=DOMAIN, translation_key="service_call_unallowed", ) from e - except (HabiticaException, ClientError) as e: + except HabiticaException as e: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="service_call_exception", + translation_placeholders={"reason": e.error.message}, + ) from e + except ClientError as e: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="service_call_exception", + translation_placeholders={"reason": str(e)}, ) from e else: await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/habitica/coordinator.py b/homeassistant/components/habitica/coordinator.py index 587f8148398..f97b98410bb 100644 --- a/homeassistant/components/habitica/coordinator.py +++ b/homeassistant/components/habitica/coordinator.py @@ -85,11 +85,19 @@ class HabiticaDataUpdateCoordinator(DataUpdateCoordinator[HabiticaData]): raise ConfigEntryNotReady( translation_domain=DOMAIN, translation_key="setup_rate_limit_exception", + translation_placeholders={"retry_after": str(e.retry_after)}, ) from e - except (HabiticaException, ClientError) as e: + except HabiticaException as e: raise ConfigEntryNotReady( translation_domain=DOMAIN, translation_key="service_call_exception", + translation_placeholders={"reason": str(e.error.message)}, + ) from e + except ClientError as e: + raise ConfigEntryNotReady( + translation_domain=DOMAIN, + translation_key="service_call_exception", + translation_placeholders={"reason": str(e)}, ) from e if not self.config_entry.data.get(CONF_NAME): @@ -108,8 +116,18 @@ class HabiticaDataUpdateCoordinator(DataUpdateCoordinator[HabiticaData]): except TooManyRequestsError: _LOGGER.debug("Rate limit exceeded, will try again later") return self.data - except (HabiticaException, ClientError) as e: - raise UpdateFailed(f"Unable to connect to Habitica: {e}") from e + except HabiticaException as e: + raise UpdateFailed( + translation_domain=DOMAIN, + translation_key="service_call_exception", + translation_placeholders={"reason": str(e.error.message)}, + ) from e + except ClientError as e: + raise UpdateFailed( + translation_domain=DOMAIN, + translation_key="service_call_exception", + translation_placeholders={"reason": str(e)}, + ) from e else: return HabiticaData(user=user, tasks=tasks + completed_todos) @@ -124,11 +142,19 @@ class HabiticaDataUpdateCoordinator(DataUpdateCoordinator[HabiticaData]): raise HomeAssistantError( translation_domain=DOMAIN, translation_key="setup_rate_limit_exception", + translation_placeholders={"retry_after": str(e.retry_after)}, ) from e - except (HabiticaException, ClientError) as e: + except HabiticaException as e: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="service_call_exception", + translation_placeholders={"reason": e.error.message}, + ) from e + except ClientError as e: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="service_call_exception", + translation_placeholders={"reason": str(e)}, ) from e else: await self.async_request_refresh() diff --git a/homeassistant/components/habitica/quality_scale.yaml b/homeassistant/components/habitica/quality_scale.yaml index e279f924b72..c4ad2c76110 100644 --- a/homeassistant/components/habitica/quality_scale.yaml +++ b/homeassistant/components/habitica/quality_scale.yaml @@ -64,9 +64,7 @@ rules: entity-device-class: done entity-disabled-by-default: done entity-translations: done - exception-translations: - status: todo - comment: translations for UpdateFailed missing + exception-translations: done icon-translations: done reconfiguration-flow: todo repair-issues: diff --git a/homeassistant/components/habitica/services.py b/homeassistant/components/habitica/services.py index 5961c139003..a28aada85fa 100644 --- a/homeassistant/components/habitica/services.py +++ b/homeassistant/components/habitica/services.py @@ -224,6 +224,7 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 raise HomeAssistantError( translation_domain=DOMAIN, translation_key="setup_rate_limit_exception", + translation_placeholders={"retry_after": str(e.retry_after)}, ) from e except NotAuthorizedError as e: raise ServiceValidationError( @@ -243,10 +244,17 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 translation_key="skill_not_found", translation_placeholders={"skill": call.data[ATTR_SKILL]}, ) from e - except (HabiticaException, ClientError) as e: + except HabiticaException as e: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="service_call_exception", + translation_placeholders={"reason": str(e.error.message)}, + ) from e + except ClientError as e: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="service_call_exception", + translation_placeholders={"reason": str(e)}, ) from e else: await coordinator.async_request_refresh() @@ -274,6 +282,7 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 raise HomeAssistantError( translation_domain=DOMAIN, translation_key="setup_rate_limit_exception", + translation_placeholders={"retry_after": str(e.retry_after)}, ) from e except NotAuthorizedError as e: raise ServiceValidationError( @@ -283,9 +292,17 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 raise ServiceValidationError( translation_domain=DOMAIN, translation_key="quest_not_found" ) from e - except (HabiticaException, ClientError) as e: + except HabiticaException as e: raise HomeAssistantError( - translation_domain=DOMAIN, translation_key="service_call_exception" + translation_domain=DOMAIN, + translation_key="service_call_exception", + translation_placeholders={"reason": str(e.error.message)}, + ) from e + except ClientError as e: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="service_call_exception", + translation_placeholders={"reason": str(e)}, ) from e else: return asdict(response.data) @@ -335,6 +352,7 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 raise HomeAssistantError( translation_domain=DOMAIN, translation_key="setup_rate_limit_exception", + translation_placeholders={"retry_after": str(e.retry_after)}, ) from e except NotAuthorizedError as e: if task_value is not None: @@ -349,11 +367,19 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 raise HomeAssistantError( translation_domain=DOMAIN, translation_key="service_call_exception", + translation_placeholders={"reason": e.error.message}, ) from e - except (HabiticaException, ClientError) as e: + except HabiticaException as e: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="service_call_exception", + translation_placeholders={"reason": str(e.error.message)}, + ) from e + except ClientError as e: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="service_call_exception", + translation_placeholders={"reason": str(e)}, ) from e else: await coordinator.async_request_refresh() @@ -382,10 +408,17 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 translation_domain=DOMAIN, translation_key="party_not_found", ) from e - except (ClientError, HabiticaException) as e: + except HabiticaException as e: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="service_call_exception", + translation_placeholders={"reason": str(e.error.message)}, + ) from e + except ClientError as e: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="service_call_exception", + translation_placeholders={"reason": str(e)}, ) from e try: target_id = next( @@ -411,6 +444,7 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 raise HomeAssistantError( translation_domain=DOMAIN, translation_key="setup_rate_limit_exception", + translation_placeholders={"retry_after": str(e.retry_after)}, ) from e except NotAuthorizedError as e: raise ServiceValidationError( @@ -418,10 +452,17 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 translation_key="item_not_found", translation_placeholders={"item": call.data[ATTR_ITEM]}, ) from e - except (HabiticaException, ClientError) as e: + except HabiticaException as e: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="service_call_exception", + translation_placeholders={"reason": str(e.error.message)}, + ) from e + except ClientError as e: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="service_call_exception", + translation_placeholders={"reason": str(e)}, ) from e else: return asdict(response.data) diff --git a/homeassistant/components/habitica/strings.json b/homeassistant/components/habitica/strings.json index 4e1a0ac9f64..44487e7cb37 100644 --- a/homeassistant/components/habitica/strings.json +++ b/homeassistant/components/habitica/strings.json @@ -375,13 +375,13 @@ "message": "Unable to create new to-do `{name}` for Habitica, please try again" }, "setup_rate_limit_exception": { - "message": "Rate limit exceeded, try again later" + "message": "Rate limit exceeded, try again in {retry_after} seconds" }, "service_call_unallowed": { "message": "Unable to complete action, the required conditions are not met" }, "service_call_exception": { - "message": "Unable to connect to Habitica, try again later" + "message": "Unable to connect to Habitica: {reason}" }, "not_enough_mana": { "message": "Unable to cast skill, not enough mana. Your character has {mana}, but the skill costs {cost}." diff --git a/homeassistant/components/habitica/todo.py b/homeassistant/components/habitica/todo.py index a14327f5378..c1786059300 100644 --- a/homeassistant/components/habitica/todo.py +++ b/homeassistant/components/habitica/todo.py @@ -3,11 +3,18 @@ from __future__ import annotations from enum import StrEnum +import logging from typing import TYPE_CHECKING from uuid import UUID from aiohttp import ClientError -from habiticalib import Direction, HabiticaException, Task, TaskType +from habiticalib import ( + Direction, + HabiticaException, + Task, + TaskType, + TooManyRequestsError, +) from homeassistant.components import persistent_notification from homeassistant.components.todo import ( @@ -17,7 +24,7 @@ from homeassistant.components.todo import ( TodoListEntityFeature, ) from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ServiceValidationError +from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import dt as dt_util @@ -28,6 +35,8 @@ from .entity import HabiticaBase from .types import HabiticaConfigEntry from .util import next_due_date +_LOGGER = logging.getLogger(__name__) + PARALLEL_UPDATES = 1 @@ -72,7 +81,14 @@ class BaseHabiticaListEntity(HabiticaBase, TodoListEntity): if len(uids) > 1 and self.entity_description.key is HabiticaTodoList.TODOS: try: await self.coordinator.habitica.delete_completed_todos() + except TooManyRequestsError as e: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="setup_rate_limit_exception", + translation_placeholders={"retry_after": str(e.retry_after)}, + ) from e except (HabiticaException, ClientError) as e: + _LOGGER.debug(str(e)) raise ServiceValidationError( translation_domain=DOMAIN, translation_key="delete_completed_todos_failed", @@ -81,7 +97,14 @@ class BaseHabiticaListEntity(HabiticaBase, TodoListEntity): for task_id in uids: try: await self.coordinator.habitica.delete_task(UUID(task_id)) + except TooManyRequestsError as e: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="setup_rate_limit_exception", + translation_placeholders={"retry_after": str(e.retry_after)}, + ) from e except (HabiticaException, ClientError) as e: + _LOGGER.debug(str(e)) raise ServiceValidationError( translation_domain=DOMAIN, translation_key=f"delete_{self.entity_description.key}_failed", @@ -108,7 +131,14 @@ class BaseHabiticaListEntity(HabiticaBase, TodoListEntity): try: await self.coordinator.habitica.reorder_task(UUID(uid), pos) + except TooManyRequestsError as e: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="setup_rate_limit_exception", + translation_placeholders={"retry_after": str(e.retry_after)}, + ) from e except (HabiticaException, ClientError) as e: + _LOGGER.debug(str(e)) raise ServiceValidationError( translation_domain=DOMAIN, translation_key=f"move_{self.entity_description.key}_item_failed", @@ -160,7 +190,14 @@ class BaseHabiticaListEntity(HabiticaBase, TodoListEntity): try: await self.coordinator.habitica.update_task(UUID(item.uid), task) refresh_required = True + except TooManyRequestsError as e: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="setup_rate_limit_exception", + translation_placeholders={"retry_after": str(e.retry_after)}, + ) from e except (HabiticaException, ClientError) as e: + _LOGGER.debug(str(e)) raise ServiceValidationError( translation_domain=DOMAIN, translation_key=f"update_{self.entity_description.key}_item_failed", @@ -187,8 +224,14 @@ class BaseHabiticaListEntity(HabiticaBase, TodoListEntity): refresh_required = True else: score_result = None - + except TooManyRequestsError as e: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="setup_rate_limit_exception", + translation_placeholders={"retry_after": str(e.retry_after)}, + ) from e except (HabiticaException, ClientError) as e: + _LOGGER.debug(str(e)) raise ServiceValidationError( translation_domain=DOMAIN, translation_key=f"score_{self.entity_description.key}_item_failed", @@ -260,7 +303,14 @@ class HabiticaTodosListEntity(BaseHabiticaListEntity): date=item.due, ) ) + except TooManyRequestsError as e: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="setup_rate_limit_exception", + translation_placeholders={"retry_after": str(e.retry_after)}, + ) from e except (HabiticaException, ClientError) as e: + _LOGGER.debug(str(e)) raise ServiceValidationError( translation_domain=DOMAIN, translation_key=f"create_{self.entity_description.key}_item_failed", diff --git a/tests/components/habitica/conftest.py b/tests/components/habitica/conftest.py index b1410f559db..daf1c669463 100644 --- a/tests/components/habitica/conftest.py +++ b/tests/components/habitica/conftest.py @@ -32,11 +32,13 @@ from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, load_fixture -ERROR_RESPONSE = HabiticaErrorResponse(success=False, error="error", message="message") +ERROR_RESPONSE = HabiticaErrorResponse(success=False, error="error", message="reason") ERROR_NOT_AUTHORIZED = NotAuthorizedError(error=ERROR_RESPONSE, headers={}) ERROR_NOT_FOUND = NotFoundError(error=ERROR_RESPONSE, headers={}) ERROR_BAD_REQUEST = BadRequestError(error=ERROR_RESPONSE, headers={}) -ERROR_TOO_MANY_REQUESTS = TooManyRequestsError(error=ERROR_RESPONSE, headers={}) +ERROR_TOO_MANY_REQUESTS = TooManyRequestsError( + error=ERROR_RESPONSE, headers={"retry-after": 5} +) @pytest.fixture(name="config_entry") diff --git a/tests/components/habitica/test_button.py b/tests/components/habitica/test_button.py index adce8dce080..dc1a155b541 100644 --- a/tests/components/habitica/test_button.py +++ b/tests/components/habitica/test_button.py @@ -4,6 +4,7 @@ from collections.abc import Generator from datetime import timedelta from unittest.mock import AsyncMock, patch +from aiohttp import ClientError from freezegun.api import FrozenDateTimeFactory from habiticalib import HabiticaUserResponse, Skill import pytest @@ -215,12 +216,12 @@ async def test_button_press( [ ( ERROR_TOO_MANY_REQUESTS, - "Rate limit exceeded, try again later", + "Rate limit exceeded, try again in 5 seconds", HomeAssistantError, ), ( ERROR_BAD_REQUEST, - "Unable to connect to Habitica, try again later", + "Unable to connect to Habitica: reason", HomeAssistantError, ), ( @@ -228,6 +229,11 @@ async def test_button_press( "Unable to complete action, the required conditions are not met", ServiceValidationError, ), + ( + ClientError, + "Unable to connect to Habitica: ", + HomeAssistantError, + ), ], ) async def test_button_press_exceptions( diff --git a/tests/components/habitica/test_init.py b/tests/components/habitica/test_init.py index ed2efd89f30..e953ec254d6 100644 --- a/tests/components/habitica/test_init.py +++ b/tests/components/habitica/test_init.py @@ -4,6 +4,7 @@ import datetime import logging from unittest.mock import AsyncMock +from aiohttp import ClientError from freezegun.api import FrozenDateTimeFactory import pytest @@ -85,11 +86,12 @@ async def test_service_call( @pytest.mark.parametrize( ("exception"), - [ - ERROR_BAD_REQUEST, - ERROR_TOO_MANY_REQUESTS, + [ERROR_BAD_REQUEST, ERROR_TOO_MANY_REQUESTS, ClientError], + ids=[ + "BadRequestError", + "TooManyRequestsError", + "ClientError", ], - ids=["BadRequestError", "TooManyRequestsError"], ) async def test_config_entry_not_ready( hass: HomeAssistant, @@ -131,14 +133,16 @@ async def test_config_entry_auth_failed( assert flow["context"].get("entry_id") == config_entry.entry_id +@pytest.mark.parametrize("exception", [ERROR_NOT_FOUND, ClientError]) async def test_coordinator_update_failed( hass: HomeAssistant, config_entry: MockConfigEntry, habitica: AsyncMock, + exception: Exception, ) -> None: """Test coordinator update failed.""" - habitica.get_tasks.side_effect = ERROR_NOT_FOUND + habitica.get_tasks.side_effect = exception config_entry.add_to_hass(hass) await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/habitica/test_services.py b/tests/components/habitica/test_services.py index 3ada16b9735..5fca1884bdf 100644 --- a/tests/components/habitica/test_services.py +++ b/tests/components/habitica/test_services.py @@ -5,6 +5,7 @@ from typing import Any from unittest.mock import AsyncMock, patch from uuid import UUID +from aiohttp import ClientError from habiticalib import Direction, Skill import pytest from syrupy.assertion import SnapshotAssertion @@ -46,8 +47,8 @@ from .conftest import ( from tests.common import MockConfigEntry -REQUEST_EXCEPTION_MSG = "Unable to connect to Habitica, try again later" -RATE_LIMIT_EXCEPTION_MSG = "Rate limit exceeded, try again later" +REQUEST_EXCEPTION_MSG = "Unable to connect to Habitica: reason" +RATE_LIMIT_EXCEPTION_MSG = "Rate limit exceeded, try again in 5 seconds" @pytest.fixture(autouse=True) @@ -235,6 +236,15 @@ async def test_cast_skill( HomeAssistantError, REQUEST_EXCEPTION_MSG, ), + ( + { + ATTR_TASK: "Rechnungen bezahlen", + ATTR_SKILL: "smash", + }, + ClientError, + HomeAssistantError, + "Unable to connect to Habitica: ", + ), ], ) async def test_cast_skill_exceptions( @@ -360,6 +370,11 @@ async def test_handle_quests( HomeAssistantError, REQUEST_EXCEPTION_MSG, ), + ( + ClientError, + HomeAssistantError, + "Unable to connect to Habitica: ", + ), ], ) @pytest.mark.parametrize( @@ -520,6 +535,15 @@ async def test_score_task( HomeAssistantError, REQUEST_EXCEPTION_MSG, ), + ( + { + ATTR_TASK: "e97659e0-2c42-4599-a7bb-00282adc410d", + ATTR_DIRECTION: "up", + }, + ClientError, + HomeAssistantError, + "Unable to connect to Habitica: ", + ), ( { ATTR_TASK: "5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b", @@ -722,7 +746,7 @@ async def test_transformation( ERROR_BAD_REQUEST, None, HomeAssistantError, - "Unable to connect to Habitica, try again later", + REQUEST_EXCEPTION_MSG, ), ( { @@ -752,7 +776,27 @@ async def test_transformation( None, ERROR_BAD_REQUEST, HomeAssistantError, - "Unable to connect to Habitica, try again later", + REQUEST_EXCEPTION_MSG, + ), + ( + { + ATTR_TARGET: "test-partymember-username", + ATTR_ITEM: "spooky_sparkles", + }, + None, + ClientError, + HomeAssistantError, + "Unable to connect to Habitica: ", + ), + ( + { + ATTR_TARGET: "test-partymember-username", + ATTR_ITEM: "spooky_sparkles", + }, + ClientError, + None, + HomeAssistantError, + "Unable to connect to Habitica: ", ), ], ) diff --git a/tests/components/habitica/test_switch.py b/tests/components/habitica/test_switch.py index c259f53f183..1799788a48e 100644 --- a/tests/components/habitica/test_switch.py +++ b/tests/components/habitica/test_switch.py @@ -3,6 +3,7 @@ from collections.abc import Generator from unittest.mock import AsyncMock, patch +from aiohttp import ClientError import pytest from syrupy.assertion import SnapshotAssertion @@ -96,6 +97,7 @@ async def test_turn_on_off_toggle( [ (ERROR_TOO_MANY_REQUESTS, HomeAssistantError), (ERROR_BAD_REQUEST, HomeAssistantError), + (ClientError, HomeAssistantError), ], ) async def test_turn_on_off_toggle_exceptions( diff --git a/tests/components/habitica/test_todo.py b/tests/components/habitica/test_todo.py index 6453510a97f..8f20b3e685a 100644 --- a/tests/components/habitica/test_todo.py +++ b/tests/components/habitica/test_todo.py @@ -23,10 +23,10 @@ from homeassistant.components.todo import ( from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ServiceValidationError +from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.helpers import entity_registry as er -from .conftest import ERROR_NOT_FOUND +from .conftest import ERROR_NOT_FOUND, ERROR_TOO_MANY_REQUESTS from tests.common import ( MockConfigEntry, @@ -183,12 +183,30 @@ async def test_uncomplete_todo_item( ], ids=["completed", "needs_action"], ) +@pytest.mark.parametrize( + ("exception", "exc_msg", "expected_exception"), + [ + ( + ERROR_NOT_FOUND, + r"Unable to update the score for your Habitica to-do `.+`, please try again", + ServiceValidationError, + ), + ( + ERROR_TOO_MANY_REQUESTS, + "Rate limit exceeded, try again in 5 seconds", + HomeAssistantError, + ), + ], +) async def test_complete_todo_item_exception( hass: HomeAssistant, config_entry: MockConfigEntry, habitica: AsyncMock, uid: str, status: str, + exception: Exception, + exc_msg: str, + expected_exception: Exception, ) -> None: """Test exception when completing/uncompleting an item on the todo list.""" @@ -198,10 +216,10 @@ async def test_complete_todo_item_exception( assert config_entry.state is ConfigEntryState.LOADED - habitica.update_score.side_effect = ERROR_NOT_FOUND + habitica.update_score.side_effect = exception with pytest.raises( - expected_exception=ServiceValidationError, - match=r"Unable to update the score for your Habitica to-do `.+`, please try again", + expected_exception=expected_exception, + match=exc_msg, ): await hass.services.async_call( TODO_DOMAIN, @@ -311,10 +329,28 @@ async def test_update_todo_item( habitica.update_task.assert_awaited_once_with(*call_args) +@pytest.mark.parametrize( + ("exception", "exc_msg", "expected_exception"), + [ + ( + ERROR_NOT_FOUND, + "Unable to update the Habitica to-do `test-summary`, please try again", + ServiceValidationError, + ), + ( + ERROR_TOO_MANY_REQUESTS, + "Rate limit exceeded, try again in 5 seconds", + HomeAssistantError, + ), + ], +) async def test_update_todo_item_exception( hass: HomeAssistant, config_entry: MockConfigEntry, habitica: AsyncMock, + exception: Exception, + exc_msg: str, + expected_exception: Exception, ) -> None: """Test exception when update item on the todo list.""" uid = "88de7cd9-af2b-49ce-9afd-bf941d87336b" @@ -324,11 +360,8 @@ async def test_update_todo_item_exception( assert config_entry.state is ConfigEntryState.LOADED - habitica.update_task.side_effect = ERROR_NOT_FOUND - with pytest.raises( - expected_exception=ServiceValidationError, - match="Unable to update the Habitica to-do `test-summary`, please try again", - ): + habitica.update_task.side_effect = exception + with pytest.raises(expected_exception=expected_exception, match=exc_msg): await hass.services.async_call( TODO_DOMAIN, TodoServices.UPDATE_ITEM, @@ -378,10 +411,28 @@ async def test_add_todo_item( ) +@pytest.mark.parametrize( + ("exception", "exc_msg", "expected_exception"), + [ + ( + ERROR_NOT_FOUND, + "Unable to create new to-do `test-summary` for Habitica, please try again", + ServiceValidationError, + ), + ( + ERROR_TOO_MANY_REQUESTS, + "Rate limit exceeded, try again in 5 seconds", + HomeAssistantError, + ), + ], +) async def test_add_todo_item_exception( hass: HomeAssistant, config_entry: MockConfigEntry, habitica: AsyncMock, + exception: Exception, + exc_msg: str, + expected_exception: Exception, ) -> None: """Test exception when adding a todo item to the todo list.""" @@ -391,10 +442,11 @@ async def test_add_todo_item_exception( assert config_entry.state is ConfigEntryState.LOADED - habitica.create_task.side_effect = ERROR_NOT_FOUND + habitica.create_task.side_effect = exception with pytest.raises( - expected_exception=ServiceValidationError, - match="Unable to create new to-do `test-summary` for Habitica, please try again", + expected_exception=expected_exception, + # match="Unable to create new to-do `test-summary` for Habitica, please try again", + match=exc_msg, ): await hass.services.async_call( TODO_DOMAIN, @@ -434,10 +486,28 @@ async def test_delete_todo_item( habitica.delete_task.assert_awaited_once_with(UUID(uid)) +@pytest.mark.parametrize( + ("exception", "exc_msg", "expected_exception"), + [ + ( + ERROR_NOT_FOUND, + "Unable to delete item from Habitica to-do list, please try again", + ServiceValidationError, + ), + ( + ERROR_TOO_MANY_REQUESTS, + "Rate limit exceeded, try again in 5 seconds", + HomeAssistantError, + ), + ], +) async def test_delete_todo_item_exception( hass: HomeAssistant, config_entry: MockConfigEntry, habitica: AsyncMock, + exception: Exception, + exc_msg: str, + expected_exception: Exception, ) -> None: """Test exception when deleting a todo item from the todo list.""" @@ -448,11 +518,11 @@ async def test_delete_todo_item_exception( assert config_entry.state is ConfigEntryState.LOADED - habitica.delete_task.side_effect = ERROR_NOT_FOUND + habitica.delete_task.side_effect = exception with pytest.raises( - expected_exception=ServiceValidationError, - match="Unable to delete item from Habitica to-do list, please try again", + expected_exception=expected_exception, + match=exc_msg, ): await hass.services.async_call( TODO_DOMAIN, @@ -486,10 +556,28 @@ async def test_delete_completed_todo_items( habitica.delete_completed_todos.assert_awaited_once() +@pytest.mark.parametrize( + ("exception", "exc_msg", "expected_exception"), + [ + ( + ERROR_NOT_FOUND, + "Unable to delete completed to-do items from Habitica to-do list, please try again", + ServiceValidationError, + ), + ( + ERROR_TOO_MANY_REQUESTS, + "Rate limit exceeded, try again in 5 seconds", + HomeAssistantError, + ), + ], +) async def test_delete_completed_todo_items_exception( hass: HomeAssistant, config_entry: MockConfigEntry, habitica: AsyncMock, + exception: Exception, + exc_msg: str, + expected_exception: Exception, ) -> None: """Test exception when deleting completed todo items from the todo list.""" config_entry.add_to_hass(hass) @@ -498,10 +586,10 @@ async def test_delete_completed_todo_items_exception( assert config_entry.state is ConfigEntryState.LOADED - habitica.delete_completed_todos.side_effect = ERROR_NOT_FOUND + habitica.delete_completed_todos.side_effect = exception with pytest.raises( - expected_exception=ServiceValidationError, - match="Unable to delete completed to-do items from Habitica to-do list, please try again", + expected_exception=expected_exception, + match=exc_msg, ): await hass.services.async_call( TODO_DOMAIN, @@ -575,11 +663,26 @@ async def test_move_todo_item( habitica.reorder_task.assert_awaited_once_with(UUID(uid), 0) +@pytest.mark.parametrize( + ("exception", "exc_msg"), + [ + ( + ERROR_NOT_FOUND, + "Unable to move the Habitica to-do to position 0, please try again", + ), + ( + ERROR_TOO_MANY_REQUESTS, + "Rate limit exceeded, try again in 5 seconds", + ), + ], +) async def test_move_todo_item_exception( hass: HomeAssistant, config_entry: MockConfigEntry, habitica: AsyncMock, hass_ws_client: WebSocketGenerator, + exception: Exception, + exc_msg: str, ) -> None: """Test exception when moving todo item.""" @@ -590,7 +693,7 @@ async def test_move_todo_item_exception( assert config_entry.state is ConfigEntryState.LOADED - habitica.reorder_task.side_effect = ERROR_NOT_FOUND + habitica.reorder_task.side_effect = exception client = await hass_ws_client() data = { @@ -605,10 +708,7 @@ async def test_move_todo_item_exception( habitica.reorder_task.assert_awaited_once_with(UUID(uid), 0) assert resp["success"] is False - assert ( - resp["error"]["message"] - == "Unable to move the Habitica to-do to position 0, please try again" - ) + assert resp["error"]["message"] == exc_msg @pytest.mark.parametrize(