Add tests to Evohome for its native services (#139104)

initial commit
pull/139108/head
David Bonnes 2025-02-23 11:43:25 +00:00 committed by GitHub
parent 91668e99e3
commit 746d1800f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 202 additions and 65 deletions

View File

@ -25,6 +25,7 @@ import voluptuous as vol
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_MODE,
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_USERNAME,
@ -40,11 +41,10 @@ from homeassistant.helpers.typing import ConfigType
from homeassistant.util.hass_dict import HassKey
from .const import (
ATTR_DURATION_DAYS,
ATTR_DURATION_HOURS,
ATTR_DURATION,
ATTR_DURATION_UNTIL,
ATTR_SYSTEM_MODE,
ATTR_ZONE_TEMP,
ATTR_PERIOD,
ATTR_SETPOINT,
CONF_LOCATION_IDX,
DOMAIN,
SCAN_INTERVAL_DEFAULT,
@ -81,7 +81,7 @@ RESET_ZONE_OVERRIDE_SCHEMA: Final = vol.Schema(
SET_ZONE_OVERRIDE_SCHEMA: Final = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(ATTR_ZONE_TEMP): vol.All(
vol.Required(ATTR_SETPOINT): vol.All(
vol.Coerce(float), vol.Range(min=4.0, max=35.0)
),
vol.Optional(ATTR_DURATION_UNTIL): vol.All(
@ -222,7 +222,7 @@ def setup_service_functions(
# Permanent-only modes will use this schema
perm_modes = [m[SZ_SYSTEM_MODE] for m in modes if not m[SZ_CAN_BE_TEMPORARY]]
if perm_modes: # any of: "Auto", "HeatingOff": permanent only
schema = vol.Schema({vol.Required(ATTR_SYSTEM_MODE): vol.In(perm_modes)})
schema = vol.Schema({vol.Required(ATTR_MODE): vol.In(perm_modes)})
system_mode_schemas.append(schema)
modes = [m for m in modes if m[SZ_CAN_BE_TEMPORARY]]
@ -232,8 +232,8 @@ def setup_service_functions(
if temp_modes: # any of: "AutoWithEco", permanent or for 0-24 hours
schema = vol.Schema(
{
vol.Required(ATTR_SYSTEM_MODE): vol.In(temp_modes),
vol.Optional(ATTR_DURATION_HOURS): vol.All(
vol.Required(ATTR_MODE): vol.In(temp_modes),
vol.Optional(ATTR_DURATION): vol.All(
cv.time_period,
vol.Range(min=timedelta(hours=0), max=timedelta(hours=24)),
),
@ -246,8 +246,8 @@ def setup_service_functions(
if temp_modes: # any of: "Away", "Custom", "DayOff", permanent or for 1-99 days
schema = vol.Schema(
{
vol.Required(ATTR_SYSTEM_MODE): vol.In(temp_modes),
vol.Optional(ATTR_DURATION_DAYS): vol.All(
vol.Required(ATTR_MODE): vol.In(temp_modes),
vol.Optional(ATTR_PERIOD): vol.All(
cv.time_period,
vol.Range(min=timedelta(days=1), max=timedelta(days=99)),
),

View File

@ -29,7 +29,7 @@ from homeassistant.components.climate import (
ClimateEntityFeature,
HVACMode,
)
from homeassistant.const import PRECISION_TENTHS, UnitOfTemperature
from homeassistant.const import ATTR_MODE, PRECISION_TENTHS, UnitOfTemperature
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -38,11 +38,10 @@ from homeassistant.util import dt as dt_util
from . import EVOHOME_KEY
from .const import (
ATTR_DURATION_DAYS,
ATTR_DURATION_HOURS,
ATTR_DURATION,
ATTR_DURATION_UNTIL,
ATTR_SYSTEM_MODE,
ATTR_ZONE_TEMP,
ATTR_PERIOD,
ATTR_SETPOINT,
EvoService,
)
from .coordinator import EvoDataUpdateCoordinator
@ -180,7 +179,7 @@ class EvoZone(EvoChild, EvoClimateEntity):
return
# otherwise it is EvoService.SET_ZONE_OVERRIDE
temperature = max(min(data[ATTR_ZONE_TEMP], self.max_temp), self.min_temp)
temperature = max(min(data[ATTR_SETPOINT], self.max_temp), self.min_temp)
if ATTR_DURATION_UNTIL in data:
duration: timedelta = data[ATTR_DURATION_UNTIL]
@ -349,16 +348,16 @@ class EvoController(EvoClimateEntity):
Data validation is not required, it will have been done upstream.
"""
if service == EvoService.SET_SYSTEM_MODE:
mode = data[ATTR_SYSTEM_MODE]
mode = data[ATTR_MODE]
else: # otherwise it is EvoService.RESET_SYSTEM
mode = EvoSystemMode.AUTO_WITH_RESET
if ATTR_DURATION_DAYS in data:
if ATTR_PERIOD in data:
until = dt_util.start_of_local_day()
until += data[ATTR_DURATION_DAYS]
until += data[ATTR_PERIOD]
elif ATTR_DURATION_HOURS in data:
until = dt_util.now() + data[ATTR_DURATION_HOURS]
elif ATTR_DURATION in data:
until = dt_util.now() + data[ATTR_DURATION]
else:
until = None

View File

@ -18,11 +18,10 @@ USER_DATA: Final = "user_data"
SCAN_INTERVAL_DEFAULT: Final = timedelta(seconds=300)
SCAN_INTERVAL_MINIMUM: Final = timedelta(seconds=60)
ATTR_SYSTEM_MODE: Final = "mode"
ATTR_DURATION_DAYS: Final = "period"
ATTR_DURATION_HOURS: Final = "duration"
ATTR_PERIOD: Final = "period" # number of days
ATTR_DURATION: Final = "duration" # number of minutes, <24h
ATTR_ZONE_TEMP: Final = "setpoint"
ATTR_SETPOINT: Final = "setpoint"
ATTR_DURATION_UNTIL: Final = "duration"

View File

@ -0,0 +1,177 @@
"""The tests for the native services of Evohome."""
from __future__ import annotations
from datetime import UTC, datetime
from unittest.mock import patch
from evohomeasync2 import EvohomeClient
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.components.evohome.const import (
ATTR_DURATION,
ATTR_PERIOD,
ATTR_SETPOINT,
DOMAIN,
EvoService,
)
from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE
from homeassistant.core import HomeAssistant
@pytest.mark.parametrize("install", ["default"])
async def test_service_refresh_system(
hass: HomeAssistant,
evohome: EvohomeClient,
) -> None:
"""Test Evohome's refresh_system service (for all temperature control systems)."""
# EvoService.REFRESH_SYSTEM
with patch("evohomeasync2.location.Location.update") as mock_fcn:
await hass.services.async_call(
DOMAIN,
EvoService.REFRESH_SYSTEM,
{},
blocking=True,
)
mock_fcn.assert_awaited_once_with()
@pytest.mark.parametrize("install", ["default"])
async def test_service_reset_system(
hass: HomeAssistant,
ctl_id: str,
) -> None:
"""Test Evohome's reset_system service (for a temperature control system)."""
# EvoService.RESET_SYSTEM (if SZ_AUTO_WITH_RESET in modes)
with patch("evohomeasync2.control_system.ControlSystem.set_mode") as mock_fcn:
await hass.services.async_call(
DOMAIN,
EvoService.RESET_SYSTEM,
{},
blocking=True,
)
mock_fcn.assert_awaited_once_with("AutoWithReset", until=None)
@pytest.mark.parametrize("install", ["default"])
async def test_ctl_set_system_mode(
hass: HomeAssistant,
ctl_id: str,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test Evohome's set_system_mode service (for a temperature control system)."""
# EvoService.SET_SYSTEM_MODE: Auto
with patch("evohomeasync2.control_system.ControlSystem.set_mode") as mock_fcn:
await hass.services.async_call(
DOMAIN,
EvoService.SET_SYSTEM_MODE,
{
ATTR_MODE: "Auto",
},
blocking=True,
)
mock_fcn.assert_awaited_once_with("Auto", until=None)
freezer.move_to("2024-07-10T12:00:00+00:00")
# EvoService.SET_SYSTEM_MODE: AutoWithEco, hours=12
with patch("evohomeasync2.control_system.ControlSystem.set_mode") as mock_fcn:
await hass.services.async_call(
DOMAIN,
EvoService.SET_SYSTEM_MODE,
{
ATTR_MODE: "AutoWithEco",
ATTR_DURATION: {"hours": 12},
},
blocking=True,
)
mock_fcn.assert_awaited_once_with(
"AutoWithEco", until=datetime(2024, 7, 11, 0, 0, tzinfo=UTC)
)
# EvoService.SET_SYSTEM_MODE: Away, days=7
with patch("evohomeasync2.control_system.ControlSystem.set_mode") as mock_fcn:
await hass.services.async_call(
DOMAIN,
EvoService.SET_SYSTEM_MODE,
{
ATTR_MODE: "Away",
ATTR_PERIOD: {"days": 7},
},
blocking=True,
)
mock_fcn.assert_awaited_once_with(
"Away", until=datetime(2024, 7, 16, 23, 0, tzinfo=UTC)
)
@pytest.mark.parametrize("install", ["default"])
async def test_zone_clear_zone_override(
hass: HomeAssistant,
zone_id: str,
) -> None:
"""Test Evohome's clear_zone_override service (for a heating zone)."""
# EvoZoneMode.FOLLOW_SCHEDULE
with patch("evohomeasync2.zone.Zone.reset") as mock_fcn:
await hass.services.async_call(
DOMAIN,
EvoService.RESET_ZONE_OVERRIDE,
{
ATTR_ENTITY_ID: zone_id,
},
blocking=True,
)
mock_fcn.assert_awaited_once_with()
@pytest.mark.parametrize("install", ["default"])
async def test_zone_set_zone_override(
hass: HomeAssistant,
zone_id: str,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test Evohome's set_zone_override service (for a heating zone)."""
freezer.move_to("2024-07-10T12:00:00+00:00")
# EvoZoneMode.PERMANENT_OVERRIDE
with patch("evohomeasync2.zone.Zone.set_temperature") as mock_fcn:
await hass.services.async_call(
DOMAIN,
EvoService.SET_ZONE_OVERRIDE,
{
ATTR_ENTITY_ID: zone_id,
ATTR_SETPOINT: 19.5,
},
blocking=True,
)
mock_fcn.assert_awaited_once_with(19.5, until=None)
# EvoZoneMode.TEMPORARY_OVERRIDE
with patch("evohomeasync2.zone.Zone.set_temperature") as mock_fcn:
await hass.services.async_call(
DOMAIN,
EvoService.SET_ZONE_OVERRIDE,
{
ATTR_ENTITY_ID: zone_id,
ATTR_SETPOINT: 19.5,
ATTR_DURATION: {"minutes": 135},
},
blocking=True,
)
mock_fcn.assert_awaited_once_with(
19.5, until=datetime(2024, 7, 10, 14, 15, tzinfo=UTC)
)

View File

@ -1,4 +1,4 @@
"""The tests for evohome."""
"""The tests for Evohome."""
from __future__ import annotations
@ -11,7 +11,7 @@ from evohomeasync2 import EvohomeClient, exceptions as exc
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.evohome.const import DOMAIN, EvoService
from homeassistant.components.evohome.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
@ -187,41 +187,3 @@ async def test_setup(
"""
assert hass.services.async_services_for_domain(DOMAIN).keys() == snapshot
@pytest.mark.parametrize("install", ["default"])
async def test_service_refresh_system(
hass: HomeAssistant,
evohome: EvohomeClient,
) -> None:
"""Test EvoService.REFRESH_SYSTEM of an evohome system."""
# EvoService.REFRESH_SYSTEM
with patch("evohomeasync2.location.Location.update") as mock_fcn:
await hass.services.async_call(
DOMAIN,
EvoService.REFRESH_SYSTEM,
{},
blocking=True,
)
mock_fcn.assert_awaited_once_with()
@pytest.mark.parametrize("install", ["default"])
async def test_service_reset_system(
hass: HomeAssistant,
evohome: EvohomeClient,
) -> None:
"""Test EvoService.RESET_SYSTEM of an evohome system."""
# EvoService.RESET_SYSTEM (if SZ_AUTO_WITH_RESET in modes)
with patch("evohomeasync2.control_system.ControlSystem.set_mode") as mock_fcn:
await hass.services.async_call(
DOMAIN,
EvoService.RESET_SYSTEM,
{},
blocking=True,
)
mock_fcn.assert_awaited_once_with("AutoWithReset", until=None)