1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-02 08:26:41 +01:00

Add exception translations to Tessie (#166047)

This commit is contained in:
Brett Adams
2026-03-24 08:20:36 +10:00
committed by GitHub
parent 745dc0e183
commit 2d4c96864b
8 changed files with 99 additions and 22 deletions

View File

@@ -7,7 +7,7 @@ from http import HTTPStatus
import logging
from typing import TYPE_CHECKING, Any
from aiohttp import ClientResponseError
from aiohttp import ClientError, ClientResponseError
from tesla_fleet_api.const import TeslaEnergyPeriod
from tesla_fleet_api.exceptions import InvalidToken, MissingToken, TeslaFleetError
from tesla_fleet_api.tessie import EnergySite
@@ -83,7 +83,15 @@ class TessieStateUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
except ClientResponseError as e:
if e.status == HTTPStatus.UNAUTHORIZED:
raise ConfigEntryAuthFailed from e
raise
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="cannot_connect",
) from e
except ClientError as e:
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="cannot_connect",
) from e
return flatten(vehicle)
@@ -123,7 +131,10 @@ class TessieEnergySiteLiveCoordinator(DataUpdateCoordinator[dict[str, Any]]):
except (InvalidToken, MissingToken) as e:
raise ConfigEntryAuthFailed from e
except TeslaFleetError as e:
raise UpdateFailed(e.message) from e
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="cannot_connect",
) from e
# Convert Wall Connectors from array to dict
data["wall_connectors"] = {
@@ -159,7 +170,10 @@ class TessieEnergySiteInfoCoordinator(DataUpdateCoordinator[dict[str, Any]]):
except (InvalidToken, MissingToken) as e:
raise ConfigEntryAuthFailed from e
except TeslaFleetError as e:
raise UpdateFailed(e.message) from e
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="cannot_connect",
) from e
return flatten(data)
@@ -197,7 +211,10 @@ class TessieEnergyHistoryCoordinator(DataUpdateCoordinator[dict[str, Any]]):
translation_key="auth_failed",
) from e
except TeslaFleetError as e:
raise UpdateFailed(e.message) from e
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="cannot_connect",
) from e
if (
not data

View File

@@ -4,7 +4,7 @@ from abc import abstractmethod
from collections.abc import Awaitable, Callable
from typing import Any
from aiohttp import ClientResponseError
from aiohttp import ClientError
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
@@ -103,8 +103,11 @@ class TessieEntity(TessieBaseEntity):
api_key=self._api_key,
**kargs,
)
except ClientResponseError as e:
raise HomeAssistantError from e
except ClientError as e:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="cannot_connect",
) from e
if response["result"] is False:
name: str = getattr(self, "name", self.entity_id)
reason: str = response.get("reason", "unknown")

View File

@@ -72,13 +72,7 @@ rules:
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations:
status: todo
comment: |
Most user-facing exceptions have translations (HomeAssistantError and
ServiceValidationError use translation keys from strings.json). Remaining:
entity.py raises bare HomeAssistantError for ClientResponseError, and
coordinators raise UpdateFailed with untranslated messages.
exception-translations: done
icon-translations: done
reconfiguration-flow: todo
repair-issues: todo

View File

@@ -631,6 +631,9 @@
"cable_connected": {
"message": "Charge cable is connected."
},
"cannot_connect": {
"message": "[%key:common::config_flow::error::cannot_connect%]"
},
"command_failed": {
"message": "Command failed, {message}"
},

View File

