Improve BMW translations (#133236)

This commit is contained in:
Richard Kroegel 2024-12-15 12:16:10 +01:00 committed by GitHub
parent ebc8ca8419
commit 8953ac1357
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 209 additions and 64 deletions

View File

@ -16,7 +16,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import BMWConfigEntry from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
from .entity import BMWBaseEntity from .entity import BMWBaseEntity
if TYPE_CHECKING: if TYPE_CHECKING:
@ -55,7 +55,6 @@ BUTTON_TYPES: tuple[BMWButtonEntityDescription, ...] = (
BMWButtonEntityDescription( BMWButtonEntityDescription(
key="deactivate_air_conditioning", key="deactivate_air_conditioning",
translation_key="deactivate_air_conditioning", translation_key="deactivate_air_conditioning",
name="Deactivate air conditioning",
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning_stop(), remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning_stop(),
is_available=lambda vehicle: vehicle.is_remote_climate_stop_enabled, is_available=lambda vehicle: vehicle.is_remote_climate_stop_enabled,
), ),
@ -111,6 +110,10 @@ class BMWButton(BMWBaseEntity, ButtonEntity):
try: try:
await self.entity_description.remote_function(self.vehicle) await self.entity_description.remote_function(self.vehicle)
except MyBMWAPIError as ex: except MyBMWAPIError as ex:
raise HomeAssistantError(ex) from ex raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex
self.coordinator.async_update_listeners() self.coordinator.async_update_listeners()

View File

@ -22,7 +22,13 @@ from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util.ssl import get_default_context from homeassistant.util.ssl import get_default_context
from .const import CONF_GCID, CONF_READ_ONLY, CONF_REFRESH_TOKEN, DOMAIN, SCAN_INTERVALS from .const import (
CONF_GCID,
CONF_READ_ONLY,
CONF_REFRESH_TOKEN,
DOMAIN as BMW_DOMAIN,
SCAN_INTERVALS,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -57,7 +63,7 @@ class BMWDataUpdateCoordinator(DataUpdateCoordinator[None]):
hass, hass,
_LOGGER, _LOGGER,
config_entry=config_entry, config_entry=config_entry,
name=f"{DOMAIN}-{config_entry.data[CONF_USERNAME]}", name=f"{BMW_DOMAIN}-{config_entry.data[CONF_USERNAME]}",
update_interval=timedelta( update_interval=timedelta(
seconds=SCAN_INTERVALS[config_entry.data[CONF_REGION]] seconds=SCAN_INTERVALS[config_entry.data[CONF_REGION]]
), ),
@ -75,18 +81,29 @@ class BMWDataUpdateCoordinator(DataUpdateCoordinator[None]):
except MyBMWCaptchaMissingError as err: except MyBMWCaptchaMissingError as err:
# If a captcha is required (user/password login flow), always trigger the reauth flow # If a captcha is required (user/password login flow), always trigger the reauth flow
raise ConfigEntryAuthFailed( raise ConfigEntryAuthFailed(
translation_domain=DOMAIN, translation_domain=BMW_DOMAIN,
translation_key="missing_captcha", translation_key="missing_captcha",
) from err ) from err
except MyBMWAuthError as err: except MyBMWAuthError as err:
# Allow one retry interval before raising AuthFailed to avoid flaky API issues # Allow one retry interval before raising AuthFailed to avoid flaky API issues
if self.last_update_success: if self.last_update_success:
raise UpdateFailed(err) from err raise UpdateFailed(
translation_domain=BMW_DOMAIN,
translation_key="update_failed",
translation_placeholders={"exception": str(err)},
) from err
# Clear refresh token and trigger reauth if previous update failed as well # Clear refresh token and trigger reauth if previous update failed as well
self._update_config_entry_refresh_token(None) self._update_config_entry_refresh_token(None)
raise ConfigEntryAuthFailed(err) from err raise ConfigEntryAuthFailed(
translation_domain=BMW_DOMAIN,
translation_key="invalid_auth",
) from err
except (MyBMWAPIError, RequestError) as err: except (MyBMWAPIError, RequestError) as err:
raise UpdateFailed(err) from err raise UpdateFailed(
translation_domain=BMW_DOMAIN,
translation_key="update_failed",
translation_placeholders={"exception": str(err)},
) from err
if self.account.refresh_token != old_refresh_token: if self.account.refresh_token != old_refresh_token:
self._update_config_entry_refresh_token(self.account.refresh_token) self._update_config_entry_refresh_token(self.account.refresh_token)

View File

@ -49,7 +49,7 @@ class BMWDeviceTracker(BMWBaseEntity, TrackerEntity):
_attr_force_update = False _attr_force_update = False
_attr_translation_key = "car" _attr_translation_key = "car"
_attr_icon = "mdi:car" _attr_name = None
def __init__( def __init__(
self, self,
@ -58,9 +58,7 @@ class BMWDeviceTracker(BMWBaseEntity, TrackerEntity):
) -> None: ) -> None:
"""Initialize the Tracker.""" """Initialize the Tracker."""
super().__init__(coordinator, vehicle) super().__init__(coordinator, vehicle)
self._attr_unique_id = vehicle.vin self._attr_unique_id = vehicle.vin
self._attr_name = None
@property @property
def extra_state_attributes(self) -> dict[str, Any]: def extra_state_attributes(self) -> dict[str, Any]:

View File

@ -14,7 +14,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import BMWConfigEntry from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
from .coordinator import BMWDataUpdateCoordinator from .coordinator import BMWDataUpdateCoordinator
from .entity import BMWBaseEntity from .entity import BMWBaseEntity
@ -70,7 +70,11 @@ class BMWLock(BMWBaseEntity, LockEntity):
# Set the state to unknown if the command fails # Set the state to unknown if the command fails
self._attr_is_locked = None self._attr_is_locked = None
self.async_write_ha_state() self.async_write_ha_state()
raise HomeAssistantError(ex) from ex raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex
finally: finally:
# Always update the listeners to get the latest state # Always update the listeners to get the latest state
self.coordinator.async_update_listeners() self.coordinator.async_update_listeners()
@ -90,7 +94,11 @@ class BMWLock(BMWBaseEntity, LockEntity):
# Set the state to unknown if the command fails # Set the state to unknown if the command fails
self._attr_is_locked = None self._attr_is_locked = None
self.async_write_ha_state() self.async_write_ha_state()
raise HomeAssistantError(ex) from ex raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex
finally: finally:
# Always update the listeners to get the latest state # Always update the listeners to get the latest state
self.coordinator.async_update_listeners() self.coordinator.async_update_listeners()

View File

@ -20,7 +20,7 @@ from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import DOMAIN, BMWConfigEntry from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
PARALLEL_UPDATES = 1 PARALLEL_UPDATES = 1
@ -92,7 +92,7 @@ class BMWNotificationService(BaseNotificationService):
except (vol.Invalid, TypeError, ValueError) as ex: except (vol.Invalid, TypeError, ValueError) as ex:
raise ServiceValidationError( raise ServiceValidationError(
translation_domain=DOMAIN, translation_domain=BMW_DOMAIN,
translation_key="invalid_poi", translation_key="invalid_poi",
translation_placeholders={ translation_placeholders={
"poi_exception": str(ex), "poi_exception": str(ex),
@ -106,4 +106,8 @@ class BMWNotificationService(BaseNotificationService):
try: try:
await vehicle.remote_services.trigger_send_poi(poi) await vehicle.remote_services.trigger_send_poi(poi)
except MyBMWAPIError as ex: except MyBMWAPIError as ex:
raise HomeAssistantError(ex) from ex raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex

View File

@ -18,7 +18,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import BMWConfigEntry from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
from .coordinator import BMWDataUpdateCoordinator from .coordinator import BMWDataUpdateCoordinator
from .entity import BMWBaseEntity from .entity import BMWBaseEntity
@ -109,6 +109,10 @@ class BMWNumber(BMWBaseEntity, NumberEntity):
try: try:
await self.entity_description.remote_service(self.vehicle, value) await self.entity_description.remote_service(self.vehicle, value)
except MyBMWAPIError as ex: except MyBMWAPIError as ex:
raise HomeAssistantError(ex) from ex raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex
self.coordinator.async_update_listeners() self.coordinator.async_update_listeners()

View File

@ -15,7 +15,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import BMWConfigEntry from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
from .coordinator import BMWDataUpdateCoordinator from .coordinator import BMWDataUpdateCoordinator
from .entity import BMWBaseEntity from .entity import BMWBaseEntity
@ -123,6 +123,10 @@ class BMWSelect(BMWBaseEntity, SelectEntity):
try: try:
await self.entity_description.remote_service(self.vehicle, option) await self.entity_description.remote_service(self.vehicle, option)
except MyBMWAPIError as ex: except MyBMWAPIError as ex:
raise HomeAssistantError(ex) from ex raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex
self.coordinator.async_update_listeners() self.coordinator.async_update_listeners()

View File

@ -2,11 +2,16 @@
"config": { "config": {
"step": { "step": {
"user": { "user": {
"description": "Enter your MyBMW/MINI Connected credentials.", "description": "Connect to your MyBMW/MINI Connected account to retrieve vehicle data.",
"data": { "data": {
"username": "[%key:common::config_flow::data::username%]", "username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]", "password": "[%key:common::config_flow::data::password%]",
"region": "ConnectedDrive Region" "region": "ConnectedDrive Region"
},
"data_description": {
"username": "The email address of your MyBMW/MINI Connected account.",
"password": "The password of your MyBMW/MINI Connected account.",
"region": "The region of your MyBMW/MINI Connected account."
} }
}, },
"captcha": { "captcha": {
@ -23,6 +28,9 @@
"description": "Update your MyBMW/MINI Connected password for account `{username}` in region `{region}`.", "description": "Update your MyBMW/MINI Connected password for account `{username}` in region `{region}`.",
"data": { "data": {
"password": "[%key:common::config_flow::data::password%]" "password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"password": "[%key:component::bmw_connected_drive::config::step::user::data_description::password%]"
} }
} }
}, },
@ -41,7 +49,10 @@
"step": { "step": {
"account_options": { "account_options": {
"data": { "data": {
"read_only": "Read-only (only sensors and notify, no execution of services, no lock)" "read_only": "Read-only mode"
},
"data_description": {
"read_only": "Only retrieve values and send POI data, but don't offer any services that can change the vehicle state."
} }
} }
} }
@ -83,6 +94,9 @@
"activate_air_conditioning": { "activate_air_conditioning": {
"name": "Activate air conditioning" "name": "Activate air conditioning"
}, },
"deactivate_air_conditioning": {
"name": "Deactivate air conditioning"
},
"find_vehicle": { "find_vehicle": {
"name": "Find vehicle" "name": "Find vehicle"
} }
@ -220,6 +234,15 @@
}, },
"missing_captcha": { "missing_captcha": {
"message": "Login requires captcha validation" "message": "Login requires captcha validation"
},
"invalid_auth": {
"message": "[%key:common::config_flow::error::invalid_auth%]"
},
"remote_service_error": {
"message": "Error executing remote service on vehicle. {exception}"
},
"update_failed": {
"message": "Error updating vehicle data. {exception}"
} }
} }
} }

View File

@ -14,7 +14,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import BMWConfigEntry from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
from .coordinator import BMWDataUpdateCoordinator from .coordinator import BMWDataUpdateCoordinator
from .entity import BMWBaseEntity from .entity import BMWBaseEntity
@ -111,8 +111,11 @@ class BMWSwitch(BMWBaseEntity, SwitchEntity):
try: try:
await self.entity_description.remote_service_on(self.vehicle) await self.entity_description.remote_service_on(self.vehicle)
except MyBMWAPIError as ex: except MyBMWAPIError as ex:
raise HomeAssistantError(ex) from ex raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex
self.coordinator.async_update_listeners() self.coordinator.async_update_listeners()
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
@ -120,6 +123,9 @@ class BMWSwitch(BMWBaseEntity, SwitchEntity):
try: try:
await self.entity_description.remote_service_off(self.vehicle) await self.entity_description.remote_service_off(self.vehicle)
except MyBMWAPIError as ex: except MyBMWAPIError as ex:
raise HomeAssistantError(ex) from ex raise HomeAssistantError(
translation_domain=BMW_DOMAIN,
translation_key="remote_service_error",
translation_placeholders={"exception": str(ex)},
) from ex
self.coordinator.async_update_listeners() self.coordinator.async_update_listeners()

View File

@ -48,6 +48,11 @@ FIXTURE_CONFIG_ENTRY = {
"unique_id": f"{FIXTURE_USER_INPUT[CONF_REGION]}-{FIXTURE_USER_INPUT[CONF_USERNAME]}", "unique_id": f"{FIXTURE_USER_INPUT[CONF_REGION]}-{FIXTURE_USER_INPUT[CONF_USERNAME]}",
} }
REMOTE_SERVICE_EXC_REASON = "HTTPStatusError: 502 Bad Gateway"
REMOTE_SERVICE_EXC_TRANSLATION = (
"Error executing remote service on vehicle. HTTPStatusError: 502 Bad Gateway"
)
async def setup_mocked_integration(hass: HomeAssistant) -> MockConfigEntry: async def setup_mocked_integration(hass: HomeAssistant) -> MockConfigEntry:
"""Mock a fully setup config entry and all components based on fixtures.""" """Mock a fully setup config entry and all components based on fixtures."""

View File

@ -13,7 +13,11 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from . import check_remote_service_call, setup_mocked_integration from . import (
REMOTE_SERVICE_EXC_TRANSLATION,
check_remote_service_call,
setup_mocked_integration,
)
from tests.common import snapshot_platform from tests.common import snapshot_platform
@ -81,11 +85,13 @@ async def test_service_call_fail(
monkeypatch.setattr( monkeypatch.setattr(
RemoteServices, RemoteServices,
"trigger_remote_service", "trigger_remote_service",
AsyncMock(side_effect=MyBMWRemoteServiceError), AsyncMock(
side_effect=MyBMWRemoteServiceError("HTTPStatusError: 502 Bad Gateway")
),
) )
# Test # Test
with pytest.raises(HomeAssistantError): with pytest.raises(HomeAssistantError, match=REMOTE_SERVICE_EXC_TRANSLATION):
await hass.services.async_call( await hass.services.async_call(
"button", "button",
"press", "press",

View File

@ -16,7 +16,12 @@ 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
from . import check_remote_service_call, setup_mocked_integration from . import (
REMOTE_SERVICE_EXC_REASON,
REMOTE_SERVICE_EXC_TRANSLATION,
check_remote_service_call,
setup_mocked_integration,
)
from tests.common import snapshot_platform from tests.common import snapshot_platform
from tests.components.recorder.common import async_wait_recording_done from tests.components.recorder.common import async_wait_recording_done
@ -118,11 +123,11 @@ async def test_service_call_fail(
monkeypatch.setattr( monkeypatch.setattr(
RemoteServices, RemoteServices,
"trigger_remote_service", "trigger_remote_service",
AsyncMock(side_effect=MyBMWRemoteServiceError), AsyncMock(side_effect=MyBMWRemoteServiceError(REMOTE_SERVICE_EXC_REASON)),
) )
# Test # Test
with pytest.raises(HomeAssistantError): with pytest.raises(HomeAssistantError, match=REMOTE_SERVICE_EXC_TRANSLATION):
await hass.services.async_call( await hass.services.async_call(
"lock", "lock",
service, service,

View File

@ -11,7 +11,11 @@ import respx
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from . import check_remote_service_call, setup_mocked_integration from . import (
REMOTE_SERVICE_EXC_TRANSLATION,
check_remote_service_call,
setup_mocked_integration,
)
async def test_legacy_notify_service_simple( async def test_legacy_notify_service_simple(
@ -68,21 +72,21 @@ async def test_legacy_notify_service_simple(
{ {
"latitude": POI_DATA.get("lat"), "latitude": POI_DATA.get("lat"),
}, },
"Invalid data for point of interest: required key not provided @ data['longitude']", r"Invalid data for point of interest: required key not provided @ data\['longitude'\]",
), ),
( (
{ {
"latitude": POI_DATA.get("lat"), "latitude": POI_DATA.get("lat"),
"longitude": "text", "longitude": "text",
}, },
"Invalid data for point of interest: invalid longitude for dictionary value @ data['longitude']", r"Invalid data for point of interest: invalid longitude for dictionary value @ data\['longitude'\]",
), ),
( (
{ {
"latitude": POI_DATA.get("lat"), "latitude": POI_DATA.get("lat"),
"longitude": 9999, "longitude": 9999,
}, },
"Invalid data for point of interest: invalid longitude for dictionary value @ data['longitude']", r"Invalid data for point of interest: invalid longitude for dictionary value @ data\['longitude'\]",
), ),
], ],
) )
@ -96,7 +100,7 @@ async def test_service_call_invalid_input(
# Setup component # Setup component
assert await setup_mocked_integration(hass) assert await setup_mocked_integration(hass)
with pytest.raises(ServiceValidationError) as exc: with pytest.raises(ServiceValidationError, match=exc_translation):
await hass.services.async_call( await hass.services.async_call(
"notify", "notify",
"bmw_connected_drive_ix_xdrive50", "bmw_connected_drive_ix_xdrive50",
@ -106,7 +110,6 @@ async def test_service_call_invalid_input(
}, },
blocking=True, blocking=True,
) )
assert str(exc.value) == exc_translation
@pytest.mark.usefixtures("bmw_fixture") @pytest.mark.usefixtures("bmw_fixture")
@ -132,11 +135,11 @@ async def test_service_call_fail(
monkeypatch.setattr( monkeypatch.setattr(
RemoteServices, RemoteServices,
"trigger_remote_service", "trigger_remote_service",
AsyncMock(side_effect=raised), AsyncMock(side_effect=raised("HTTPStatusError: 502 Bad Gateway")),
) )
# Test # Test
with pytest.raises(expected): with pytest.raises(expected, match=REMOTE_SERVICE_EXC_TRANSLATION):
await hass.services.async_call( await hass.services.async_call(
"notify", "notify",
"bmw_connected_drive_ix_xdrive50", "bmw_connected_drive_ix_xdrive50",

View File

@ -13,7 +13,12 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from . import check_remote_service_call, setup_mocked_integration from . import (
REMOTE_SERVICE_EXC_REASON,
REMOTE_SERVICE_EXC_TRANSLATION,
check_remote_service_call,
setup_mocked_integration,
)
from tests.common import snapshot_platform from tests.common import snapshot_platform
@ -89,7 +94,10 @@ async def test_service_call_invalid_input(
old_value = hass.states.get(entity_id).state old_value = hass.states.get(entity_id).state
# Test # Test
with pytest.raises(ValueError): with pytest.raises(
ValueError,
match="Target SoC must be an integer between 20 and 100 that is a multiple of 5.",
):
await hass.services.async_call( await hass.services.async_call(
"number", "number",
"set_value", "set_value",
@ -102,17 +110,32 @@ async def test_service_call_invalid_input(
@pytest.mark.usefixtures("bmw_fixture") @pytest.mark.usefixtures("bmw_fixture")
@pytest.mark.parametrize( @pytest.mark.parametrize(
("raised", "expected"), ("raised", "expected", "exc_translation"),
[ [
(MyBMWRemoteServiceError, HomeAssistantError), (
(MyBMWAPIError, HomeAssistantError), MyBMWRemoteServiceError(REMOTE_SERVICE_EXC_REASON),
(ValueError, ValueError), HomeAssistantError,
REMOTE_SERVICE_EXC_TRANSLATION,
),
(
MyBMWAPIError(REMOTE_SERVICE_EXC_REASON),
HomeAssistantError,
REMOTE_SERVICE_EXC_TRANSLATION,
),
(
ValueError(
"Target SoC must be an integer between 20 and 100 that is a multiple of 5."
),
ValueError,
"Target SoC must be an integer between 20 and 100 that is a multiple of 5.",
),
], ],
) )
async def test_service_call_fail( async def test_service_call_fail(
hass: HomeAssistant, hass: HomeAssistant,
raised: Exception, raised: Exception,
expected: Exception, expected: Exception,
exc_translation: str,
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
"""Test exception handling.""" """Test exception handling."""
@ -130,7 +153,7 @@ async def test_service_call_fail(
) )
# Test # Test
with pytest.raises(expected): with pytest.raises(expected, match=exc_translation):
await hass.services.async_call( await hass.services.async_call(
"number", "number",
"set_value", "set_value",

View File

@ -16,7 +16,12 @@ from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.translation import async_get_translations from homeassistant.helpers.translation import async_get_translations
from . import check_remote_service_call, setup_mocked_integration from . import (
REMOTE_SERVICE_EXC_REASON,
REMOTE_SERVICE_EXC_TRANSLATION,
check_remote_service_call,
setup_mocked_integration,
)
from tests.common import snapshot_platform from tests.common import snapshot_platform
@ -105,7 +110,10 @@ async def test_service_call_invalid_input(
old_value = hass.states.get(entity_id).state old_value = hass.states.get(entity_id).state
# Test # Test
with pytest.raises(ServiceValidationError): with pytest.raises(
ServiceValidationError,
match=f"Option {value} is not valid for entity {entity_id}",
):
await hass.services.async_call( await hass.services.async_call(
"select", "select",
"select_option", "select_option",
@ -118,17 +126,32 @@ async def test_service_call_invalid_input(
@pytest.mark.usefixtures("bmw_fixture") @pytest.mark.usefixtures("bmw_fixture")
@pytest.mark.parametrize( @pytest.mark.parametrize(
("raised", "expected"), ("raised", "expected", "exc_translation"),
[ [
(MyBMWRemoteServiceError, HomeAssistantError), (
(MyBMWAPIError, HomeAssistantError), MyBMWRemoteServiceError(REMOTE_SERVICE_EXC_REASON),
(ServiceValidationError, ServiceValidationError), HomeAssistantError,
REMOTE_SERVICE_EXC_TRANSLATION,
),
(
MyBMWAPIError(REMOTE_SERVICE_EXC_REASON),
HomeAssistantError,
REMOTE_SERVICE_EXC_TRANSLATION,
),
(
ServiceValidationError(
"Option 17 is not valid for entity select.i4_edrive40_ac_charging_limit"
),
ServiceValidationError,
"Option 17 is not valid for entity select.i4_edrive40_ac_charging_limit",
),
], ],
) )
async def test_service_call_fail( async def test_service_call_fail(
hass: HomeAssistant, hass: HomeAssistant,
raised: Exception, raised: Exception,
expected: Exception, expected: Exception,
exc_translation: str,
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
"""Test exception handling.""" """Test exception handling."""
@ -146,7 +169,7 @@ async def test_service_call_fail(
) )
# Test # Test
with pytest.raises(expected): with pytest.raises(expected, match=exc_translation):
await hass.services.async_call( await hass.services.async_call(
"select", "select",
"select_option", "select_option",

View File

@ -13,7 +13,12 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from . import check_remote_service_call, setup_mocked_integration from . import (
REMOTE_SERVICE_EXC_REASON,
REMOTE_SERVICE_EXC_TRANSLATION,
check_remote_service_call,
setup_mocked_integration,
)
from tests.common import snapshot_platform from tests.common import snapshot_platform
@ -75,17 +80,25 @@ async def test_service_call_success(
@pytest.mark.usefixtures("bmw_fixture") @pytest.mark.usefixtures("bmw_fixture")
@pytest.mark.parametrize( @pytest.mark.parametrize(
("raised", "expected"), ("raised", "expected", "exc_translation"),
[ [
(MyBMWRemoteServiceError, HomeAssistantError), (
(MyBMWAPIError, HomeAssistantError), MyBMWRemoteServiceError(REMOTE_SERVICE_EXC_REASON),
(ValueError, ValueError), HomeAssistantError,
REMOTE_SERVICE_EXC_TRANSLATION,
),
(
MyBMWAPIError(REMOTE_SERVICE_EXC_REASON),
HomeAssistantError,
REMOTE_SERVICE_EXC_TRANSLATION,
),
], ],
) )
async def test_service_call_fail( async def test_service_call_fail(
hass: HomeAssistant, hass: HomeAssistant,
raised: Exception, raised: Exception,
expected: Exception, expected: Exception,
exc_translation: str,
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
"""Test exception handling.""" """Test exception handling."""
@ -107,7 +120,7 @@ async def test_service_call_fail(
assert hass.states.get(entity_id).state == old_value assert hass.states.get(entity_id).state == old_value
# Test # Test
with pytest.raises(expected): with pytest.raises(expected, match=exc_translation):
await hass.services.async_call( await hass.services.async_call(
"switch", "switch",
"turn_on", "turn_on",
@ -122,7 +135,7 @@ async def test_service_call_fail(
assert hass.states.get(entity_id).state == old_value assert hass.states.get(entity_id).state == old_value
# Test # Test
with pytest.raises(expected): with pytest.raises(expected, match=exc_translation):
await hass.services.async_call( await hass.services.async_call(
"switch", "switch",
"turn_off", "turn_off",