Add missing exception translations to LCN (#147723)

This commit is contained in:
Andre Lengwenus 2025-07-02 13:41:06 +02:00 committed by GitHub
parent cb8e076703
commit f77e6cc8fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 88 additions and 13 deletions

View File

@ -104,7 +104,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: LcnConfigEntry) -
) as ex: ) as ex:
await lcn_connection.async_close() await lcn_connection.async_close()
raise ConfigEntryNotReady( raise ConfigEntryNotReady(
f"Unable to connect to {config_entry.title}: {ex}" translation_domain=DOMAIN,
translation_key="cannot_connect",
translation_placeholders={
"config_entry_title": config_entry.title,
},
) from ex ) from ex
_LOGGER.info('LCN connected to "%s"', config_entry.title) _LOGGER.info('LCN connected to "%s"', config_entry.title)

View File

@ -26,6 +26,7 @@ from homeassistant.const import (
CONF_SWITCHES, CONF_SWITCHES,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
@ -100,7 +101,11 @@ def get_resource(domain_name: str, domain_data: ConfigType) -> str:
return cast(str, domain_data["setpoint"]) return cast(str, domain_data["setpoint"])
if domain_name == "scene": if domain_name == "scene":
return f"{domain_data['register']}{domain_data['scene']}" return f"{domain_data['register']}{domain_data['scene']}"
raise ValueError("Unknown domain") raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="invalid_domain",
translation_placeholders={CONF_DOMAIN: domain_name},
)
def generate_unique_id( def generate_unique_id(
@ -304,6 +309,8 @@ def get_device_config(
def is_states_string(states_string: str) -> list[str]: def is_states_string(states_string: str) -> list[str]:
"""Validate the given states string and return states list.""" """Validate the given states string and return states list."""
if len(states_string) != 8: if len(states_string) != 8:
raise ValueError("Invalid length of states string") raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="invalid_length_of_states_string"
)
states = {"1": "ON", "0": "OFF", "T": "TOGGLE", "-": "NOCHANGE"} states = {"1": "ON", "0": "OFF", "T": "TOGGLE", "-": "NOCHANGE"}
return [states[state_string] for state_string in states_string] return [states[state_string] for state_string in states_string]

View File

@ -19,7 +19,7 @@ rules:
test-before-setup: done test-before-setup: done
unique-config-entry: done unique-config-entry: done
# Silver # Silver
action-exceptions: todo action-exceptions: done
config-entry-unloading: done config-entry-unloading: done
docs-configuration-parameters: docs-configuration-parameters:
status: exempt status: exempt

View File

@ -330,8 +330,9 @@ class SendKeys(LcnServiceCall):
if (delay_time := service.data[CONF_TIME]) != 0: if (delay_time := service.data[CONF_TIME]) != 0:
hit = pypck.lcn_defs.SendKeyCommand.HIT hit = pypck.lcn_defs.SendKeyCommand.HIT
if pypck.lcn_defs.SendKeyCommand[service.data[CONF_STATE]] != hit: if pypck.lcn_defs.SendKeyCommand[service.data[CONF_STATE]] != hit:
raise ValueError( raise ServiceValidationError(
"Only hit command is allowed when sending deferred keys." translation_domain=DOMAIN,
translation_key="invalid_send_keys_action",
) )
delay_unit = pypck.lcn_defs.TimeUnit.parse(service.data[CONF_TIME_UNIT]) delay_unit = pypck.lcn_defs.TimeUnit.parse(service.data[CONF_TIME_UNIT])
await device_connection.send_keys_hit_deferred(keys, delay_time, delay_unit) await device_connection.send_keys_hit_deferred(keys, delay_time, delay_unit)
@ -368,8 +369,9 @@ class LockKeys(LcnServiceCall):
if (delay_time := service.data[CONF_TIME]) != 0: if (delay_time := service.data[CONF_TIME]) != 0:
if table_id != 0: if table_id != 0:
raise ValueError( raise ServiceValidationError(
"Only table A is allowed when locking keys for a specific time." translation_domain=DOMAIN,
translation_key="invalid_lock_keys_table",
) )
delay_unit = pypck.lcn_defs.TimeUnit.parse(service.data[CONF_TIME_UNIT]) delay_unit = pypck.lcn_defs.TimeUnit.parse(service.data[CONF_TIME_UNIT])
await device_connection.lock_keys_tab_a_temporary( await device_connection.lock_keys_tab_a_temporary(

View File

@ -414,11 +414,23 @@
} }
}, },
"exceptions": { "exceptions": {
"invalid_address": { "cannot_connect": {
"message": "LCN device for given address has not been configured." "message": "Unable to connect to {config_entry_title}."
}, },
"invalid_device_id": { "invalid_device_id": {
"message": "LCN device for given device ID has not been configured." "message": "LCN device for given device ID {device_id} has not been configured."
},
"invalid_domain": {
"message": "Invalid domain {domain}."
},
"invalid_send_keys_action": {
"message": "Invalid state for sending keys. Only 'hit' allowed for deferred sending."
},
"invalid_lock_keys_table": {
"message": "Invalid table for locking keys. Only table A allowed when locking for a specific time."
},
"invalid_length_of_states_string": {
"message": "Invalid length of states string. Expected 8 characters."
} }
} }
} }