@@ -22,7 +22,13 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from .common import ERROR_UNKNOWN, TEST_RESPONSE, assert_entities, setup_platform
from .common import (
ERROR_CONNECTION,
ERROR_UNKNOWN,
TEST_RESPONSE,
assert_entities,
setup_platform,
)
async def test_climate(
@@ -130,6 +136,27 @@ async def test_errors(hass: HomeAssistant) -> None:
)
mock_set.assert_called_once()
assert error.value.__cause__ == ERROR_UNKNOWN
assert error.value.translation_domain == "tessie"
assert error.value.translation_key == "cannot_connect"
# Test setting climate on with connection error
with (
patch(
"homeassistant.components.tessie.climate.stop_climate",
side_effect=ERROR_CONNECTION,
) as mock_set,
pytest.raises(HomeAssistantError) as error,
):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: [entity_id]},
blocking=True,
)
mock_set.assert_called_once()
assert error.value.__cause__ == ERROR_CONNECTION
assert error.value.translation_domain == "tessie"
assert error.value.translation_key == "cannot_connect"
# Test setting climate with child presence detection error
with (

View File

@@ -7,6 +7,7 @@ import pytest
from tesla_fleet_api.exceptions import Forbidden, InvalidToken, MissingToken
from homeassistant.components.tessie import PLATFORMS
from homeassistant.components.tessie.const import DOMAIN
from homeassistant.components.tessie.coordinator import (
TESSIE_ENERGY_HISTORY_INTERVAL,
TESSIE_FLEET_API_SYNC_INTERVAL,
@@ -15,6 +16,7 @@ from homeassistant.components.tessie.coordinator import (
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_ON, STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import UpdateFailed
from .common import ERROR_AUTH, ERROR_CONNECTION, ERROR_UNKNOWN, setup_platform
@@ -43,13 +45,17 @@ async def test_coordinator_clienterror(
"""Tests that the coordinator handles client errors."""
mock_get_state.side_effect = ERROR_UNKNOWN
await setup_platform(hass, [Platform.BINARY_SENSOR])
entry = await setup_platform(hass, [Platform.BINARY_SENSOR])
coordinator = entry.runtime_data.vehicles[0].data_coordinator
freezer.tick(WAIT)
async_fire_time_changed(hass)
await hass.async_block_till_done()
mock_get_state.assert_called_once()
assert hass.states.get("binary_sensor.test_status").state == STATE_UNAVAILABLE
assert isinstance(coordinator.last_exception, UpdateFailed)
assert coordinator.last_exception.translation_domain == DOMAIN
assert coordinator.last_exception.translation_key == "cannot_connect"
async def test_coordinator_auth(
@@ -72,12 +78,16 @@ async def test_coordinator_connection(
"""Tests that the coordinator handles connection errors."""
mock_get_state.side_effect = ERROR_CONNECTION
await setup_platform(hass, [Platform.BINARY_SENSOR])
entry = await setup_platform(hass, [Platform.BINARY_SENSOR])
coordinator = entry.runtime_data.vehicles[0].data_coordinator
freezer.tick(WAIT)
async_fire_time_changed(hass)
await hass.async_block_till_done()
mock_get_state.assert_called_once()
assert hass.states.get("binary_sensor.test_status").state == STATE_UNAVAILABLE
assert isinstance(coordinator.last_exception, UpdateFailed)
assert coordinator.last_exception.translation_domain == DOMAIN
assert coordinator.last_exception.translation_key == "cannot_connect"
async def test_coordinator_live_error(
@@ -85,7 +95,9 @@ async def test_coordinator_live_error(
) -> None:
"""Tests that the energy live coordinator handles fleet errors."""
await setup_platform(hass, [Platform.SENSOR])
entry = await setup_platform(hass, [Platform.SENSOR])
coordinator = entry.runtime_data.energysites[0].live_coordinator
assert coordinator is not None
mock_live_status.reset_mock()
mock_live_status.side_effect = Forbidden
@@ -94,6 +106,9 @@ async def test_coordinator_live_error(
await hass.async_block_till_done()
mock_live_status.assert_called_once()
assert hass.states.get("sensor.energy_site_solar_power").state == STATE_UNAVAILABLE
assert isinstance(coordinator.last_exception, UpdateFailed)
assert coordinator.last_exception.translation_domain == DOMAIN
assert coordinator.last_exception.translation_key == "cannot_connect"
async def test_coordinator_info_error(
@@ -101,7 +116,8 @@ async def test_coordinator_info_error(
) -> None:
"""Tests that the energy info coordinator handles fleet errors."""
await setup_platform(hass, [Platform.SENSOR])
entry = await setup_platform(hass, [Platform.SENSOR])
coordinator = entry.runtime_data.energysites[0].info_coordinator
mock_site_info.reset_mock()
mock_site_info.side_effect = Forbidden
@@ -113,6 +129,9 @@ async def test_coordinator_info_error(
hass.states.get("sensor.energy_site_vpp_backup_reserve").state
== STATE_UNAVAILABLE
)
assert isinstance(coordinator.last_exception, UpdateFailed)
assert coordinator.last_exception.translation_domain == DOMAIN
assert coordinator.last_exception.translation_key == "cannot_connect"
@pytest.mark.parametrize(
@@ -144,7 +163,9 @@ async def test_coordinator_energy_history_error(
) -> None:
"""Tests that the energy history coordinator handles fleet errors."""
await setup_platform(hass, [Platform.SENSOR])
entry = await setup_platform(hass, [Platform.SENSOR])
coordinator = entry.runtime_data.energysites[0].history_coordinator
assert coordinator is not None
mock_energy_history.reset_mock()
mock_energy_history.side_effect = Forbidden
@@ -155,6 +176,9 @@ async def test_coordinator_energy_history_error(
assert (
hass.states.get("sensor.energy_site_grid_imported").state == STATE_UNAVAILABLE
)
assert isinstance(coordinator.last_exception, UpdateFailed)
assert coordinator.last_exception.translation_domain == DOMAIN
assert coordinator.last_exception.translation_key == "cannot_connect"
async def test_coordinator_energy_history_invalid_data(
@@ -162,7 +186,9 @@ async def test_coordinator_energy_history_invalid_data(
) -> None:
"""Tests that the energy history coordinator handles invalid data."""
await setup_platform(hass, [Platform.SENSOR])
entry = await setup_platform(hass, [Platform.SENSOR])
coordinator = entry.runtime_data.energysites[0].history_coordinator
assert coordinator is not None
mock_energy_history.reset_mock()
mock_energy_history.side_effect = lambda *a, **kw: {"response": {}}
@@ -173,3 +199,6 @@ async def test_coordinator_energy_history_invalid_data(
assert (
hass.states.get("sensor.energy_site_grid_imported").state == STATE_UNAVAILABLE
)
assert isinstance(coordinator.last_exception, UpdateFailed)
assert coordinator.last_exception.translation_domain == DOMAIN
assert coordinator.last_exception.translation_key == "invalid_energy_history_data"

View File

@@ -96,6 +96,8 @@ async def test_errors(hass: HomeAssistant) -> None:
)
mock_set.assert_called_once()
assert error.value.__cause__ == ERROR_UNKNOWN
assert error.value.translation_domain == "tessie"
assert error.value.translation_key == "cannot_connect"
# Test setting cover open with unknown error
with (

View File

@@ -125,6 +125,8 @@ async def test_errors(hass: HomeAssistant) -> None:
)
mock_set.assert_called_once()
assert error.value.__cause__ == ERROR_UNKNOWN
assert error.value.translation_domain == "tessie"
assert error.value.translation_key == "cannot_connect"
# Test changing energy select with unknown error
with (