diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index d8907b0db9d..370548d67b0 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -307,17 +307,16 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): def set_fan_mode(self, fan_mode: str) -> None: """Set new target fan mode.""" if TYPE_CHECKING: - # We can rely on supported_features from __init__ + # guarded by ClimateEntityFeature.FAN_MODE assert self._fan_mode_dp_code is not None self._send_command([{"code": self._fan_mode_dp_code, "value": fan_mode}]) def set_humidity(self, humidity: int) -> None: """Set new target humidity.""" - if self._set_humidity is None: - raise RuntimeError( - "Cannot set humidity, device doesn't provide methods to set it" - ) + if TYPE_CHECKING: + # guarded by ClimateEntityFeature.TARGET_HUMIDITY + assert self._set_humidity is not None self._send_command( [ @@ -355,11 +354,9 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): def set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - if self._set_temperature is None: - raise RuntimeError( - "Cannot set target temperature, device doesn't provide methods to" - " set it" - ) + if TYPE_CHECKING: + # guarded by ClimateEntityFeature.TARGET_TEMPERATURE + assert self._set_temperature is not None self._send_command( [ diff --git a/homeassistant/components/tuya/cover.py b/homeassistant/components/tuya/cover.py index a385a35d903..205a65431dd 100644 --- a/homeassistant/components/tuya/cover.py +++ b/homeassistant/components/tuya/cover.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Any +from typing import TYPE_CHECKING, Any from tuya_sharing import CustomerDevice, Manager @@ -333,10 +333,9 @@ class TuyaCoverEntity(TuyaEntity, CoverEntity): def set_cover_position(self, **kwargs: Any) -> None: """Move the cover to a specific position.""" - if self._set_position is None: - raise RuntimeError( - "Cannot set position, device doesn't provide methods to set it" - ) + if TYPE_CHECKING: + # guarded by CoverEntityFeature.SET_POSITION + assert self._set_position is not None self._send_command( [ @@ -364,10 +363,9 @@ class TuyaCoverEntity(TuyaEntity, CoverEntity): def set_cover_tilt_position(self, **kwargs: Any) -> None: """Move the cover tilt to a specific position.""" - if self._tilt is None: - raise RuntimeError( - "Cannot set tilt, device doesn't provide methods to set it" - ) + if TYPE_CHECKING: + # guarded by CoverEntityFeature.SET_TILT_POSITION + assert self._tilt is not None self._send_command( [ diff --git a/tests/components/tuya/test_climate.py b/tests/components/tuya/test_climate.py index 9c0e3c31a26..e8aee3f4f96 100644 --- a/tests/components/tuya/test_climate.py +++ b/tests/components/tuya/test_climate.py @@ -11,6 +11,8 @@ from tuya_sharing import CustomerDevice from homeassistant.components.climate import ( DOMAIN as CLIMATE_DOMAIN, SERVICE_SET_FAN_MODE, + SERVICE_SET_HUMIDITY, + SERVICE_SET_TEMPERATURE, ) from homeassistant.components.tuya import ManagerCompat from homeassistant.const import Platform @@ -62,6 +64,36 @@ async def test_platform_setup_no_discovery( ) +@pytest.mark.parametrize( + "mock_device_code", + ["kt_serenelife_slpac905wuk_air_conditioner"], +) +async def test_set_temperature( + hass: HomeAssistant, + mock_manager: ManagerCompat, + mock_config_entry: MockConfigEntry, + mock_device: CustomerDevice, +) -> None: + """Test set temperature service.""" + entity_id = "climate.air_conditioner" + await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) + + state = hass.states.get(entity_id) + assert state is not None, f"{entity_id} does not exist" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + { + "entity_id": entity_id, + "temperature": 22.7, + }, + ) + await hass.async_block_till_done() + mock_manager.send_commands.assert_called_once_with( + mock_device.id, [{"code": "temp_set", "value": 22}] + ) + + @pytest.mark.parametrize( "mock_device_code", ["kt_serenelife_slpac905wuk_air_conditioner"], @@ -125,3 +157,31 @@ async def test_fan_mode_no_valid_code( }, blocking=True, ) + + +@pytest.mark.parametrize( + "mock_device_code", + ["kt_serenelife_slpac905wuk_air_conditioner"], +) +async def test_set_humidity_not_supported( + hass: HomeAssistant, + mock_manager: ManagerCompat, + mock_config_entry: MockConfigEntry, + mock_device: CustomerDevice, +) -> None: + """Test set humidity service (not available on this device).""" + entity_id = "climate.air_conditioner" + await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) + + state = hass.states.get(entity_id) + assert state is not None, f"{entity_id} does not exist" + with pytest.raises(ServiceNotSupported): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HUMIDITY, + { + "entity_id": entity_id, + "humidity": 50, + }, + blocking=True, + ) diff --git a/tests/components/tuya/test_cover.py b/tests/components/tuya/test_cover.py index 29a6d65978f..24e43dcccec 100644 --- a/tests/components/tuya/test_cover.py +++ b/tests/components/tuya/test_cover.py @@ -8,9 +8,17 @@ import pytest from syrupy.assertion import SnapshotAssertion from tuya_sharing import CustomerDevice +from homeassistant.components.cover import ( + DOMAIN as COVER_DOMAIN, + SERVICE_CLOSE_COVER, + SERVICE_OPEN_COVER, + SERVICE_SET_COVER_POSITION, + SERVICE_SET_COVER_TILT_POSITION, +) from homeassistant.components.tuya import ManagerCompat from homeassistant.const import Platform from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ServiceNotSupported from homeassistant.helpers import entity_registry as er from . import DEVICE_MOCKS, initialize_entry @@ -57,6 +65,107 @@ async def test_platform_setup_no_discovery( ) +@pytest.mark.parametrize( + "mock_device_code", + ["cl_am43_corded_motor_zigbee_cover"], +) +@patch("homeassistant.components.tuya.PLATFORMS", [Platform.COVER]) +async def test_open_service( + hass: HomeAssistant, + mock_manager: ManagerCompat, + mock_config_entry: MockConfigEntry, + mock_device: CustomerDevice, +) -> None: + """Test open service.""" + entity_id = "cover.kitchen_blinds_curtain" + await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) + + state = hass.states.get(entity_id) + assert state is not None, f"{entity_id} does not exist" + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_OPEN_COVER, + { + "entity_id": entity_id, + }, + ) + await hass.async_block_till_done() + mock_manager.send_commands.assert_called_once_with( + mock_device.id, + [ + {"code": "control", "value": "open"}, + {"code": "percent_control", "value": 0}, + ], + ) + + +@pytest.mark.parametrize( + "mock_device_code", + ["cl_am43_corded_motor_zigbee_cover"], +) +@patch("homeassistant.components.tuya.PLATFORMS", [Platform.COVER]) +async def test_close_service( + hass: HomeAssistant, + mock_manager: ManagerCompat, + mock_config_entry: MockConfigEntry, + mock_device: CustomerDevice, +) -> None: + """Test close service.""" + entity_id = "cover.kitchen_blinds_curtain" + await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) + + state = hass.states.get(entity_id) + assert state is not None, f"{entity_id} does not exist" + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_CLOSE_COVER, + { + "entity_id": entity_id, + }, + ) + await hass.async_block_till_done() + mock_manager.send_commands.assert_called_once_with( + mock_device.id, + [ + {"code": "control", "value": "close"}, + {"code": "percent_control", "value": 100}, + ], + ) + + +@pytest.mark.parametrize( + "mock_device_code", + ["cl_am43_corded_motor_zigbee_cover"], +) +async def test_set_position( + hass: HomeAssistant, + mock_manager: ManagerCompat, + mock_config_entry: MockConfigEntry, + mock_device: CustomerDevice, +) -> None: + """Test set position service (not available on this device).""" + entity_id = "cover.kitchen_blinds_curtain" + await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) + + state = hass.states.get(entity_id) + assert state is not None, f"{entity_id} does not exist" + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_SET_COVER_POSITION, + { + "entity_id": entity_id, + "position": 25, + }, + ) + await hass.async_block_till_done() + mock_manager.send_commands.assert_called_once_with( + mock_device.id, + [ + {"code": "percent_control", "value": 75}, + ], + ) + + @pytest.mark.parametrize( "mock_device_code", ["cl_am43_corded_motor_zigbee_cover"], @@ -89,3 +198,31 @@ async def test_percent_state_on_cover( state = hass.states.get(entity_id) assert state is not None, f"{entity_id} does not exist" assert state.attributes["current_position"] == percent_state + + +@pytest.mark.parametrize( + "mock_device_code", + ["cl_am43_corded_motor_zigbee_cover"], +) +async def test_set_tilt_position_not_supported( + hass: HomeAssistant, + mock_manager: ManagerCompat, + mock_config_entry: MockConfigEntry, + mock_device: CustomerDevice, +) -> None: + """Test set tilt position service (not available on this device).""" + entity_id = "cover.kitchen_blinds_curtain" + await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) + + state = hass.states.get(entity_id) + assert state is not None, f"{entity_id} does not exist" + with pytest.raises(ServiceNotSupported): + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_SET_COVER_TILT_POSITION, + { + "entity_id": entity_id, + "tilt_position": 50, + }, + blocking=True, + )