Add exception translation to NUT (#141629)

* Add exception translation and test cases

* Capitalize ID in error string

* Test translation placeholders, simplify test cases
This commit is contained in:
tdfountain 2025-03-28 08:43:16 -07:00 committed by GitHub
parent ef06d2c06e
commit 2121b943a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 83 additions and 39 deletions

View File

@ -79,9 +79,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: NutConfigEntry) -> bool:
try:
return await data.async_update()
except NUTLoginError as err:
raise ConfigEntryAuthFailed from err
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="device_authentication",
translation_placeholders={
"err": str(err),
},
) from err
except NUTError as err:
raise UpdateFailed(f"Error fetching UPS state: {err}") from err
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="data_fetch_error",
translation_placeholders={
"err": str(err),
},
) from err
coordinator = DataUpdateCoordinator(
hass,
@ -328,7 +340,12 @@ class PyNUTData:
await self._client.run_command(self._alias, command_name)
except NUTError as err:
raise HomeAssistantError(
f"Error running command {command_name}, {err}"
translation_domain=DOMAIN,
translation_key="nut_command_error",
translation_placeholders={
"command_name": command_name,
"err": str(err),
},
) from err
async def async_list_commands(self) -> set[str] | None:

View File

@ -51,7 +51,11 @@ async def async_call_action_from_config(
runtime_data = _get_runtime_data_from_device_id(hass, device_id)
if not runtime_data:
raise InvalidDeviceAutomationConfig(
f"Unable to find a NUT device with id {device_id}"
translation_domain=DOMAIN,
translation_key="device_invalid",
translation_placeholders={
"device_id": device_id,
},
)
await runtime_data.data.async_run_command(command_name)

View File

@ -217,5 +217,19 @@
"switch": {
"outlet_number_load_poweronoff": { "name": "Power outlet {outlet_name}" }
}
},
"exceptions": {
"data_fetch_error": {
"message": "Error fetching UPS state: {err}"
},
"device_authentication": {
"message": "Device authentication error: {err}"
},
"device_invalid": {
"message": "Unable to find a NUT device with ID {device_id}"
},
"nut_command_error": {
"message": "Error running command {command_name}, {err}"
}
}
}

View File

@ -15,6 +15,7 @@ from homeassistant.components.nut import DOMAIN
from homeassistant.components.nut.const import INTEGRATION_SUPPORTED_COMMANDS
from homeassistant.const import CONF_DEVICE_ID, CONF_TYPE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr
from homeassistant.setup import async_setup_component
@ -191,48 +192,39 @@ async def test_action(hass: HomeAssistant, device_registry: dr.DeviceRegistry) -
run_command.assert_called_with("someUps", "beeper.disable")
async def test_rund_command_exception(
async def test_run_command_exception(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test logged error if run command raises exception."""
"""Test if run command raises exception with translation."""
list_commands_return_value = {"beeper.enable": None}
error_message = "Something wrong happened"
run_command = AsyncMock(side_effect=NUTError(error_message))
command_name = "beeper.enable"
nut_error_message = "Something wrong happened"
run_command = AsyncMock(side_effect=NUTError(nut_error_message))
await async_init_integration(
hass,
list_vars={"ups.status": "OL"},
list_commands_return_value=list_commands_return_value,
list_ups={"ups1": "UPS 1"},
list_commands_return_value={command_name: None},
run_command=run_command,
)
device_entry = next(device for device in device_registry.devices.values())
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "event",
"event_type": "test_some_event",
},
"action": {
"domain": DOMAIN,
"device_id": device_entry.id,
"type": "beeper_enable",
},
},
]
},
platform = await device_automation.async_get_device_automation_platform(
hass, DOMAIN, DeviceAutomationType.ACTION
)
hass.bus.async_fire("test_some_event")
await hass.async_block_till_done()
assert error_message in caplog.text
error_message = f"Error running command {command_name}, {nut_error_message}"
with pytest.raises(HomeAssistantError, match=error_message):
await platform.async_call_action_from_config(
hass,
{
CONF_TYPE: command_name,
CONF_DEVICE_ID: device_entry.id,
},
{},
None,
)
async def test_action_exception_invalid_device(hass: HomeAssistant) -> None:
@ -248,10 +240,12 @@ async def test_action_exception_invalid_device(hass: HomeAssistant) -> None:
hass, DOMAIN, DeviceAutomationType.ACTION
)
with pytest.raises(InvalidDeviceAutomationConfig):
device_id = "invalid_device_id"
error_message = f"Unable to find a NUT device with ID {device_id}"
with pytest.raises(InvalidDeviceAutomationConfig, match=error_message):
await platform.async_call_action_from_config(
hass,
{CONF_TYPE: "beeper.enable", CONF_DEVICE_ID: "invalid_device_id"},
{CONF_TYPE: "beeper.enable", CONF_DEVICE_ID: device_id},
{},
None,
)

View File

@ -4,6 +4,7 @@ from copy import deepcopy
from unittest.mock import patch
from aionut import NUTError, NUTLoginError
import pytest
from homeassistant.components.nut.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
@ -56,7 +57,10 @@ async def test_async_setup_entry(hass: HomeAssistant) -> None:
assert not hass.data.get(DOMAIN)
async def test_config_not_ready(hass: HomeAssistant) -> None:
async def test_config_not_ready(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test for setup failure if connection to broker is missing."""
entry = MockConfigEntry(
domain=DOMAIN,
@ -64,6 +68,8 @@ async def test_config_not_ready(hass: HomeAssistant) -> None:
)
entry.add_to_hass(hass)
nut_error_message = "Something wrong happened"
error_message = f"Error fetching UPS state: {nut_error_message}"
with (
patch(
"homeassistant.components.nut.AIONUTClient.list_ups",
@ -71,15 +77,20 @@ async def test_config_not_ready(hass: HomeAssistant) -> None:
),
patch(
"homeassistant.components.nut.AIONUTClient.list_vars",
side_effect=NUTError,
side_effect=NUTError(nut_error_message),
),
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.state is ConfigEntryState.SETUP_RETRY
assert error_message in caplog.text
async def test_auth_fails(hass: HomeAssistant) -> None:
async def test_auth_fails(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test for setup failure if auth has changed."""
entry = MockConfigEntry(
domain=DOMAIN,
@ -87,6 +98,8 @@ async def test_auth_fails(hass: HomeAssistant) -> None:
)
entry.add_to_hass(hass)
nut_error_message = "Something wrong happened"
error_message = f"Device authentication error: {nut_error_message}"
with (
patch(
"homeassistant.components.nut.AIONUTClient.list_ups",
@ -94,13 +107,15 @@ async def test_auth_fails(hass: HomeAssistant) -> None:
),
patch(
"homeassistant.components.nut.AIONUTClient.list_vars",
side_effect=NUTLoginError,
side_effect=NUTLoginError(nut_error_message),
),
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.state is ConfigEntryState.SETUP_ERROR
assert error_message in caplog.text
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
assert flows[0]["context"]["source"] == "reauth"