diff --git a/tests/components/velbus/conftest.py b/tests/components/velbus/conftest.py index 8e5322e6e05..1007b435212 100644 --- a/tests/components/velbus/conftest.py +++ b/tests/components/velbus/conftest.py @@ -4,7 +4,7 @@ from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch import pytest -from velbusaio.channels import Button, Relay +from velbusaio.channels import Button, Relay, Temperature from homeassistant.components.velbus import VelbusConfigEntry from homeassistant.components.velbus.const import DOMAIN @@ -18,7 +18,9 @@ from tests.common import MockConfigEntry @pytest.fixture(name="controller") def mock_controller( - mock_button: AsyncMock, mock_relay: AsyncMock + mock_button: AsyncMock, + mock_relay: AsyncMock, + mock_temperature: AsyncMock, ) -> Generator[AsyncMock]: """Mock a successful velbus controller.""" with ( @@ -32,6 +34,7 @@ def mock_controller( cont.get_all_binary_sensor.return_value = [mock_button] cont.get_all_button.return_value = [mock_button] cont.get_all_switch.return_value = [mock_relay] + cont.get_all_climate.return_value = [mock_temperature] yield controller @@ -51,6 +54,31 @@ def mock_button() -> AsyncMock: return channel +@pytest.fixture +def mock_temperature() -> AsyncMock: + """Mock a successful velbus channel.""" + channel = AsyncMock(spec=Temperature) + channel.get_categories.return_value = ["sensor", "climate"] + channel.get_name.return_value = "Temperature" + channel.get_module_address.return_value = 88 + channel.get_channel_number.return_value = 3 + channel.get_module_type_name.return_value = "VMB4GPO" + channel.get_full_name.return_value = "Channel full name" + channel.get_module_sw_version.return_value = "3.0.0" + channel.get_module_serial.return_value = "asdfghjk" + channel.get_class.return_value = "temperature" + channel.get_unit.return_value = "°C" + channel.get_state.return_value = 20.0 + channel.is_temperature.return_value = True + channel.get_max.return_value = 30.0 + channel.get_min.return_value = 10.0 + channel.get_climate_target.return_value = 21.0 + channel.get_climate_preset.return_value = 1 + channel.get_climate_mode.return_value = "day" + channel.get_cool_mode.return_value = "heat" + return channel + + @pytest.fixture def mock_relay() -> AsyncMock: """Mock a successful velbus channel.""" diff --git a/tests/components/velbus/snapshots/test_climate.ambr b/tests/components/velbus/snapshots/test_climate.ambr new file mode 100644 index 00000000000..567e45d9299 --- /dev/null +++ b/tests/components/velbus/snapshots/test_climate.ambr @@ -0,0 +1,76 @@ +# serializer version: 1 +# name: test_entities[climate.temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'hvac_modes': list([ + , + , + ]), + 'max_temp': 35, + 'min_temp': 7, + 'preset_modes': list([ + 'eco', + 'away', + 'home', + 'comfort', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'climate', + 'entity_category': None, + 'entity_id': 'climate.temperature', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Temperature', + 'platform': 'velbus', + 'previous_unique_id': None, + 'supported_features': , + 'translation_key': None, + 'unique_id': 'asdfghjk-3', + 'unit_of_measurement': None, + }) +# --- +# name: test_entities[climate.temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'current_temperature': 20.0, + 'friendly_name': 'Temperature', + 'hvac_modes': list([ + , + , + ]), + 'max_temp': 35, + 'min_temp': 7, + 'preset_mode': None, + 'preset_modes': list([ + 'eco', + 'away', + 'home', + 'comfort', + ]), + 'supported_features': , + 'temperature': 21.0, + }), + 'context': , + 'entity_id': 'climate.temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'cool', + }) +# --- diff --git a/tests/components/velbus/test_climate.py b/tests/components/velbus/test_climate.py new file mode 100644 index 00000000000..fd0a268bb0f --- /dev/null +++ b/tests/components/velbus/test_climate.py @@ -0,0 +1,125 @@ +"""Velbus climate platform tests.""" + +from unittest.mock import AsyncMock, patch + +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.climate import ( + ATTR_HVAC_MODE, + ATTR_PRESET_MODE, + DOMAIN as CLIMATE_DOMAIN, + PRESET_AWAY, + PRESET_COMFORT, + PRESET_ECO, + PRESET_HOME, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_PRESET_MODE, + SERVICE_SET_TEMPERATURE, +) +from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ServiceValidationError +from homeassistant.helpers import entity_registry as er + +from . import init_integration + +from tests.common import MockConfigEntry, snapshot_platform + + +async def test_entities( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, +) -> None: + """Test all entities.""" + with patch("homeassistant.components.velbus.PLATFORMS", [Platform.CLIMATE]): + await init_integration(hass, config_entry) + + await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id) + + +async def test_set_target_temperature( + hass: HomeAssistant, + mock_temperature: AsyncMock, + config_entry: MockConfigEntry, +) -> None: + """Test the target temperature climate action.""" + await init_integration(hass, config_entry) + + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: "climate.temperature", ATTR_TEMPERATURE: 29}, + blocking=True, + ) + mock_temperature.set_temp.assert_called_once_with(29) + + +@pytest.mark.parametrize( + ("set_mode", "expected_mode"), + [ + (PRESET_AWAY, "night"), + (PRESET_COMFORT, "comfort"), + (PRESET_ECO, "safe"), + (PRESET_HOME, "day"), + ], +) +async def test_set_preset_mode( + hass: HomeAssistant, + mock_temperature: AsyncMock, + config_entry: MockConfigEntry, + set_mode: str, + expected_mode: str, +) -> None: + """Test the preset mode climate action.""" + await init_integration(hass, config_entry) + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: "climate.temperature", ATTR_PRESET_MODE: set_mode}, + blocking=True, + ) + mock_temperature.set_preset.assert_called_once_with(expected_mode) + + +@pytest.mark.parametrize( + ("set_mode"), + [ + ("heat"), + ("cool"), + ], +) +async def test_set_hvac_mode( + hass: HomeAssistant, + mock_temperature: AsyncMock, + config_entry: MockConfigEntry, + set_mode: str, +) -> None: + """Test the hvac mode climate action.""" + await init_integration(hass, config_entry) + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: "climate.temperature", ATTR_HVAC_MODE: set_mode}, + blocking=True, + ) + mock_temperature.set_mode.assert_called_once_with(set_mode) + + +async def test_set_hvac_mode_invalid( + hass: HomeAssistant, + mock_temperature: AsyncMock, + config_entry: MockConfigEntry, +) -> None: + """Test the hvac mode climate action with an invalid mode.""" + await init_integration(hass, config_entry) + with pytest.raises(ServiceValidationError): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: "climate.temperature", ATTR_HVAC_MODE: "auto"}, + blocking=True, + ) + mock_temperature.set_mode.assert_not_called()