Improve exception handling in Habitica integration (#135950)

pull/136016/head
Manu 2025-01-19 19:51:55 +01:00 committed by GitHub
parent ccd7b1c21a
commit ec45cb4939
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 337 additions and 56 deletions

View File

@ -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()

View File

@ -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()

View File

@ -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:

View File

@ -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)

View File

@ -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}."

View File

@ -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",

View File

@ -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")

View File

@ -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(

View File

@ -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()

View File

@ -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: ",
),
],
)

View File

@ -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(

View File

@ -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(