Add evohome test for setup (#123129)

* allow for different systems

* installation is a load_json_*fixture param

* allow installation to be parameterized

* test setup of various systems

* add more fixtures

* test setup of integration

* tweak test

* tweak const

* add expected state/services

* extend setup test

* tidy up

* tidy up tweaks

* code tweaks

* refactor expected results dicts

* woops

* refatcor serialize

* refactor test

* tweak

* tweak code

* rename symbol

* ensure actual I/O remains blocked

* tweak

* typo

* use constants

* Update conftest.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* change filename

* add config fixture

* config is a fixture

* config is a fixture now 2

* lint

* lint

* refactor

* lint

* lint

* restore email addr

* use const

* use snapshots instead of helper class

* doctweak

* correct snapshot

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
David Bonnes 2024-08-28 21:40:57 +01:00 committed by GitHub
parent 2b20b2a80b
commit ada6b7875c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 1549 additions and 58 deletions

View File

@ -2,8 +2,10 @@
from __future__ import annotations
from collections.abc import Callable
from datetime import datetime, timedelta
from typing import Any, Final
from http import HTTPMethod
from typing import Any
from unittest.mock import MagicMock, patch
from aiohttp import ClientSession
@ -16,75 +18,112 @@ from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from homeassistant.util.json import JsonArrayType, JsonObjectType
from .const import ACCESS_TOKEN, REFRESH_TOKEN
from .const import ACCESS_TOKEN, REFRESH_TOKEN, USERNAME
from tests.common import load_json_array_fixture, load_json_object_fixture
TEST_CONFIG: Final = {
CONF_USERNAME: "username",
CONF_PASSWORD: "password",
}
def user_account_config_fixture() -> JsonObjectType:
def user_account_config_fixture(install: str) -> JsonObjectType:
"""Load JSON for the config of a user's account."""
return load_json_object_fixture("user_account.json", DOMAIN)
try:
return load_json_object_fixture(f"{install}/user_account.json", DOMAIN)
except FileNotFoundError:
return load_json_object_fixture("default/user_account.json", DOMAIN)
def user_locations_config_fixture() -> JsonArrayType:
def user_locations_config_fixture(install: str) -> JsonArrayType:
"""Load JSON for the config of a user's installation (a list of locations)."""
return load_json_array_fixture("user_locations.json", DOMAIN)
return load_json_array_fixture(f"{install}/user_locations.json", DOMAIN)
def location_status_fixture(loc_id: str) -> JsonObjectType:
def location_status_fixture(install: str, loc_id: str | None = None) -> JsonObjectType:
"""Load JSON for the status of a specific location."""
return load_json_object_fixture(f"status_{loc_id}.json", DOMAIN)
if loc_id is None:
_install = load_json_array_fixture(f"{install}/user_locations.json", DOMAIN)
loc_id = _install[0]["locationInfo"]["locationId"] # type: ignore[assignment, call-overload, index]
return load_json_object_fixture(f"{install}/status_{loc_id}.json", DOMAIN)
def dhw_schedule_fixture() -> JsonObjectType:
def dhw_schedule_fixture(install: str) -> JsonObjectType:
"""Load JSON for the schedule of a domesticHotWater zone."""
return load_json_object_fixture("schedule_dhw.json", DOMAIN)
try:
return load_json_object_fixture(f"{install}/schedule_dhw.json", DOMAIN)
except FileNotFoundError:
return load_json_object_fixture("default/schedule_dhw.json", DOMAIN)
def zone_schedule_fixture() -> JsonObjectType:
def zone_schedule_fixture(install: str) -> JsonObjectType:
"""Load JSON for the schedule of a temperatureZone zone."""
return load_json_object_fixture("schedule_zone.json", DOMAIN)
try:
return load_json_object_fixture(f"{install}/schedule_zone.json", DOMAIN)
except FileNotFoundError:
return load_json_object_fixture("default/schedule_zone.json", DOMAIN)
async def mock_get(
self: Broker, url: str, **kwargs: Any
) -> JsonArrayType | JsonObjectType:
"""Return the JSON for a HTTP get of a given URL."""
def mock_get_factory(install: str) -> Callable:
"""Return a get method for a specified installation."""
# a proxy for the behaviour of the real web API
if self.refresh_token is None:
self.refresh_token = f"new_{REFRESH_TOKEN}"
async def mock_get(
self: Broker, url: str, **kwargs: Any
) -> JsonArrayType | JsonObjectType:
"""Return the JSON for a HTTP get of a given URL."""
if self.access_token_expires is None or self.access_token_expires < datetime.now():
self.access_token = f"new_{ACCESS_TOKEN}"
self.access_token_expires = datetime.now() + timedelta(minutes=30)
# a proxy for the behaviour of the real web API
if self.refresh_token is None:
self.refresh_token = f"new_{REFRESH_TOKEN}"
# assume a valid GET, and return the JSON for that web API
if url == "userAccount": # userAccount
return user_account_config_fixture()
if (
self.access_token_expires is None
or self.access_token_expires < datetime.now()
):
self.access_token = f"new_{ACCESS_TOKEN}"
self.access_token_expires = datetime.now() + timedelta(minutes=30)
if url.startswith("location"):
if "installationInfo" in url: # location/installationInfo?userId={id}
return user_locations_config_fixture()
if "location" in url: # location/{id}/status
return location_status_fixture("2738909")
# assume a valid GET, and return the JSON for that web API
if url == "userAccount": # userAccount
return user_account_config_fixture(install)
elif "schedule" in url:
if url.startswith("domesticHotWater"): # domesticHotWater/{id}/schedule
return dhw_schedule_fixture()
if url.startswith("temperatureZone"): # temperatureZone/{id}/schedule
return zone_schedule_fixture()
if url.startswith("location"):
if "installationInfo" in url: # location/installationInfo?userId={id}
return user_locations_config_fixture(install)
if "location" in url: # location/{id}/status
return location_status_fixture(install)
pytest.xfail(f"Unexpected URL: {url}")
elif "schedule" in url:
if url.startswith("domesticHotWater"): # domesticHotWater/{id}/schedule
return dhw_schedule_fixture(install)
if url.startswith("temperatureZone"): # temperatureZone/{id}/schedule
return zone_schedule_fixture(install)
pytest.fail(f"Unexpected request: {HTTPMethod.GET} {url}")
return mock_get
@patch("evohomeasync2.broker.Broker.get", mock_get)
async def setup_evohome(hass: HomeAssistant, test_config: dict[str, str]) -> MagicMock:
async def block_request(
self: Broker, method: HTTPMethod, url: str, **kwargs: Any
) -> None:
"""Fail if the code attempts any actual I/O via aiohttp."""
pytest.fail(f"Unexpected request: {method} {url}")
@pytest.fixture
def evo_config() -> dict[str, str]:
"Return a default/minimal configuration."
return {
CONF_USERNAME: USERNAME,
CONF_PASSWORD: "password",
}
@patch("evohomeasync.broker.Broker._make_request", block_request)
@patch("evohomeasync2.broker.Broker._client", block_request)
async def setup_evohome(
hass: HomeAssistant,
test_config: dict[str, str],
install: str = "default",
) -> MagicMock:
"""Set up the evohome integration and return its client.
The class is mocked here to check the client was instantiated with the correct args.
@ -93,6 +132,7 @@ async def setup_evohome(hass: HomeAssistant, test_config: dict[str, str]) -> Mag
with (
patch("homeassistant.components.evohome.evo.EvohomeClient") as mock_client,
patch("homeassistant.components.evohome.ev1.EvohomeClient", return_value=None),
patch("evohomeasync2.broker.Broker.get", mock_get_factory(install)),
):
mock_client.side_effect = EvohomeClient

View File

@ -8,3 +8,12 @@ ACCESS_TOKEN: Final = "at_1dc7z657UKzbhKA..."
REFRESH_TOKEN: Final = "rf_jg68ZCKYdxEI3fF..."
SESSION_ID: Final = "F7181186..."
USERNAME: Final = "test_user@gmail.com"
# The h-numbers refer to issues in HA's core repo
TEST_INSTALLS: Final = (
"minimal", # evohome (single zone, no DHW)
"default", # evohome (multi-zone, with DHW & ghost zones)
"h032585", # VisionProWifi (no preset_mode for TCS)
"h099625", # RoundThermostat
"system_004", # RoundModulation
)

View File

@ -246,7 +246,7 @@
},
{
"zoneId": "3450733",
"modelType": "xx",
"modelType": "xxx",
"setpointCapabilities": {
"maxHeatSetpoint": 35.0,
"minHeatSetpoint": 5.0,
@ -268,7 +268,7 @@
"setpointValueResolution": 0.5
},
"name": "Spare Room",
"zoneType": "xx"
"zoneType": "xxx"
}
],
"dhw": {

View File

@ -0,0 +1,31 @@
{
"locationId": "111111",
"gateways": [
{
"gatewayId": "222222",
"temperatureControlSystems": [
{
"systemId": "416856",
"zones": [
{
"zoneId": "416856",
"temperatureStatus": {
"temperature": 21.5,
"isAvailable": true
},
"activeFaults": [],
"setpointStatus": {
"targetHeatTemperature": 21.5,
"setpointMode": "FollowSchedule"
},
"name": "THERMOSTAT"
}
],
"activeFaults": [],
"systemModeStatus": { "mode": "Heat", "isPermanent": true }
}
],
"activeFaults": []
}
]
}

View File

@ -0,0 +1,3 @@
{
"416856": 21.5
}

View File

@ -0,0 +1,79 @@
[
{
"locationInfo": {
"locationId": "111111",
"name": "My Home",
"timeZone": {
"timeZoneId": "GMTStandardTime",
"displayName": "(UTC+00:00) Dublin, Edinburgh, Lisbon, London",
"offsetMinutes": 0,
"currentOffsetMinutes": 60,
"supportsDaylightSaving": true
}
},
"gateways": [
{
"gatewayInfo": {
"gatewayId": "222222",
"mac": "00D02DEE0000",
"crc": "1234",
"isWiFi": false
},
"temperatureControlSystems": [
{
"systemId": "416856",
"modelType": "VisionProWifiRetail",
"zones": [
{
"zoneId": "416856",
"modelType": "VisionProWifiRetail",
"setpointCapabilities": {
"vacationHoldCapabilities": {
"isChangeable": true,
"isCancelable": true,
"minDuration": "1.00:00:00",
"maxDuration": "365.23:45:00",
"timingResolution": "00:15:00"
},
"maxHeatSetpoint": 32.0,
"minHeatSetpoint": 4.5,
"valueResolution": 0.5,
"canControlHeat": true,
"canControlCool": false,
"allowedSetpointModes": [
"PermanentOverride",
"FollowSchedule",
"TemporaryOverride",
"VacationHold"
],
"maxDuration": "1.00:00:00",
"timingResolution": "00:15:00"
},
"scheduleCapabilities": {
"maxSwitchpointsPerDay": 4,
"minSwitchpointsPerDay": 0,
"timingResolution": "00:15:00",
"setpointValueResolution": 0.5
},
"name": "THERMOSTAT",
"zoneType": "Thermostat"
}
],
"allowedSystemModes": [
{
"systemMode": "Off",
"canBePermanent": true,
"canBeTemporary": false
},
{
"systemMode": "Heat",
"canBePermanent": true,
"canBeTemporary": false
}
]
}
]
}
]
}
]

View File

@ -0,0 +1,44 @@
{
"locationId": "111111",
"gateways": [
{
"gatewayId": "222222",
"temperatureControlSystems": [
{
"systemId": "8557535",
"zones": [
{
"zoneId": "8557539",
"temperatureStatus": {
"temperature": 21.5,
"isAvailable": true
},
"activeFaults": [],
"setpointStatus": {
"targetHeatTemperature": 21.5,
"setpointMode": "FollowSchedule"
},
"name": "THERMOSTAT"
},
{
"zoneId": "8557541",
"temperatureStatus": {
"temperature": 21.5,
"isAvailable": true
},
"activeFaults": [],
"setpointStatus": {
"targetHeatTemperature": 21.5,
"setpointMode": "FollowSchedule"
},
"name": "THERMOSTAT"
}
],
"activeFaults": [],
"systemModeStatus": { "mode": "Auto", "isPermanent": true }
}
],
"activeFaults": []
}
]
}

View File

@ -0,0 +1,113 @@
[
{
"locationInfo": {
"locationId": "111111",
"name": "My Home",
"timeZone": {
"timeZoneId": "FLEStandardTime",
"displayName": "(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius",
"offsetMinutes": 120,
"currentOffsetMinutes": 180,
"supportsDaylightSaving": true
}
},
"gateways": [
{
"gatewayInfo": {
"gatewayId": "222222",
"mac": "00D02DEE0000",
"crc": "1234",
"isWiFi": false
},
"temperatureControlSystems": [
{
"systemId": "8557535",
"modelType": "EvoTouch",
"zones": [
{
"zoneId": "8557539",
"modelType": "RoundWireless",
"setpointCapabilities": {
"maxHeatSetpoint": 35.0,
"minHeatSetpoint": 5.0,
"valueResolution": 0.5,
"canControlHeat": true,
"canControlCool": false,
"allowedSetpointModes": [
"PermanentOverride",
"FollowSchedule",
"TemporaryOverride"
],
"maxDuration": "1.00:00:00",
"timingResolution": "00:10:00"
},
"scheduleCapabilities": {
"maxSwitchpointsPerDay": 6,
"minSwitchpointsPerDay": 0,
"timingResolution": "00:10:00",
"setpointValueResolution": 0.5
},
"name": "Thermostat",
"zoneType": "Thermostat"
},
{
"zoneId": "8557541",
"modelType": "RoundWireless",
"setpointCapabilities": {
"maxHeatSetpoint": 35.0,
"minHeatSetpoint": 5.0,
"valueResolution": 0.5,
"canControlHeat": true,
"canControlCool": false,
"allowedSetpointModes": [
"PermanentOverride",
"FollowSchedule",
"TemporaryOverride"
],
"maxDuration": "1.00:00:00",
"timingResolution": "00:10:00"
},
"scheduleCapabilities": {
"maxSwitchpointsPerDay": 6,
"minSwitchpointsPerDay": 0,
"timingResolution": "00:10:00",
"setpointValueResolution": 0.5
},
"name": "Thermostat 2",
"zoneType": "Thermostat"
}
],
"allowedSystemModes": [
{
"systemMode": "Auto",
"canBePermanent": true,
"canBeTemporary": false
},
{
"systemMode": "AutoWithEco",
"canBePermanent": true,
"canBeTemporary": true,
"maxDuration": "1.00:00:00",
"timingResolution": "01:00:00",
"timingMode": "Duration"
},
{
"systemMode": "Away",
"canBePermanent": true,
"canBeTemporary": true,
"maxDuration": "99.00:00:00",
"timingResolution": "1.00:00:00",
"timingMode": "Period"
},
{
"systemMode": "HeatingOff",
"canBePermanent": true,
"canBeTemporary": false
}
]
}
]
}
]
}
]

View File

@ -0,0 +1,28 @@
{
"locationId": "2738909",
"gateways": [
{
"gatewayId": "2499896",
"temperatureControlSystems": [
{
"systemId": "3432522",
"zones": [
{
"zoneId": "3432576",
"name": "Main Room",
"temperatureStatus": { "temperature": 19.0, "isAvailable": true },
"setpointStatus": {
"targetHeatTemperature": 17.0,
"setpointMode": "FollowSchedule"
},
"activeFaults": []
}
],
"activeFaults": [],
"systemModeStatus": { "mode": "AutoWithEco", "isPermanent": true }
}
],
"activeFaults": []
}
]
}

View File

@ -0,0 +1,120 @@
[
{
"locationInfo": {
"locationId": "2738909",
"name": "My Home",
"streetAddress": "1 Main Street",
"city": "London",
"country": "UnitedKingdom",
"postcode": "E1 1AA",
"locationType": "Residential",
"useDaylightSaveSwitching": true,
"timeZone": {
"timeZoneId": "GMTStandardTime",
"displayName": "(UTC+00:00) Dublin, Edinburgh, Lisbon, London",
"offsetMinutes": 0,
"currentOffsetMinutes": 60,
"supportsDaylightSaving": true
},
"locationOwner": {
"userId": "2263181",
"username": "user_2263181@gmail.com",
"firstname": "John",
"lastname": "Smith"
}
},
"gateways": [
{
"gatewayInfo": {
"gatewayId": "2499896",
"mac": "00D02DEE0000",
"crc": "1234",
"isWiFi": false
},
"temperatureControlSystems": [
{
"systemId": "3432522",
"modelType": "EvoTouch",
"zones": [
{
"zoneId": "3432576",
"modelType": "HeatingZone",
"setpointCapabilities": {
"maxHeatSetpoint": 35.0,
"minHeatSetpoint": 5.0,
"valueResolution": 0.5,
"canControlHeat": true,
"canControlCool": false,
"allowedSetpointModes": [
"PermanentOverride",
"FollowSchedule",
"TemporaryOverride"
],
"maxDuration": "1.00:00:00",
"timingResolution": "00:10:00"
},
"scheduleCapabilities": {
"maxSwitchpointsPerDay": 6,
"minSwitchpointsPerDay": 1,
"timingResolution": "00:10:00",
"setpointValueResolution": 0.5
},
"name": "Main Room",
"zoneType": "RadiatorZone"
}
],
"allowedSystemModes": [
{
"systemMode": "HeatingOff",
"canBePermanent": true,
"canBeTemporary": false
},
{
"systemMode": "Auto",
"canBePermanent": true,
"canBeTemporary": false
},
{
"systemMode": "AutoWithReset",
"canBePermanent": true,
"canBeTemporary": false
},
{
"systemMode": "AutoWithEco",
"canBePermanent": true,
"canBeTemporary": true,
"maxDuration": "1.00:00:00",
"timingResolution": "01:00:00",
"timingMode": "Duration"
},
{
"systemMode": "Away",
"canBePermanent": true,
"canBeTemporary": true,
"maxDuration": "99.00:00:00",
"timingResolution": "1.00:00:00",
"timingMode": "Period"
},
{
"systemMode": "DayOff",
"canBePermanent": true,
"canBeTemporary": true,
"maxDuration": "99.00:00:00",
"timingResolution": "1.00:00:00",
"timingMode": "Period"
},
{
"systemMode": "Custom",
"canBePermanent": true,
"canBeTemporary": true,
"maxDuration": "99.00:00:00",
"timingResolution": "1.00:00:00",
"timingMode": "Period"
}
]
}
]
}
]
}
]

View File

@ -0,0 +1,33 @@
{
"locationId": "3164610",
"gateways": [
{
"gatewayId": "2938388",
"temperatureControlSystems": [
{
"systemId": "4187769",
"zones": [
{
"zoneId": "4187768",
"temperatureStatus": { "temperature": 19.5, "isAvailable": true },
"activeFaults": [],
"setpointStatus": {
"targetHeatTemperature": 15.0,
"setpointMode": "PermanentOverride"
},
"name": "Thermostat"
}
],
"activeFaults": [],
"systemModeStatus": { "mode": "Auto", "isPermanent": true }
}
],
"activeFaults": [
{
"faultType": "GatewayCommunicationLost",
"since": "2023-05-04T18:47:36.7727046"
}
]
}
]
}

View File

@ -0,0 +1,99 @@
[
{
"locationInfo": {
"locationId": "3164610",
"name": "Living room",
"streetAddress": "1 Main Road",
"city": "Boomtown",
"country": "Netherlands",
"postcode": "1234XX",
"locationType": "Residential",
"useDaylightSaveSwitching": true,
"timeZone": {
"timeZoneId": "WEuropeStandardTime",
"displayName": "(UTC+01:00) Amsterdam, Berlijn, Bern, Rome, Stockholm, Wenen",
"offsetMinutes": 60,
"currentOffsetMinutes": 120,
"supportsDaylightSaving": true
},
"locationOwner": {
"userId": "2624305",
"username": "user_2624305@gmail.com",
"firstname": "Chris",
"lastname": "Jones"
}
},
"gateways": [
{
"gatewayInfo": {
"gatewayId": "2938388",
"mac": "00D02D5A7000",
"crc": "1234",
"isWiFi": false
},
"temperatureControlSystems": [
{
"systemId": "4187769",
"modelType": "EvoTouch",
"zones": [
{
"zoneId": "4187768",
"modelType": "RoundModulation",
"setpointCapabilities": {
"maxHeatSetpoint": 35.0,
"minHeatSetpoint": 5.0,
"valueResolution": 0.5,
"canControlHeat": true,
"canControlCool": false,
"allowedSetpointModes": [
"PermanentOverride",
"FollowSchedule",
"TemporaryOverride"
],
"maxDuration": "1.00:00:00",
"timingResolution": "00:10:00"
},
"scheduleCapabilities": {
"maxSwitchpointsPerDay": 6,
"minSwitchpointsPerDay": 0,
"timingResolution": "00:10:00",
"setpointValueResolution": 0.5
},
"name": "Thermostat",
"zoneType": "Thermostat"
}
],
"allowedSystemModes": [
{
"systemMode": "Auto",
"canBePermanent": true,
"canBeTemporary": false
},
{
"systemMode": "AutoWithEco",
"canBePermanent": true,
"canBeTemporary": true,
"maxDuration": "1.00:00:00",
"timingResolution": "01:00:00",
"timingMode": "Duration"
},
{
"systemMode": "Away",
"canBePermanent": true,
"canBeTemporary": true,
"maxDuration": "99.00:00:00",
"timingResolution": "1.00:00:00",
"timingMode": "Period"
},
{
"systemMode": "HeatingOff",
"canBePermanent": true,
"canBeTemporary": false
}
]
}
]
}
]
}
]

View File

@ -0,0 +1,863 @@
# serializer version: 1
# name: test_entities[default]
list([
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 19.7,
'friendly_name': 'My Home',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'icon': 'mdi:thermostat',
'max_temp': 35,
'min_temp': 7,
'preset_mode': 'eco',
'preset_modes': list([
'Reset',
'eco',
'away',
'home',
'Custom',
]),
'status': dict({
'active_system_faults': list([
]),
'system_id': '3432522',
'system_mode_status': dict({
'is_permanent': True,
'mode': 'AutoWithEco',
}),
}),
'supported_features': <ClimateEntityFeature: 400>,
}),
'context': <ANY>,
'entity_id': 'climate.my_home',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': None,
'friendly_name': 'Dead Zone',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_mode': 'none',
'preset_modes': list([
'none',
'temporary',
'permanent',
]),
'status': dict({
'active_faults': list([
]),
'setpoint_status': dict({
'setpoint_mode': 'FollowSchedule',
'target_heat_temperature': 17.0,
}),
'setpoints': dict({
'next_sp_from': '2024-08-14T23:00:00-07:00',
'next_sp_temp': 18.1,
'this_sp_from': '2024-08-14T15:00:00-07:00',
'this_sp_temp': 15.9,
}),
'temperature_status': dict({
'is_available': False,
}),
'zone_id': '3432521',
}),
'supported_features': <ClimateEntityFeature: 401>,
'temperature': 17.0,
}),
'context': <ANY>,
'entity_id': 'climate.dead_zone',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 19.0,
'friendly_name': 'Main Room',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_mode': 'permanent',
'preset_modes': list([
'none',
'temporary',
'permanent',
]),
'status': dict({
'active_faults': list([
dict({
'faultType': 'TempZoneActuatorCommunicationLost',
'since': '2022-03-02T15:56:01',
}),
]),
'setpoint_status': dict({
'setpoint_mode': 'PermanentOverride',
'target_heat_temperature': 17.0,
}),
'setpoints': dict({
'next_sp_from': '2024-08-14T23:00:00-07:00',
'next_sp_temp': 18.1,
'this_sp_from': '2024-08-14T15:00:00-07:00',
'this_sp_temp': 15.9,
}),
'temperature_status': dict({
'is_available': True,
'temperature': 19.0,
}),
'zone_id': '3432576',
}),
'supported_features': <ClimateEntityFeature: 401>,
'temperature': 17.0,
}),
'context': <ANY>,
'entity_id': 'climate.main_room',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 19.0,
'friendly_name': 'Front Room',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_mode': 'temporary',
'preset_modes': list([
'none',
'temporary',
'permanent',
]),
'status': dict({
'active_faults': list([
dict({
'faultType': 'TempZoneActuatorLowBattery',
'since': '2022-03-02T04:50:20',
}),
]),
'setpoint_status': dict({
'setpoint_mode': 'TemporaryOverride',
'target_heat_temperature': 21.0,
'until': '2022-03-07T11:00:00-08:00',
}),
'setpoints': dict({
'next_sp_from': '2024-08-14T23:00:00-07:00',
'next_sp_temp': 18.1,
'this_sp_from': '2024-08-14T15:00:00-07:00',
'this_sp_temp': 15.9,
}),
'temperature_status': dict({
'is_available': True,
'temperature': 19.0,
}),
'zone_id': '3432577',
}),
'supported_features': <ClimateEntityFeature: 401>,
'temperature': 21.0,
}),
'context': <ANY>,
'entity_id': 'climate.front_room',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 20.0,
'friendly_name': 'Kitchen',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_mode': 'none',
'preset_modes': list([
'none',
'temporary',
'permanent',
]),
'status': dict({
'active_faults': list([
]),
'setpoint_status': dict({
'setpoint_mode': 'FollowSchedule',
'target_heat_temperature': 17.0,
}),
'setpoints': dict({
'next_sp_from': '2024-08-14T23:00:00-07:00',
'next_sp_temp': 18.1,
'this_sp_from': '2024-08-14T15:00:00-07:00',
'this_sp_temp': 15.9,
}),
'temperature_status': dict({
'is_available': True,
'temperature': 20.0,
}),
'zone_id': '3432578',
}),
'supported_features': <ClimateEntityFeature: 401>,
'temperature': 17.0,
}),
'context': <ANY>,
'entity_id': 'climate.kitchen',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 20.0,
'friendly_name': 'Bathroom Dn',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_mode': 'none',
'preset_modes': list([
'none',
'temporary',
'permanent',
]),
'status': dict({
'active_faults': list([
]),
'setpoint_status': dict({
'setpoint_mode': 'FollowSchedule',
'target_heat_temperature': 16.0,
}),
'setpoints': dict({
'next_sp_from': '2024-08-14T23:00:00-07:00',
'next_sp_temp': 18.1,
'this_sp_from': '2024-08-14T15:00:00-07:00',
'this_sp_temp': 15.9,
}),
'temperature_status': dict({
'is_available': True,
'temperature': 20.0,
}),
'zone_id': '3432579',
}),
'supported_features': <ClimateEntityFeature: 401>,
'temperature': 16.0,
}),
'context': <ANY>,
'entity_id': 'climate.bathroom_dn',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 21.0,
'friendly_name': 'Main Bedroom',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_mode': 'none',
'preset_modes': list([
'none',
'temporary',
'permanent',
]),
'status': dict({
'active_faults': list([
]),
'setpoint_status': dict({
'setpoint_mode': 'FollowSchedule',
'target_heat_temperature': 16.0,
}),
'setpoints': dict({
'next_sp_from': '2024-08-14T23:00:00-07:00',
'next_sp_temp': 18.1,
'this_sp_from': '2024-08-14T15:00:00-07:00',
'this_sp_temp': 15.9,
}),
'temperature_status': dict({
'is_available': True,
'temperature': 21.0,
}),
'zone_id': '3432580',
}),
'supported_features': <ClimateEntityFeature: 401>,
'temperature': 16.0,
}),
'context': <ANY>,
'entity_id': 'climate.main_bedroom',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 19.5,
'friendly_name': 'Kids Room',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_mode': 'none',
'preset_modes': list([
'none',
'temporary',
'permanent',
]),
'status': dict({
'active_faults': list([
]),
'setpoint_status': dict({
'setpoint_mode': 'FollowSchedule',
'target_heat_temperature': 17.0,
}),
'setpoints': dict({
'next_sp_from': '2024-08-14T23:00:00-07:00',
'next_sp_temp': 18.1,
'this_sp_from': '2024-08-14T15:00:00-07:00',
'this_sp_temp': 15.9,
}),
'temperature_status': dict({
'is_available': True,
'temperature': 19.5,
}),
'zone_id': '3449703',
}),
'supported_features': <ClimateEntityFeature: 401>,
'temperature': 17.0,
}),
'context': <ANY>,
'entity_id': 'climate.kids_room',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'away_mode': 'on',
'current_temperature': 23,
'friendly_name': 'Domestic Hot Water',
'icon': 'mdi:thermometer-lines',
'max_temp': 60,
'min_temp': 43,
'operation_list': list([
'auto',
'on',
'off',
]),
'operation_mode': 'off',
'status': dict({
'active_faults': list([
]),
'dhw_id': '3933910',
'setpoints': dict({
'next_sp_from': '2024-08-14T22:30:00-07:00',
'next_sp_state': 'On',
'this_sp_from': '2024-08-14T14:30:00-07:00',
'this_sp_state': 'Off',
}),
'state_status': dict({
'mode': 'PermanentOverride',
'state': 'Off',
}),
'temperature_status': dict({
'is_available': True,
'temperature': 23.0,
}),
}),
'supported_features': <WaterHeaterEntityFeature: 6>,
'target_temp_high': None,
'target_temp_low': None,
'temperature': None,
}),
'context': <ANY>,
'entity_id': 'water_heater.domestic_hot_water',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
}),
])
# ---
# name: test_entities[h032585]
list([
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 21.5,
'friendly_name': 'My Home',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'icon': 'mdi:thermostat',
'max_temp': 35,
'min_temp': 7,
'status': dict({
'active_system_faults': list([
]),
'system_id': '416856',
'system_mode_status': dict({
'is_permanent': True,
'mode': 'Heat',
}),
}),
'supported_features': <ClimateEntityFeature: 384>,
}),
'context': <ANY>,
'entity_id': 'climate.my_home',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 21.5,
'friendly_name': 'THERMOSTAT',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 32.0,
'min_temp': 4.5,
'preset_mode': 'none',
'preset_modes': list([
'none',
'temporary',
'permanent',
]),
'status': dict({
'active_faults': list([
]),
'setpoint_status': dict({
'setpoint_mode': 'FollowSchedule',
'target_heat_temperature': 21.5,
}),
'setpoints': dict({
'next_sp_from': '2024-08-14T23:00:00-07:00',
'next_sp_temp': 18.1,
'this_sp_from': '2024-08-14T15:00:00-07:00',
'this_sp_temp': 15.9,
}),
'temperature_status': dict({
'is_available': True,
'temperature': 21.5,
}),
'zone_id': '416856',
}),
'supported_features': <ClimateEntityFeature: 401>,
'temperature': 21.5,
}),
'context': <ANY>,
'entity_id': 'climate.thermostat',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
])
# ---
# name: test_entities[h099625]
list([
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 21.5,
'friendly_name': 'My Home',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'icon': 'mdi:thermostat',
'max_temp': 35,
'min_temp': 7,
'preset_mode': None,
'preset_modes': list([
'eco',
'away',
]),
'status': dict({
'active_system_faults': list([
]),
'system_id': '8557535',
'system_mode_status': dict({
'is_permanent': True,
'mode': 'Auto',
}),
}),
'supported_features': <ClimateEntityFeature: 400>,
}),
'context': <ANY>,
'entity_id': 'climate.my_home',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 21.5,
'friendly_name': 'THERMOSTAT',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_mode': 'none',
'preset_modes': list([
'none',
'temporary',
'permanent',
]),
'status': dict({
'active_faults': list([
]),
'setpoint_status': dict({
'setpoint_mode': 'FollowSchedule',
'target_heat_temperature': 21.5,
}),
'setpoints': dict({
'next_sp_from': '2024-08-14T21:00:00-07:00',
'next_sp_temp': 18.1,
'this_sp_from': '2024-08-14T13:00:00-07:00',
'this_sp_temp': 15.9,
}),
'temperature_status': dict({
'is_available': True,
'temperature': 21.5,
}),
'zone_id': '8557539',
}),
'supported_features': <ClimateEntityFeature: 401>,
'temperature': 21.5,
}),
'context': <ANY>,
'entity_id': 'climate.thermostat',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 21.5,
'friendly_name': 'THERMOSTAT',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_mode': 'none',
'preset_modes': list([
'none',
'temporary',
'permanent',
]),
'status': dict({
'active_faults': list([
]),
'setpoint_status': dict({
'setpoint_mode': 'FollowSchedule',
'target_heat_temperature': 21.5,
}),
'setpoints': dict({
'next_sp_from': '2024-08-14T21:00:00-07:00',
'next_sp_temp': 18.1,
'this_sp_from': '2024-08-14T13:00:00-07:00',
'this_sp_temp': 15.9,
}),
'temperature_status': dict({
'is_available': True,
'temperature': 21.5,
}),
'zone_id': '8557541',
}),
'supported_features': <ClimateEntityFeature: 401>,
'temperature': 21.5,
}),
'context': <ANY>,
'entity_id': 'climate.thermostat_2',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
])
# ---
# name: test_entities[h118169]
list([
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 21.5,
'friendly_name': 'My Home',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'icon': 'mdi:thermostat',
'max_temp': 35,
'min_temp': 7,
'status': dict({
'active_system_faults': list([
]),
'system_id': '333333',
'system_mode_status': dict({
'is_permanent': True,
'mode': 'Heat',
}),
}),
'supported_features': <ClimateEntityFeature: 384>,
}),
'context': <ANY>,
'entity_id': 'climate.my_home',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 21.5,
'friendly_name': 'THERMOSTAT',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 32.0,
'min_temp': 4.5,
'preset_mode': 'none',
'preset_modes': list([
'none',
'temporary',
'permanent',
]),
'status': dict({
'active_faults': list([
]),
'setpoint_status': dict({
'setpoint_mode': 'FollowSchedule',
'target_heat_temperature': 21.5,
}),
'setpoints': dict({
'next_sp_from': '2024-08-14T23:00:00-07:00',
'next_sp_temp': 18.1,
'this_sp_from': '2024-08-14T15:00:00-07:00',
'this_sp_temp': 15.9,
}),
'temperature_status': dict({
'is_available': True,
'temperature': 21.5,
}),
'zone_id': '444444',
}),
'supported_features': <ClimateEntityFeature: 401>,
'temperature': 21.5,
}),
'context': <ANY>,
'entity_id': 'climate.thermostat',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
])
# ---
# name: test_entities[minimal]
list([
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 19.0,
'friendly_name': 'My Home',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'icon': 'mdi:thermostat',
'max_temp': 35,
'min_temp': 7,
'preset_mode': 'eco',
'preset_modes': list([
'Reset',
'eco',
'away',
'home',
'Custom',
]),
'status': dict({
'active_system_faults': list([
]),
'system_id': '3432522',
'system_mode_status': dict({
'is_permanent': True,
'mode': 'AutoWithEco',
}),
}),
'supported_features': <ClimateEntityFeature: 400>,
}),
'context': <ANY>,
'entity_id': 'climate.my_home',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 19.0,
'friendly_name': 'Main Room',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_mode': 'none',
'preset_modes': list([
'none',
'temporary',
'permanent',
]),
'status': dict({
'active_faults': list([
]),
'setpoint_status': dict({
'setpoint_mode': 'FollowSchedule',
'target_heat_temperature': 17.0,
}),
'setpoints': dict({
'next_sp_from': '2024-08-14T23:00:00-07:00',
'next_sp_temp': 18.1,
'this_sp_from': '2024-08-14T15:00:00-07:00',
'this_sp_temp': 15.9,
}),
'temperature_status': dict({
'is_available': True,
'temperature': 19.0,
}),
'zone_id': '3432576',
}),
'supported_features': <ClimateEntityFeature: 401>,
'temperature': 17.0,
}),
'context': <ANY>,
'entity_id': 'climate.main_room',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
])
# ---
# name: test_entities[system_004]
list([
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 19.5,
'friendly_name': 'Living room',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'icon': 'mdi:thermostat',
'max_temp': 35,
'min_temp': 7,
'preset_mode': None,
'preset_modes': list([
'eco',
'away',
]),
'status': dict({
'active_system_faults': list([
]),
'system_id': '4187769',
'system_mode_status': dict({
'is_permanent': True,
'mode': 'Auto',
}),
}),
'supported_features': <ClimateEntityFeature: 400>,
}),
'context': <ANY>,
'entity_id': 'climate.living_room',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 19.5,
'friendly_name': 'Thermostat',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_mode': 'permanent',
'preset_modes': list([
'none',
'temporary',
'permanent',
]),
'status': dict({
'active_faults': list([
]),
'setpoint_status': dict({
'setpoint_mode': 'PermanentOverride',
'target_heat_temperature': 15.0,
}),
'setpoints': dict({
'next_sp_from': '2024-08-14T22:00:00-07:00',
'next_sp_temp': 18.1,
'this_sp_from': '2024-08-14T14:00:00-07:00',
'this_sp_temp': 15.9,
}),
'temperature_status': dict({
'is_available': True,
'temperature': 19.5,
}),
'zone_id': '4187768',
}),
'supported_features': <ClimateEntityFeature: 401>,
'temperature': 15.0,
}),
'context': <ANY>,
'entity_id': 'climate.thermostat',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
}),
])
# ---

View File

@ -0,0 +1,25 @@
"""The tests for evohome."""
from __future__ import annotations
import pytest
from syrupy import SnapshotAssertion
from homeassistant.core import HomeAssistant
from .conftest import setup_evohome
from .const import TEST_INSTALLS
@pytest.mark.parametrize("install", TEST_INSTALLS)
async def test_entities(
hass: HomeAssistant,
evo_config: dict[str, str],
install: str,
snapshot: SnapshotAssertion,
) -> None:
"""Test entities and state after setup of a Honeywell TCC-compatible system."""
await setup_evohome(hass, evo_config, install=install)
assert hass.states.async_all() == snapshot

View File

@ -8,7 +8,6 @@ from typing import Any, Final, NotRequired, TypedDict
import pytest
from homeassistant.components.evohome import (
CONF_PASSWORD,
CONF_USERNAME,
DOMAIN,
STORAGE_KEY,
@ -56,11 +55,6 @@ ACCESS_TOKEN_EXP_DTM, ACCESS_TOKEN_EXP_STR = dt_pair(dt_util.now() + timedelta(h
USERNAME_DIFF: Final = f"not_{USERNAME}"
USERNAME_SAME: Final = USERNAME
TEST_CONFIG: Final = {
CONF_USERNAME: USERNAME_SAME,
CONF_PASSWORD: "password",
}
TEST_DATA: Final[dict[str, _TokenStoreT]] = {
"sans_session_id": {
SZ_USERNAME: USERNAME_SAME,
@ -93,13 +87,14 @@ DOMAIN_STORAGE_BASE: Final = {
async def test_auth_tokens_null(
hass: HomeAssistant,
hass_storage: dict[str, Any],
evo_config: dict[str, str],
idx: str,
) -> None:
"""Test loading/saving authentication tokens when no cached tokens in the store."""
hass_storage[DOMAIN] = DOMAIN_STORAGE_BASE | {"data": TEST_DATA_NULL[idx]}
mock_client = await setup_evohome(hass, TEST_CONFIG)
mock_client = await setup_evohome(hass, evo_config, install="minimal")
# Confirm client was instantiated without tokens, as cache was empty...
assert SZ_REFRESH_TOKEN not in mock_client.call_args.kwargs
@ -120,13 +115,16 @@ async def test_auth_tokens_null(
@pytest.mark.parametrize("idx", TEST_DATA)
async def test_auth_tokens_same(
hass: HomeAssistant, hass_storage: dict[str, Any], idx: str
hass: HomeAssistant,
hass_storage: dict[str, Any],
evo_config: dict[str, str],
idx: str,
) -> None:
"""Test loading/saving authentication tokens when matching username."""
hass_storage[DOMAIN] = DOMAIN_STORAGE_BASE | {"data": TEST_DATA[idx]}
mock_client = await setup_evohome(hass, TEST_CONFIG)
mock_client = await setup_evohome(hass, evo_config, install="minimal")
# Confirm client was instantiated with the cached tokens...
assert mock_client.call_args.kwargs[SZ_REFRESH_TOKEN] == REFRESH_TOKEN
@ -146,7 +144,10 @@ async def test_auth_tokens_same(
@pytest.mark.parametrize("idx", TEST_DATA)
async def test_auth_tokens_past(
hass: HomeAssistant, hass_storage: dict[str, Any], idx: str
hass: HomeAssistant,
hass_storage: dict[str, Any],
evo_config: dict[str, str],
idx: str,
) -> None:
"""Test loading/saving authentication tokens with matching username, but expired."""
@ -158,7 +159,7 @@ async def test_auth_tokens_past(
hass_storage[DOMAIN] = DOMAIN_STORAGE_BASE | {"data": test_data}
mock_client = await setup_evohome(hass, TEST_CONFIG)
mock_client = await setup_evohome(hass, evo_config, install="minimal")
# Confirm client was instantiated with the cached tokens...
assert mock_client.call_args.kwargs[SZ_REFRESH_TOKEN] == REFRESH_TOKEN
@ -181,14 +182,17 @@ async def test_auth_tokens_past(
@pytest.mark.parametrize("idx", TEST_DATA)
async def test_auth_tokens_diff(
hass: HomeAssistant, hass_storage: dict[str, Any], idx: str
hass: HomeAssistant,
hass_storage: dict[str, Any],
evo_config: dict[str, str],
idx: str,
) -> None:
"""Test loading/saving authentication tokens when unmatched username."""
hass_storage[DOMAIN] = DOMAIN_STORAGE_BASE | {"data": TEST_DATA[idx]}
mock_client = await setup_evohome(
hass, TEST_CONFIG | {CONF_USERNAME: USERNAME_DIFF}
hass, evo_config | {CONF_USERNAME: USERNAME_DIFF}, install="minimal"
)
# Confirm client was instantiated without tokens, as username was different...