Replace RuntimeError with TYPE_CHECKING in Tuya (#149227)

This commit is contained in:
epenet 2025-07-22 12:10:23 +02:00 committed by GitHub
parent f5d68a4ea4
commit 8d1c789ca2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 211 additions and 19 deletions

View File

@ -307,17 +307,16 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity):
def set_fan_mode(self, fan_mode: str) -> None: def set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode.""" """Set new target fan mode."""
if TYPE_CHECKING: 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 assert self._fan_mode_dp_code is not None
self._send_command([{"code": self._fan_mode_dp_code, "value": fan_mode}]) self._send_command([{"code": self._fan_mode_dp_code, "value": fan_mode}])
def set_humidity(self, humidity: int) -> None: def set_humidity(self, humidity: int) -> None:
"""Set new target humidity.""" """Set new target humidity."""
if self._set_humidity is None: if TYPE_CHECKING:
raise RuntimeError( # guarded by ClimateEntityFeature.TARGET_HUMIDITY
"Cannot set humidity, device doesn't provide methods to set it" assert self._set_humidity is not None
)
self._send_command( self._send_command(
[ [
@ -355,11 +354,9 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity):
def set_temperature(self, **kwargs: Any) -> None: def set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature.""" """Set new target temperature."""
if self._set_temperature is None: if TYPE_CHECKING:
raise RuntimeError( # guarded by ClimateEntityFeature.TARGET_TEMPERATURE
"Cannot set target temperature, device doesn't provide methods to" assert self._set_temperature is not None
" set it"
)
self._send_command( self._send_command(
[ [

View File

@ -3,7 +3,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import TYPE_CHECKING, Any
from tuya_sharing import CustomerDevice, Manager from tuya_sharing import CustomerDevice, Manager
@ -333,10 +333,9 @@ class TuyaCoverEntity(TuyaEntity, CoverEntity):
def set_cover_position(self, **kwargs: Any) -> None: def set_cover_position(self, **kwargs: Any) -> None:
"""Move the cover to a specific position.""" """Move the cover to a specific position."""
if self._set_position is None: if TYPE_CHECKING:
raise RuntimeError( # guarded by CoverEntityFeature.SET_POSITION
"Cannot set position, device doesn't provide methods to set it" assert self._set_position is not None
)
self._send_command( self._send_command(
[ [
@ -364,10 +363,9 @@ class TuyaCoverEntity(TuyaEntity, CoverEntity):
def set_cover_tilt_position(self, **kwargs: Any) -> None: def set_cover_tilt_position(self, **kwargs: Any) -> None:
"""Move the cover tilt to a specific position.""" """Move the cover tilt to a specific position."""
if self._tilt is None: if TYPE_CHECKING:
raise RuntimeError( # guarded by CoverEntityFeature.SET_TILT_POSITION
"Cannot set tilt, device doesn't provide methods to set it" assert self._tilt is not None
)
self._send_command( self._send_command(
[ [

View File

@ -11,6 +11,8 @@ from tuya_sharing import CustomerDevice
from homeassistant.components.climate import ( from homeassistant.components.climate import (
DOMAIN as CLIMATE_DOMAIN, DOMAIN as CLIMATE_DOMAIN,
SERVICE_SET_FAN_MODE, SERVICE_SET_FAN_MODE,
SERVICE_SET_HUMIDITY,
SERVICE_SET_TEMPERATURE,
) )
from homeassistant.components.tuya import ManagerCompat from homeassistant.components.tuya import ManagerCompat
from homeassistant.const import Platform 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( @pytest.mark.parametrize(
"mock_device_code", "mock_device_code",
["kt_serenelife_slpac905wuk_air_conditioner"], ["kt_serenelife_slpac905wuk_air_conditioner"],
@ -125,3 +157,31 @@ async def test_fan_mode_no_valid_code(
}, },
blocking=True, 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,
)

View File

@ -8,9 +8,17 @@ import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from tuya_sharing import CustomerDevice 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.components.tuya import ManagerCompat
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceNotSupported
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from . import DEVICE_MOCKS, initialize_entry 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( @pytest.mark.parametrize(
"mock_device_code", "mock_device_code",
["cl_am43_corded_motor_zigbee_cover"], ["cl_am43_corded_motor_zigbee_cover"],
@ -89,3 +198,31 @@ async def test_percent_state_on_cover(
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state is not None, f"{entity_id} does not exist" assert state is not None, f"{entity_id} does not exist"
assert state.attributes["current_position"] == percent_state 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,
)