diff --git a/tests/components/evohome/conftest.py b/tests/components/evohome/conftest.py index 85ef0b5756d..38441cf56cd 100644 --- a/tests/components/evohome/conftest.py +++ b/tests/components/evohome/conftest.py @@ -11,12 +11,14 @@ from unittest.mock import MagicMock, patch from aiohttp import ClientSession from evohomeasync2 import EvohomeClient from evohomeasync2.broker import Broker +from evohomeasync2.zone import Zone import pytest from homeassistant.components.evohome import CONF_PASSWORD, CONF_USERNAME, DOMAIN +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component -from homeassistant.util import dt as dt_util +from homeassistant.util import dt as dt_util, slugify from homeassistant.util.json import JsonArrayType, JsonObjectType from .const import ACCESS_TOKEN, REFRESH_TOKEN, USERNAME @@ -173,3 +175,18 @@ async def evohome( async for mock_client in setup_evohome(hass, config, install=install): yield mock_client + + +@pytest.fixture +async def zone_id( + hass: HomeAssistant, + config: dict[str, str], + install: MagicMock, +) -> AsyncGenerator[str]: + """Return the entity_id of the evohome integration' first Climate zone.""" + + async for mock_client in setup_evohome(hass, config, install=install): + evo: EvohomeClient = mock_client.return_value + zone: Zone = list(evo._get_single_tcs().zones.values())[0] + + yield f"{Platform.CLIMATE}.{slugify(zone.name)}" diff --git a/tests/components/evohome/snapshots/test_climate.ambr b/tests/components/evohome/snapshots/test_climate.ambr index 1a77cf0e80d..861d761908b 100644 --- a/tests/components/evohome/snapshots/test_climate.ambr +++ b/tests/components/evohome/snapshots/test_climate.ambr @@ -2,189 +2,170 @@ # name: test_zone_set_hvac_mode[default] list([ tuple( - dict({ - 'HeatSetpointValue': 5.0, - 'setpointMode': , - }), + 5.0, ), ]) # --- # name: test_zone_set_hvac_mode[h032585] list([ tuple( - dict({ - 'HeatSetpointValue': 4.5, - 'setpointMode': , - }), + 4.5, ), ]) # --- # name: test_zone_set_hvac_mode[h099625] list([ tuple( - dict({ - 'HeatSetpointValue': 5.0, - 'setpointMode': , - }), + 5.0, ), ]) # --- # name: test_zone_set_hvac_mode[minimal] list([ tuple( - dict({ - 'HeatSetpointValue': 5.0, - 'setpointMode': , - }), + 5.0, ), ]) # --- # name: test_zone_set_hvac_mode[sys_004] list([ tuple( - dict({ - 'HeatSetpointValue': 5.0, - 'setpointMode': , - }), + 5.0, ), ]) # --- # name: test_zone_set_preset_mode[default] list([ tuple( - dict({ - 'HeatSetpointValue': 17.0, - 'setpointMode': , - }), + 17.0, ), tuple( - dict({ - 'HeatSetpointValue': 17.0, - 'setpointMode': , - 'timeUntil': '2024-07-10T21:10:00Z', - }), + 17.0, ), + dict({ + 'until': datetime.datetime(2024, 7, 10, 21, 10, tzinfo=datetime.timezone.utc), + }), ]) # --- # name: test_zone_set_preset_mode[h032585] list([ tuple( - dict({ - 'HeatSetpointValue': 21.5, - 'setpointMode': , - }), + 21.5, ), tuple( - dict({ - 'HeatSetpointValue': 21.5, - 'setpointMode': , - 'timeUntil': '2024-07-10T21:10:00Z', - }), + 21.5, ), + dict({ + 'until': datetime.datetime(2024, 7, 10, 21, 10, tzinfo=datetime.timezone.utc), + }), ]) # --- # name: test_zone_set_preset_mode[h099625] list([ tuple( - dict({ - 'HeatSetpointValue': 21.5, - 'setpointMode': , - }), + 21.5, ), tuple( - dict({ - 'HeatSetpointValue': 21.5, - 'setpointMode': , - 'timeUntil': '2024-07-10T19:10:00Z', - }), + 21.5, ), + dict({ + 'until': datetime.datetime(2024, 7, 10, 19, 10, tzinfo=datetime.timezone.utc), + }), ]) # --- # name: test_zone_set_preset_mode[minimal] list([ tuple( - dict({ - 'HeatSetpointValue': 17.0, - 'setpointMode': , - }), + 17.0, ), tuple( - dict({ - 'HeatSetpointValue': 17.0, - 'setpointMode': , - 'timeUntil': '2024-07-10T21:10:00Z', - }), + 17.0, ), + dict({ + 'until': datetime.datetime(2024, 7, 10, 21, 10, tzinfo=datetime.timezone.utc), + }), ]) # --- # name: test_zone_set_preset_mode[sys_004] list([ tuple( - dict({ - 'HeatSetpointValue': 15.0, - 'setpointMode': , - }), + 15.0, ), tuple( - dict({ - 'HeatSetpointValue': 15.0, - 'setpointMode': , - 'timeUntil': '2024-07-10T20:10:00Z', - }), + 15.0, ), + dict({ + 'until': datetime.datetime(2024, 7, 10, 20, 10, tzinfo=datetime.timezone.utc), + }), ]) # --- # name: test_zone_set_temperature[default] list([ - tuple( - dict({ - 'HeatSetpointValue': 19.1, - 'setpointMode': , - 'timeUntil': '2024-07-10T21:10:00Z', - }), - ), + dict({ + 'until': datetime.datetime(2024, 7, 10, 21, 10, tzinfo=datetime.timezone.utc), + }), ]) # --- # name: test_zone_set_temperature[h032585] list([ - tuple( - dict({ - 'HeatSetpointValue': 19.1, - 'setpointMode': , - 'timeUntil': '2024-07-10T21:10:00Z', - }), - ), + dict({ + 'until': datetime.datetime(2024, 7, 10, 21, 10, tzinfo=datetime.timezone.utc), + }), ]) # --- # name: test_zone_set_temperature[h099625] list([ - tuple( - dict({ - 'HeatSetpointValue': 19.1, - 'setpointMode': , - 'timeUntil': '2024-07-10T19:10:00Z', - }), - ), + dict({ + 'until': datetime.datetime(2024, 7, 10, 19, 10, tzinfo=datetime.timezone.utc), + }), ]) # --- # name: test_zone_set_temperature[minimal] list([ - tuple( - dict({ - 'HeatSetpointValue': 19.1, - 'setpointMode': , - 'timeUntil': '2024-07-10T21:10:00Z', - }), - ), + dict({ + 'until': datetime.datetime(2024, 7, 10, 21, 10, tzinfo=datetime.timezone.utc), + }), ]) # --- # name: test_zone_set_temperature[sys_004] + list([ + dict({ + 'until': None, + }), + ]) +# --- +# name: test_zone_turn_off[default] list([ tuple( - dict({ - 'HeatSetpointValue': 19.1, - 'setpointMode': , - }), + 5.0, + ), + ]) +# --- +# name: test_zone_turn_off[h032585] + list([ + tuple( + 4.5, + ), + ]) +# --- +# name: test_zone_turn_off[h099625] + list([ + tuple( + 5.0, + ), + ]) +# --- +# name: test_zone_turn_off[minimal] + list([ + tuple( + 5.0, + ), + ]) +# --- +# name: test_zone_turn_off[sys_004] + list([ + tuple( + 5.0, ), ]) # --- diff --git a/tests/components/evohome/test_climate.py b/tests/components/evohome/test_climate.py index 602a2ac561a..21fad33e9ec 100644 --- a/tests/components/evohome/test_climate.py +++ b/tests/components/evohome/test_climate.py @@ -11,78 +11,69 @@ from freezegun.api import FrozenDateTimeFactory import pytest from syrupy import SnapshotAssertion -from homeassistant.components.climate import HVACMode -from homeassistant.components.evohome import DOMAIN -from homeassistant.components.evohome.climate import EvoZone -from homeassistant.components.evohome.coordinator import EvoBroker -from homeassistant.const import Platform +from homeassistant.components.climate import ( + ATTR_HVAC_MODE, + ATTR_PRESET_MODE, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_PRESET_MODE, + SERVICE_SET_TEMPERATURE, + HVACMode, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_TEMPERATURE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + Platform, +) from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er -from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.util import dt as dt_util -from .conftest import setup_evohome from .const import TEST_INSTALLS -def get_zone_entity(hass: HomeAssistant) -> EvoZone: - """Return the entity of the first zone of the evohome system.""" - - broker: EvoBroker = hass.data[DOMAIN]["broker"] - - unique_id = broker.tcs._zones[0]._id - if unique_id == broker.tcs._id: - unique_id += "z" # special case of merged controller/zone - - entity_registry = er.async_get(hass) - entity_id = entity_registry.async_get_entity_id(Platform.CLIMATE, DOMAIN, unique_id) - - component: EntityComponent = hass.data.get(Platform.CLIMATE) # type: ignore[assignment] - return next(e for e in component.entities if e.entity_id == entity_id) # type: ignore[return-value] - - @pytest.mark.parametrize("install", TEST_INSTALLS) async def test_zone_set_hvac_mode( hass: HomeAssistant, - config: dict[str, str], - install: str, + zone_id: str, snapshot: SnapshotAssertion, ) -> None: - """Test climate methods of a evohome-compatible zone.""" + """Test SERVICE_SET_HVAC_MODE of an evohome zone Climate entity.""" results = [] - async for _ in setup_evohome(hass, config, install=install): - zone = get_zone_entity(hass) + # SERVICE_SET_HVAC_MODE: HVACMode.HEAT + with patch("evohomeasync2.zone.Zone.reset_mode") as mock_fcn: + await hass.services.async_call( + Platform.CLIMATE, + SERVICE_SET_HVAC_MODE, + { + ATTR_ENTITY_ID: zone_id, + ATTR_HVAC_MODE: HVACMode.HEAT, + }, + blocking=True, + ) - assert zone.hvac_modes == [HVACMode.OFF, HVACMode.HEAT] + assert mock_fcn.await_count == 1 + assert mock_fcn.await_args.args == () + assert mock_fcn.await_args.kwargs == {} - # set_hvac_mode(HVACMode.HEAT): FollowSchedule - with patch("evohomeasync2.zone.Zone._set_mode") as mock_fcn: - await zone.async_set_hvac_mode(HVACMode.HEAT) + # SERVICE_SET_HVAC_MODE: HVACMode.OFF + with patch("evohomeasync2.zone.Zone.set_temperature") as mock_fcn: + await hass.services.async_call( + Platform.CLIMATE, + SERVICE_SET_HVAC_MODE, + { + ATTR_ENTITY_ID: zone_id, + ATTR_HVAC_MODE: HVACMode.OFF, + }, + blocking=True, + ) - assert mock_fcn.await_count == 1 - assert install != "default" or mock_fcn.await_args.args == ( - { - "setpointMode": "FollowSchedule", - }, - ) - assert mock_fcn.await_args.kwargs == {} + assert mock_fcn.await_count == 1 + assert mock_fcn.await_args.args != () # minimum target temp + assert mock_fcn.await_args.kwargs == {"until": None} - # set_hvac_mode(HVACMode.OFF): PermanentOverride, minHeatSetpoint - with patch("evohomeasync2.zone.Zone._set_mode") as mock_fcn: - await zone.async_set_hvac_mode(HVACMode.OFF) - - assert mock_fcn.await_count == 1 - assert install != "default" or mock_fcn.await_args.args == ( - { - "setpointMode": "PermanentOverride", - "HeatSetpointValue": 5.0, # varies by install - }, - ) - assert mock_fcn.await_args.kwargs == {} - - results.append(mock_fcn.await_args.args) + results.append(mock_fcn.await_args.args) assert results == snapshot @@ -90,63 +81,67 @@ async def test_zone_set_hvac_mode( @pytest.mark.parametrize("install", TEST_INSTALLS) async def test_zone_set_preset_mode( hass: HomeAssistant, - config: dict[str, str], - install: str, + zone_id: str, freezer: FrozenDateTimeFactory, snapshot: SnapshotAssertion, ) -> None: - """Test climate methods of a evohome-compatible zone.""" + """Test SERVICE_SET_PRESET_MODE of an evohome zone Climate entity.""" freezer.move_to("2024-07-10T12:00:00Z") results = [] - async for _ in setup_evohome(hass, config, install=install): - zone = get_zone_entity(hass) + # SERVICE_SET_PRESET_MODE: none + with patch("evohomeasync2.zone.Zone.reset_mode") as mock_fcn: + await hass.services.async_call( + Platform.CLIMATE, + SERVICE_SET_PRESET_MODE, + { + ATTR_ENTITY_ID: zone_id, + ATTR_PRESET_MODE: "none", + }, + blocking=True, + ) - assert zone.preset_modes == ["none", "temporary", "permanent"] + assert mock_fcn.await_count == 1 + assert mock_fcn.await_args.args == () + assert mock_fcn.await_args.kwargs == {} - # set_preset_mode(none): FollowSchedule - with patch("evohomeasync2.zone.Zone._set_mode") as mock_fcn: - await zone.async_set_preset_mode("none") + # SERVICE_SET_PRESET_MODE: permanent + with patch("evohomeasync2.zone.Zone.set_temperature") as mock_fcn: + await hass.services.async_call( + Platform.CLIMATE, + SERVICE_SET_PRESET_MODE, + { + ATTR_ENTITY_ID: zone_id, + ATTR_PRESET_MODE: "permanent", + }, + blocking=True, + ) - assert mock_fcn.await_count == 1 - assert mock_fcn.await_args.args == ( - { - "setpointMode": "FollowSchedule", - }, - ) - assert mock_fcn.await_args.kwargs == {} + assert mock_fcn.await_count == 1 + assert mock_fcn.await_args.args != () # current target temp + assert mock_fcn.await_args.kwargs == {"until": None} - # set_preset_mode(permanent): PermanentOverride - with patch("evohomeasync2.zone.Zone._set_mode") as mock_fcn: - await zone.async_set_preset_mode("permanent") + results.append(mock_fcn.await_args.args) - assert mock_fcn.await_count == 1 - assert install != "default" or mock_fcn.await_args.args == ( - { - "setpointMode": "PermanentOverride", - "HeatSetpointValue": 17.0, # varies by install - }, - ) - assert mock_fcn.await_args.kwargs == {} + # SERVICE_SET_PRESET_MODE: temporary + with patch("evohomeasync2.zone.Zone.set_temperature") as mock_fcn: + await hass.services.async_call( + Platform.CLIMATE, + SERVICE_SET_PRESET_MODE, + { + ATTR_ENTITY_ID: zone_id, + ATTR_PRESET_MODE: "temporary", + }, + blocking=True, + ) - results.append(mock_fcn.await_args.args) + assert mock_fcn.await_count == 1 + assert mock_fcn.await_args.args != () # current target temp + assert mock_fcn.await_args.kwargs != {} # next setpoint dtm - # set_preset_mode(permanent): TemporaryOverride - with patch("evohomeasync2.zone.Zone._set_mode") as mock_fcn: - await zone.async_set_preset_mode("temporary") - - assert mock_fcn.await_count == 1 - assert install != "default" or mock_fcn.await_args.args == ( - { - "setpointMode": "TemporaryOverride", - "HeatSetpointValue": 17.0, # varies by install - "timeUntil": "2024-07-10T21:10:00Z", # varies by install - }, - ) - assert mock_fcn.await_args.kwargs == {} - - results.append(mock_fcn.await_args.args) + results.append(mock_fcn.await_args.args) + results.append(mock_fcn.await_args.kwargs) assert results == snapshot @@ -154,50 +149,84 @@ async def test_zone_set_preset_mode( @pytest.mark.parametrize("install", TEST_INSTALLS) async def test_zone_set_temperature( hass: HomeAssistant, - config: dict[str, str], - install: str, + zone_id: str, freezer: FrozenDateTimeFactory, snapshot: SnapshotAssertion, ) -> None: - """Test climate methods of a evohome-compatible zone.""" + """Test SERVICE_SET_TEMPERATURE of an evohome zone Climate entity.""" freezer.move_to("2024-07-10T12:00:00Z") results = [] - async for _ in setup_evohome(hass, config, install=install): - zone = get_zone_entity(hass) + # SERVICE_SET_TEMPERATURE: temperature + with patch("evohomeasync2.zone.Zone.set_temperature") as mock_fcn: + await hass.services.async_call( + Platform.CLIMATE, + SERVICE_SET_TEMPERATURE, + { + ATTR_ENTITY_ID: zone_id, + ATTR_TEMPERATURE: 19.1, + }, + blocking=True, + ) - # set_temperature(temp): TemporaryOverride, advanced - with patch("evohomeasync2.zone.Zone._set_mode") as mock_fcn: - await zone.async_set_temperature(temperature=19.1) + assert mock_fcn.await_count == 1 + assert mock_fcn.await_args.args == (19.1,) + assert mock_fcn.await_args.kwargs != {} # next setpoint dtm - assert mock_fcn.await_count == 1 - assert install != "default" or mock_fcn.await_args.args == ( - { - "setpointMode": "TemporaryOverride", - "HeatSetpointValue": 19.1, - "timeUntil": "2024-07-10T21:10:00Z", # varies by install - }, - ) - assert mock_fcn.await_args.kwargs == {} - - results.append(mock_fcn.await_args.args) - - # set_temperature(temp, until): TemporaryOverride, until - with patch("evohomeasync2.zone.Zone._set_mode") as mock_fcn: - await zone.async_set_temperature( - temperature=19.2, - until=dt_util.parse_datetime("2024-07-10T13:30:00Z"), - ) - - assert mock_fcn.await_count == 1 - assert mock_fcn.await_args.args == ( - { - "setpointMode": "TemporaryOverride", - "HeatSetpointValue": 19.2, - "timeUntil": "2024-07-10T13:30:00Z", - }, - ) - assert mock_fcn.await_args.kwargs == {} + results.append(mock_fcn.await_args.kwargs) assert results == snapshot + + +@pytest.mark.parametrize("install", TEST_INSTALLS) +async def test_zone_turn_off( + hass: HomeAssistant, + zone_id: str, + snapshot: SnapshotAssertion, +) -> None: + """Test SERVICE_TURN_OFF of a evohome zone Climate entity.""" + + results = [] + + # SERVICE_TURN_OFF + with patch("evohomeasync2.zone.Zone.set_temperature") as mock_fcn: + await hass.services.async_call( + Platform.CLIMATE, + SERVICE_TURN_OFF, + { + ATTR_ENTITY_ID: zone_id, + }, + blocking=True, + ) + + assert mock_fcn.await_count == 1 + assert mock_fcn.await_args.args != () # minimum target temp + assert mock_fcn.await_args.kwargs == {"until": None} + + results.append(mock_fcn.await_args.args) + + assert results == snapshot + + +@pytest.mark.parametrize("install", TEST_INSTALLS) +async def test_zone_turn_on( + hass: HomeAssistant, + zone_id: str, +) -> None: + """Test SERVICE_TURN_ON of a evohome zone Climate entity.""" + + # SERVICE_TURN_ON + with patch("evohomeasync2.zone.Zone.reset_mode") as mock_fcn: + await hass.services.async_call( + Platform.CLIMATE, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: zone_id, + }, + blocking=True, + ) + + assert mock_fcn.await_count == 1 + assert mock_fcn.await_args.args == () + assert mock_fcn.await_args.kwargs == {}