mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 10:13:52 -05:00
fix: handle and translate OpenEVSE charger exceptions in number entities (#171368)
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
"""Helpers for OpenEVSE."""
|
||||
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
|
||||
from aiohttp import ContentTypeError, ServerTimeoutError
|
||||
from openevsehttp.exceptions import (
|
||||
AuthenticationError,
|
||||
ParseJSONError,
|
||||
UnsupportedFeature,
|
||||
)
|
||||
|
||||
from homeassistant.exceptions import (
|
||||
ConfigEntryAuthFailed,
|
||||
HomeAssistantError,
|
||||
ServiceValidationError,
|
||||
)
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
@contextmanager
|
||||
def openevse_exception_handler(value: float) -> Iterator[None]:
|
||||
"""Context manager to handle and translate OpenEVSE exceptions."""
|
||||
try:
|
||||
yield
|
||||
except ValueError as err:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_value",
|
||||
translation_placeholders={"value": str(value)},
|
||||
) from err
|
||||
except AuthenticationError as err:
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="authentication_error",
|
||||
) from err
|
||||
except UnsupportedFeature as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="unsupported_feature",
|
||||
) from err
|
||||
except (
|
||||
TimeoutError,
|
||||
ServerTimeoutError,
|
||||
ContentTypeError,
|
||||
ParseJSONError,
|
||||
) as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="communication_error",
|
||||
) from err
|
||||
@@ -24,6 +24,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import OpenEVSEConfigEntry, OpenEVSEDataUpdateCoordinator
|
||||
from .helpers import openevse_exception_handler
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
@@ -113,4 +114,5 @@ class OpenEVSENumber(CoordinatorEntity[OpenEVSEDataUpdateCoordinator], NumberEnt
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Set new value."""
|
||||
await self.entity_description.set_value_fn(self.coordinator.charger, value)
|
||||
with openevse_exception_handler(value):
|
||||
await self.entity_description.set_value_fn(self.coordinator.charger, value)
|
||||
|
||||
@@ -166,6 +166,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"authentication_error": {
|
||||
"message": "Authentication failed while communicating with the charger."
|
||||
},
|
||||
"communication_error": {
|
||||
"message": "Failed to communicate with the charger."
|
||||
},
|
||||
"invalid_value": {
|
||||
"message": "Value {value} is invalid for the charger."
|
||||
},
|
||||
"unsupported_feature": {
|
||||
"message": "The charger does not support this feature."
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_yaml_import_issue_unavailable_host": {
|
||||
"description": "Configuring {integration_title} using YAML is being removed but there was a connection error while trying to import the YAML configuration.\n\nEnsure your OpenEVSE charger is accessible and restart Home Assistant to try again.",
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from aiohttp import ContentTypeError, ServerTimeoutError
|
||||
from openevsehttp.exceptions import (
|
||||
AuthenticationError,
|
||||
ParseJSONError,
|
||||
UnsupportedFeature,
|
||||
)
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
@@ -10,8 +16,14 @@ from homeassistant.components.number import (
|
||||
DOMAIN as NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
)
|
||||
from homeassistant.components.openevse.const import DOMAIN
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import (
|
||||
ConfigEntryAuthFailed,
|
||||
HomeAssistantError,
|
||||
ServiceValidationError,
|
||||
)
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
@@ -50,3 +62,72 @@ async def test_set_value(
|
||||
blocking=True,
|
||||
)
|
||||
mock_charger.set_current.assert_called_once_with(32.0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("raised", "expected", "translation_key", "translation_placeholders"),
|
||||
[
|
||||
(
|
||||
ValueError("out of range"),
|
||||
ServiceValidationError,
|
||||
"invalid_value",
|
||||
{"value": "32.0"},
|
||||
),
|
||||
(
|
||||
AuthenticationError("bad creds"),
|
||||
ConfigEntryAuthFailed,
|
||||
"authentication_error",
|
||||
None,
|
||||
),
|
||||
(TimeoutError("timed out"), HomeAssistantError, "communication_error", None),
|
||||
(
|
||||
ServerTimeoutError("timed out"),
|
||||
HomeAssistantError,
|
||||
"communication_error",
|
||||
None,
|
||||
),
|
||||
(ParseJSONError("bad json"), HomeAssistantError, "communication_error", None),
|
||||
(
|
||||
UnsupportedFeature("old firmware"),
|
||||
HomeAssistantError,
|
||||
"unsupported_feature",
|
||||
None,
|
||||
),
|
||||
(
|
||||
ContentTypeError(MagicMock(), (), message="bad content"),
|
||||
HomeAssistantError,
|
||||
"communication_error",
|
||||
None,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_set_value_raises(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_charger: MagicMock,
|
||||
raised: Exception,
|
||||
expected: type[Exception],
|
||||
translation_key: str,
|
||||
translation_placeholders: dict[str, str] | None,
|
||||
) -> None:
|
||||
"""Test that errors from the charger are translated to HA exceptions."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_charger.set_current.side_effect = raised
|
||||
|
||||
with pytest.raises(expected) as exc_info:
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: "number.openevse_mock_config_charge_rate",
|
||||
ATTR_VALUE: 32.0,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert exc_info.value.translation_key == translation_key
|
||||
assert exc_info.value.translation_domain == DOMAIN
|
||||
assert exc_info.value.translation_placeholders == translation_placeholders
|
||||
|
||||
Reference in New Issue
Block a user