diff --git a/homeassistant/components/motionmount/number.py b/homeassistant/components/motionmount/number.py index 3ad21abd068d..735c26013b9b 100644 --- a/homeassistant/components/motionmount/number.py +++ b/homeassistant/components/motionmount/number.py @@ -60,7 +60,12 @@ class MotionMountExtension(MotionMountEntity, NumberEntity): """Set the new value for extension.""" try: await self.mm.set_extension(int(value)) - except (TimeoutError, socket.gaierror) as ex: + except ( + ConnectionError, + TimeoutError, + socket.gaierror, + motionmount.NotConnectedError, + ) as ex: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="failed_communication", @@ -93,7 +98,12 @@ class MotionMountTurn(MotionMountEntity, NumberEntity): """Set the new value for turn.""" try: await self.mm.set_turn(int(value * -1)) - except (TimeoutError, socket.gaierror) as ex: + except ( + ConnectionError, + TimeoutError, + socket.gaierror, + motionmount.NotConnectedError, + ) as ex: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="failed_communication", diff --git a/homeassistant/components/motionmount/quality_scale.yaml b/homeassistant/components/motionmount/quality_scale.yaml index 2e8a33bd38ab..d9f175ddbb94 100644 --- a/homeassistant/components/motionmount/quality_scale.yaml +++ b/homeassistant/components/motionmount/quality_scale.yaml @@ -32,9 +32,7 @@ rules: unique-config-entry: done # Silver - action-exceptions: - status: exempt - comment: Integration does not have actions + action-exceptions: done config-entry-unloading: done docs-configuration-parameters: status: exempt diff --git a/homeassistant/components/motionmount/select.py b/homeassistant/components/motionmount/select.py index 84e38c139d32..3d82ee8c1629 100644 --- a/homeassistant/components/motionmount/select.py +++ b/homeassistant/components/motionmount/select.py @@ -138,7 +138,12 @@ class MotionMountPresets(MotionMountEntity, SelectEntity): try: await self.mm.go_to_preset(index) - except (TimeoutError, socket.gaierror) as ex: + except ( + ConnectionError, + TimeoutError, + socket.gaierror, + motionmount.NotConnectedError, + ) as ex: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="failed_communication", diff --git a/tests/components/motionmount/test_number.py b/tests/components/motionmount/test_number.py new file mode 100644 index 000000000000..138d72278fff --- /dev/null +++ b/tests/components/motionmount/test_number.py @@ -0,0 +1,94 @@ +"""Tests for the MotionMount Number platform.""" + +import socket +from unittest.mock import MagicMock + +import motionmount +import pytest + +from homeassistant.components.number import ( + ATTR_VALUE, + DOMAIN as NUMBER_DOMAIN, + SERVICE_SET_VALUE, +) +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError + +from tests.common import MockConfigEntry + +EXTENSION_ENTITY_ID = "number.my_motionmount_extension" +TURN_ENTITY_ID = "number.my_motionmount_turn" + + +@pytest.mark.parametrize( + ("entity_id", "value", "method", "expected"), + [ + pytest.param(EXTENSION_ENTITY_ID, 42, "set_extension", 42, id="extension"), + pytest.param(TURN_ENTITY_ID, 42, "set_turn", -42, id="turn"), + ], +) +async def test_set_value( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_motionmount: MagicMock, + entity_id: str, + value: int, + method: str, + expected: int, +) -> None: + """Test setting a value.""" + mock_motionmount.is_authenticated = True + mock_config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: entity_id, ATTR_VALUE: value}, + blocking=True, + ) + + getattr(mock_motionmount, method).assert_awaited_once_with(expected) + + +@pytest.mark.parametrize( + ("entity_id", "method"), + [ + pytest.param(EXTENSION_ENTITY_ID, "set_extension", id="extension"), + pytest.param(TURN_ENTITY_ID, "set_turn", id="turn"), + ], +) +@pytest.mark.parametrize( + "exception", + [ + pytest.param(ConnectionResetError, id="connection_reset"), + pytest.param(TimeoutError, id="timeout"), + pytest.param(socket.gaierror, id="gaierror"), + pytest.param(motionmount.NotConnectedError, id="not_connected"), + ], +) +async def test_set_value_communication_error( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_motionmount: MagicMock, + entity_id: str, + method: str, + exception: type[Exception], +) -> None: + """Test a communication error while setting a value raises HomeAssistantError.""" + mock_motionmount.is_authenticated = True + mock_config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + + getattr(mock_motionmount, method).side_effect = exception + + with pytest.raises(HomeAssistantError) as exc_info: + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: entity_id, ATTR_VALUE: 42}, + blocking=True, + ) + + assert exc_info.value.translation_key == "failed_communication" diff --git a/tests/components/motionmount/test_select.py b/tests/components/motionmount/test_select.py new file mode 100644 index 000000000000..d63a359696f0 --- /dev/null +++ b/tests/components/motionmount/test_select.py @@ -0,0 +1,82 @@ +"""Tests for the MotionMount Select platform.""" + +import socket +from unittest.mock import MagicMock + +import motionmount +import pytest + +from homeassistant.components.select import ( + ATTR_OPTION, + DOMAIN as SELECT_DOMAIN, + SERVICE_SELECT_OPTION, +) +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError + +from tests.common import MockConfigEntry + +PRESET_ENTITY_ID = "select.my_motionmount_preset" + + +@pytest.fixture +def mock_presets(mock_motionmount: MagicMock) -> MagicMock: + """Return a MotionMount mock with a stored preset.""" + mock_motionmount.is_authenticated = True + mock_motionmount.is_moving = False + mock_motionmount.extension = 0 + mock_motionmount.turn = 0 + mock_motionmount.get_presets.return_value = [motionmount.Preset(1, "Couch", 50, 25)] + return mock_motionmount + + +async def test_select_option( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_presets: MagicMock, +) -> None: + """Test selecting a preset.""" + mock_config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + {ATTR_ENTITY_ID: PRESET_ENTITY_ID, ATTR_OPTION: "Couch"}, + blocking=True, + ) + + mock_presets.go_to_preset.assert_awaited_once_with(1) + + +@pytest.mark.parametrize( + "exception", + [ + pytest.param(ConnectionResetError, id="connection_reset"), + pytest.param(TimeoutError, id="timeout"), + pytest.param(socket.gaierror, id="gaierror"), + pytest.param(motionmount.NotConnectedError, id="not_connected"), + ], +) +async def test_select_option_communication_error( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_presets: MagicMock, + exception: type[Exception], +) -> None: + """Test a communication error while selecting a preset raises HomeAssistantError.""" + mock_config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + + mock_presets.go_to_preset.side_effect = exception + + with pytest.raises(HomeAssistantError) as exc_info: + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + {ATTR_ENTITY_ID: PRESET_ENTITY_ID, ATTR_OPTION: "Couch"}, + blocking=True, + ) + + assert exc_info.value.translation_key == "failed_communication"