View File

@ -30,6 +30,7 @@ from homeassistant.const import (
CONF_UNIT_OF_MEASUREMENT, CONF_UNIT_OF_MEASUREMENT,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from .conftest import ( from .conftest import (
@ -134,6 +135,23 @@ async def test_service_relays(
control_relays.assert_awaited_with(relay_states) control_relays.assert_awaited_with(relay_states)
# wrong states string
with (
patch.object(MockModuleConnection, "control_relays") as control_relays,
pytest.raises(HomeAssistantError) as exc_info,
):
await hass.services.async_call(
DOMAIN,
LcnService.RELAYS,
{
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_STATE: "0011TT--00",
},
blocking=True,
)
assert exc_info.value.translation_domain == DOMAIN
assert exc_info.value.translation_key == "invalid_length_of_states_string"
async def test_service_led( async def test_service_led(
hass: HomeAssistant, hass: HomeAssistant,
@ -328,7 +346,7 @@ async def test_service_send_keys_hit_deferred(
patch.object( patch.object(
MockModuleConnection, "send_keys_hit_deferred" MockModuleConnection, "send_keys_hit_deferred"
) as send_keys_hit_deferred, ) as send_keys_hit_deferred,
pytest.raises(ValueError), pytest.raises(ServiceValidationError) as exc_info,
): ):
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
@ -342,6 +360,8 @@ async def test_service_send_keys_hit_deferred(
}, },
blocking=True, blocking=True,
) )
assert exc_info.value.translation_domain == DOMAIN
assert exc_info.value.translation_key == "invalid_send_keys_action"
async def test_service_lock_keys( async def test_service_lock_keys(
@ -369,6 +389,24 @@ async def test_service_lock_keys(
lock_keys.assert_awaited_with(0, lock_states) lock_keys.assert_awaited_with(0, lock_states)
# wrong states string
with (
patch.object(MockModuleConnection, "lock_keys") as lock_keys,
pytest.raises(HomeAssistantError) as exc_info,
):
await hass.services.async_call(
DOMAIN,
LcnService.LOCK_KEYS,
{
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_TABLE: "a",
CONF_STATE: "0011TT--00",
},
blocking=True,
)
assert exc_info.value.translation_domain == DOMAIN
assert exc_info.value.translation_key == "invalid_length_of_states_string"
async def test_service_lock_keys_tab_a_temporary( async def test_service_lock_keys_tab_a_temporary(
hass: HomeAssistant, hass: HomeAssistant,
@ -406,7 +444,7 @@ async def test_service_lock_keys_tab_a_temporary(
patch.object( patch.object(
MockModuleConnection, "lock_keys_tab_a_temporary" MockModuleConnection, "lock_keys_tab_a_temporary"
) as lock_keys_tab_a_temporary, ) as lock_keys_tab_a_temporary,
pytest.raises(ValueError), pytest.raises(ServiceValidationError) as exc_info,
): ):
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
@ -420,6 +458,8 @@ async def test_service_lock_keys_tab_a_temporary(
}, },
blocking=True, blocking=True,
) )
assert exc_info.value.translation_domain == DOMAIN
assert exc_info.value.translation_key == "invalid_lock_keys_table"
async def test_service_dyn_text( async def test_service_dyn_text(

View File

@ -192,6 +192,16 @@ async def test_lcn_entities_add_command(
assert entity_config in entry.data[CONF_ENTITIES] assert entity_config in entry.data[CONF_ENTITIES]
# invalid domain
await client.send_json_auto_id(
{**ENTITIES_ADD_PAYLOAD, "entry_id": entry.entry_id, CONF_DOMAIN: "invalid"}
)
res = await client.receive_json()
assert not res["success"]
assert res["error"]["code"] == "home_assistant_error"
assert res["error"]["translation_key"] == "invalid_domain"
async def test_lcn_entities_delete_command( async def test_lcn_entities_delete_command(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator, entry: MockConfigEntry hass: HomeAssistant, hass_ws_client: WebSocketGenerator, entry: MockConfigEntry