mirror of
https://github.com/home-assistant/core.git
synced 2025-04-29 03:37:51 +00:00
Improve error handling and add exception translations for Nettigo Air Monitor integration (#141183)
* Add update_error * Add device_communication_error * Add auth_error * Add device_communication_action_error * Coverage
This commit is contained in:
parent
ef84fc52af
commit
1f122ea54d
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiohttp.client_exceptions import ClientConnectorError, ClientError
|
from aiohttp.client_exceptions import ClientError
|
||||||
from nettigo_air_monitor import (
|
from nettigo_air_monitor import (
|
||||||
ApiError,
|
ApiError,
|
||||||
AuthFailedError,
|
AuthFailedError,
|
||||||
@ -38,15 +38,27 @@ async def async_setup_entry(hass: HomeAssistant, entry: NAMConfigEntry) -> bool:
|
|||||||
options = ConnectionOptions(host=host, username=username, password=password)
|
options = ConnectionOptions(host=host, username=username, password=password)
|
||||||
try:
|
try:
|
||||||
nam = await NettigoAirMonitor.create(websession, options)
|
nam = await NettigoAirMonitor.create(websession, options)
|
||||||
except (ApiError, ClientError, ClientConnectorError, TimeoutError) as err:
|
except (ApiError, ClientError) as err:
|
||||||
raise ConfigEntryNotReady from err
|
raise ConfigEntryNotReady(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="device_communication_error",
|
||||||
|
translation_placeholders={"device": entry.title},
|
||||||
|
) from err
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await nam.async_check_credentials()
|
await nam.async_check_credentials()
|
||||||
except ApiError as err:
|
except (ApiError, ClientError) as err:
|
||||||
raise ConfigEntryNotReady from err
|
raise ConfigEntryNotReady(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="device_communication_error",
|
||||||
|
translation_placeholders={"device": entry.title},
|
||||||
|
) from err
|
||||||
except AuthFailedError as err:
|
except AuthFailedError as err:
|
||||||
raise ConfigEntryAuthFailed from err
|
raise ConfigEntryAuthFailed(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="auth_error",
|
||||||
|
translation_placeholders={"device": entry.title},
|
||||||
|
) from err
|
||||||
|
|
||||||
coordinator = NAMDataUpdateCoordinator(hass, entry, nam)
|
coordinator = NAMDataUpdateCoordinator(hass, entry, nam)
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
@ -4,6 +4,9 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from aiohttp.client_exceptions import ClientError
|
||||||
|
from nettigo_air_monitor import ApiError, AuthFailedError
|
||||||
|
|
||||||
from homeassistant.components.button import (
|
from homeassistant.components.button import (
|
||||||
ButtonDeviceClass,
|
ButtonDeviceClass,
|
||||||
ButtonEntity,
|
ButtonEntity,
|
||||||
@ -11,9 +14,11 @@ from homeassistant.components.button import (
|
|||||||
)
|
)
|
||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
from .coordinator import NAMConfigEntry, NAMDataUpdateCoordinator
|
from .coordinator import NAMConfigEntry, NAMDataUpdateCoordinator
|
||||||
|
|
||||||
PARALLEL_UPDATES = 1
|
PARALLEL_UPDATES = 1
|
||||||
@ -59,4 +64,16 @@ class NAMButton(CoordinatorEntity[NAMDataUpdateCoordinator], ButtonEntity):
|
|||||||
|
|
||||||
async def async_press(self) -> None:
|
async def async_press(self) -> None:
|
||||||
"""Triggers the restart."""
|
"""Triggers the restart."""
|
||||||
await self.coordinator.nam.async_restart()
|
try:
|
||||||
|
await self.coordinator.nam.async_restart()
|
||||||
|
except (ApiError, ClientError) as err:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="device_communication_action_error",
|
||||||
|
translation_placeholders={
|
||||||
|
"entity": self.entity_id,
|
||||||
|
"device": self.coordinator.config_entry.title,
|
||||||
|
},
|
||||||
|
) from err
|
||||||
|
except AuthFailedError:
|
||||||
|
self.coordinator.config_entry.async_start_reauth(self.hass)
|
||||||
|
@ -64,6 +64,10 @@ class NAMDataUpdateCoordinator(DataUpdateCoordinator[NAMSensors]):
|
|||||||
# We do not need to catch AuthFailed exception here because sensor data is
|
# We do not need to catch AuthFailed exception here because sensor data is
|
||||||
# always available without authorization.
|
# always available without authorization.
|
||||||
except (ApiError, InvalidSensorDataError, RetryError) as error:
|
except (ApiError, InvalidSensorDataError, RetryError) as error:
|
||||||
raise UpdateFailed(error) from error
|
raise UpdateFailed(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="update_error",
|
||||||
|
translation_placeholders={"device": self.config_entry.title},
|
||||||
|
) from error
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
@ -205,5 +205,19 @@
|
|||||||
"name": "Last restart"
|
"name": "Last restart"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"exceptions": {
|
||||||
|
"auth_error": {
|
||||||
|
"message": "Authentication failed for {device}, please update your credentials"
|
||||||
|
},
|
||||||
|
"device_communication_error": {
|
||||||
|
"message": "An error occurred while communicating with {device}"
|
||||||
|
},
|
||||||
|
"device_communication_action_error": {
|
||||||
|
"message": "An error occurred while calling action for {entity} for {device}"
|
||||||
|
},
|
||||||
|
"update_error": {
|
||||||
|
"message": "An error occurred while retrieving data from {device}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,20 @@
|
|||||||
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, ButtonDeviceClass
|
from aiohttp.client_exceptions import ClientError
|
||||||
|
from nettigo_air_monitor import ApiError, AuthFailedError
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.button import (
|
||||||
|
DOMAIN as BUTTON_DOMAIN,
|
||||||
|
SERVICE_PRESS,
|
||||||
|
ButtonDeviceClass,
|
||||||
|
)
|
||||||
|
from homeassistant.components.nam import DOMAIN
|
||||||
|
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||||
from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, STATE_UNKNOWN
|
from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, STATE_UNKNOWN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
@ -38,7 +49,7 @@ async def test_button_press(hass: HomeAssistant) -> None:
|
|||||||
):
|
):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
BUTTON_DOMAIN,
|
BUTTON_DOMAIN,
|
||||||
"press",
|
SERVICE_PRESS,
|
||||||
{ATTR_ENTITY_ID: "button.nettigo_air_monitor_restart"},
|
{ATTR_ENTITY_ID: "button.nettigo_air_monitor_restart"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
@ -49,3 +60,55 @@ async def test_button_press(hass: HomeAssistant) -> None:
|
|||||||
state = hass.states.get("button.nettigo_air_monitor_restart")
|
state = hass.states.get("button.nettigo_air_monitor_restart")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == now.isoformat()
|
assert state.state == now.isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(("exc"), [ApiError("API Error"), ClientError])
|
||||||
|
async def test_button_press_exc(hass: HomeAssistant, exc: Exception) -> None:
|
||||||
|
"""Test button press when exception occurs."""
|
||||||
|
await init_integration(hass)
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.nam.NettigoAirMonitor.async_restart",
|
||||||
|
side_effect=exc,
|
||||||
|
),
|
||||||
|
pytest.raises(
|
||||||
|
HomeAssistantError,
|
||||||
|
match="An error occurred while calling action for button.nettigo_air_monitor_restart",
|
||||||
|
),
|
||||||
|
):
|
||||||
|
await hass.services.async_call(
|
||||||
|
BUTTON_DOMAIN,
|
||||||
|
SERVICE_PRESS,
|
||||||
|
{ATTR_ENTITY_ID: "button.nettigo_air_monitor_restart"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_button_press_auth_error(hass: HomeAssistant) -> None:
|
||||||
|
"""Test button press when auth error occurs."""
|
||||||
|
entry = await init_integration(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nam.NettigoAirMonitor.async_restart",
|
||||||
|
side_effect=AuthFailedError("auth error"),
|
||||||
|
):
|
||||||
|
await hass.services.async_call(
|
||||||
|
BUTTON_DOMAIN,
|
||||||
|
SERVICE_PRESS,
|
||||||
|
{ATTR_ENTITY_ID: "button.nettigo_air_monitor_restart"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
flows = hass.config_entries.flow.async_progress()
|
||||||
|
assert len(flows) == 1
|
||||||
|
|
||||||
|
flow = flows[0]
|
||||||
|
assert flow.get("step_id") == "reauth_confirm"
|
||||||
|
assert flow.get("handler") == DOMAIN
|
||||||
|
|
||||||
|
assert "context" in flow
|
||||||
|
assert flow["context"].get("source") == SOURCE_REAUTH
|
||||||
|
assert flow["context"].get("entry_id") == entry.entry_id
|
||||||
|
Loading…
x
Reference in New Issue
Block a user