diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index 5b188868819..dc260dffe96 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -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: diff --git a/homeassistant/components/nut/device_action.py b/homeassistant/components/nut/device_action.py index ffaa195deaf..86f7fe5a7e6 100644 --- a/homeassistant/components/nut/device_action.py +++ b/homeassistant/components/nut/device_action.py @@ -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) diff --git a/homeassistant/components/nut/strings.json b/homeassistant/components/nut/strings.json index 4d8ffd45475..4bde5742b64 100644 --- a/homeassistant/components/nut/strings.json +++ b/homeassistant/components/nut/strings.json @@ -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}" + } } } diff --git a/tests/components/nut/test_device_action.py b/tests/components/nut/test_device_action.py index 01675f928e3..ea6b7306a5f 100644 --- a/tests/components/nut/test_device_action.py +++ b/tests/components/nut/test_device_action.py @@ -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, ) diff --git a/tests/components/nut/test_init.py b/tests/components/nut/test_init.py index 0585696cef2..4f11ffb5bb0 100644 --- a/tests/components/nut/test_init.py +++ b/tests/components/nut/test_init.py @@ -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"