mirror of
https://github.com/home-assistant/core.git
synced 2026-07-01 11:46:40 +01:00
356 lines
10 KiB
Python
356 lines
10 KiB
Python
"""Climate platform tests for iAquaLink."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
from unittest.mock import AsyncMock
|
|
|
|
import httpx
|
|
from iaqualink.client import AqualinkClient
|
|
from iaqualink.exception import (
|
|
AqualinkServiceException,
|
|
AqualinkServiceUnauthorizedException,
|
|
)
|
|
from iaqualink.systems.iaqua.device import (
|
|
AqualinkState,
|
|
IaquaAuxSwitch,
|
|
IaquaSensor,
|
|
IaquaThermostat,
|
|
)
|
|
from iaqualink.systems.iaqua.system import IaquaSystem
|
|
import pytest
|
|
from syrupy.assertion import SnapshotAssertion
|
|
|
|
from homeassistant.components.climate import (
|
|
ATTR_HVAC_MODE,
|
|
DOMAIN as CLIMATE_DOMAIN,
|
|
SERVICE_SET_HVAC_MODE,
|
|
SERVICE_SET_TEMPERATURE,
|
|
HVACAction,
|
|
HVACMode,
|
|
)
|
|
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError
|
|
from homeassistant.helpers import entity_registry as er
|
|
from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
|
|
|
|
from .conftest import (
|
|
assert_platform_setup,
|
|
get_aqualink_device,
|
|
get_aqualink_system,
|
|
setup_entry,
|
|
)
|
|
|
|
from tests.common import MockConfigEntry
|
|
|
|
|
|
async def test_setup(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
client: AqualinkClient,
|
|
entity_registry: er.EntityRegistry,
|
|
snapshot: SnapshotAssertion,
|
|
) -> None:
|
|
"""Test all climate entities are created correctly."""
|
|
await assert_platform_setup(
|
|
hass, config_entry, client, entity_registry, snapshot, CLIMATE_DOMAIN
|
|
)
|
|
|
|
|
|
async def _setup_thermostat(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
client: AqualinkClient,
|
|
*,
|
|
temp_unit: str = "F",
|
|
heater_state: str = AqualinkState.ON.value,
|
|
target_temperature: str = "84",
|
|
current_temperature: str = "80",
|
|
) -> tuple[IaquaSystem, object, object, object, str, object]:
|
|
"""Set up the integration with a single thermostat entity."""
|
|
if temp_unit == "F":
|
|
hass.config.units = US_CUSTOMARY_SYSTEM
|
|
|
|
system = get_aqualink_system(
|
|
client,
|
|
cls=IaquaSystem,
|
|
data={"home_screen": [{}, {}, {}, {"temp_scale": temp_unit}]},
|
|
)
|
|
system.online = True
|
|
|
|
async def update() -> None:
|
|
system.temp_unit = temp_unit
|
|
|
|
system.update = AsyncMock(side_effect=update)
|
|
heater = get_aqualink_device(
|
|
system,
|
|
name="pool_heater",
|
|
cls=IaquaAuxSwitch,
|
|
data={"state": heater_state, "aux": "1"},
|
|
)
|
|
sensor = get_aqualink_device(
|
|
system,
|
|
name="pool_temp",
|
|
cls=IaquaSensor,
|
|
data={"state": current_temperature},
|
|
)
|
|
thermostat = get_aqualink_device(
|
|
system,
|
|
name="pool_set_point",
|
|
cls=IaquaThermostat,
|
|
data={"state": target_temperature},
|
|
)
|
|
system.devices = {
|
|
heater.name: heater,
|
|
sensor.name: sensor,
|
|
thermostat.name: thermostat,
|
|
}
|
|
system.get_devices = AsyncMock(return_value={thermostat.name: thermostat})
|
|
system.set_aux = AsyncMock()
|
|
system.set_temps = AsyncMock()
|
|
|
|
await setup_entry(hass, config_entry, system)
|
|
|
|
entity_ids = hass.states.async_entity_ids(CLIMATE_DOMAIN)
|
|
assert len(entity_ids) == 1
|
|
entity_id = entity_ids[0]
|
|
entity_state = hass.states.get(entity_id)
|
|
assert entity_state is not None
|
|
return system, heater, sensor, thermostat, entity_id, entity_state
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("heater_state", "expected_action", "expected_mode"),
|
|
[
|
|
pytest.param(
|
|
AqualinkState.ON.value, HVACAction.HEATING, HVACMode.HEAT, id="heating"
|
|
),
|
|
pytest.param(
|
|
AqualinkState.ENABLED.value, HVACAction.IDLE, HVACMode.OFF, id="idle"
|
|
),
|
|
pytest.param(AqualinkState.OFF.value, HVACAction.OFF, HVACMode.OFF, id="off"),
|
|
],
|
|
)
|
|
async def test_thermostat_properties(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
client: AqualinkClient,
|
|
heater_state: str,
|
|
expected_action: HVACAction,
|
|
expected_mode: HVACMode,
|
|
) -> None:
|
|
"""Test thermostat properties and HVAC action mapping."""
|
|
_, _, _, _, _, entity_state = await _setup_thermostat(
|
|
hass,
|
|
config_entry,
|
|
client,
|
|
heater_state=heater_state,
|
|
)
|
|
|
|
assert entity_state.state == expected_mode
|
|
assert entity_state.attributes["hvac_action"] == expected_action
|
|
assert entity_state.attributes["temperature"] == 84.0
|
|
assert entity_state.attributes["current_temperature"] == 80.0
|
|
|
|
|
|
async def test_thermostat_current_temperature_none(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
client: AqualinkClient,
|
|
) -> None:
|
|
"""Test thermostat current temperature handles empty values."""
|
|
_, _, _, _, _, entity_state = await _setup_thermostat(
|
|
hass,
|
|
config_entry,
|
|
client,
|
|
temp_unit="C",
|
|
heater_state=AqualinkState.OFF.value,
|
|
target_temperature="24",
|
|
current_temperature="",
|
|
)
|
|
|
|
assert entity_state.state == HVACMode.OFF
|
|
assert entity_state.attributes["current_temperature"] is None
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("hvac_mode", "initial_state", "expected_state"),
|
|
[
|
|
pytest.param(HVACMode.HEAT, AqualinkState.OFF.value, HVACMode.HEAT, id="heat"),
|
|
pytest.param(HVACMode.OFF, AqualinkState.ON.value, HVACMode.OFF, id="off"),
|
|
],
|
|
)
|
|
async def test_thermostat_set_hvac_mode(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
client: AqualinkClient,
|
|
hvac_mode: HVACMode,
|
|
initial_state: str,
|
|
expected_state: HVACMode,
|
|
) -> None:
|
|
"""Test thermostat HVAC mode service updates Home Assistant state."""
|
|
system, heater, _, _, entity_id, _ = await _setup_thermostat(
|
|
hass,
|
|
config_entry,
|
|
client,
|
|
heater_state=initial_state,
|
|
)
|
|
|
|
async def set_aux(_: str) -> None:
|
|
heater.data["state"] = (
|
|
AqualinkState.ON.value
|
|
if hvac_mode == HVACMode.HEAT
|
|
else AqualinkState.OFF.value
|
|
)
|
|
|
|
system.set_aux = AsyncMock(side_effect=set_aux)
|
|
|
|
await hass.services.async_call(
|
|
CLIMATE_DOMAIN,
|
|
SERVICE_SET_HVAC_MODE,
|
|
{ATTR_ENTITY_ID: entity_id, ATTR_HVAC_MODE: hvac_mode},
|
|
blocking=True,
|
|
)
|
|
|
|
entity_state = hass.states.get(entity_id)
|
|
assert entity_state is not None
|
|
assert entity_state.state == expected_state
|
|
|
|
|
|
async def test_thermostat_set_temperature(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
client: AqualinkClient,
|
|
) -> None:
|
|
"""Test thermostat target temperature service updates Home Assistant state."""
|
|
system, _, _, thermostat, entity_id, _ = await _setup_thermostat(
|
|
hass,
|
|
config_entry,
|
|
client,
|
|
)
|
|
|
|
async def set_temps(data: dict[str, str]) -> None:
|
|
thermostat.data["state"] = data["temp1"]
|
|
|
|
system.set_temps = AsyncMock(side_effect=set_temps)
|
|
|
|
await hass.services.async_call(
|
|
CLIMATE_DOMAIN,
|
|
SERVICE_SET_TEMPERATURE,
|
|
{ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 84.9},
|
|
blocking=True,
|
|
)
|
|
|
|
entity_state = hass.states.get(entity_id)
|
|
assert entity_state is not None
|
|
assert entity_state.attributes["temperature"] == 84.0
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("service", "service_data", "system_attr"),
|
|
[
|
|
pytest.param(
|
|
SERVICE_SET_HVAC_MODE,
|
|
{ATTR_HVAC_MODE: HVACMode.HEAT},
|
|
"set_aux",
|
|
id="set-hvac-mode",
|
|
),
|
|
pytest.param(
|
|
SERVICE_SET_TEMPERATURE,
|
|
{ATTR_TEMPERATURE: 84},
|
|
"set_temps",
|
|
id="set-temperature",
|
|
),
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("raised_exception", "expected_exception", "match"),
|
|
[
|
|
pytest.param(
|
|
AqualinkServiceException,
|
|
HomeAssistantError,
|
|
"Aqualink error: AqualinkServiceException",
|
|
id="service",
|
|
),
|
|
pytest.param(
|
|
TimeoutError(),
|
|
HomeAssistantError,
|
|
"Aqualink error: TimeoutError",
|
|
id="timeout",
|
|
),
|
|
pytest.param(
|
|
httpx.HTTPError("boom"),
|
|
HomeAssistantError,
|
|
"Aqualink error: boom",
|
|
id="http",
|
|
),
|
|
pytest.param(
|
|
AqualinkServiceUnauthorizedException,
|
|
ConfigEntryAuthFailed,
|
|
"Invalid credentials for iAquaLink",
|
|
id="unauthorized",
|
|
),
|
|
],
|
|
)
|
|
async def test_climate_action_errors_leave_state_unchanged(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
client: AqualinkClient,
|
|
service: str,
|
|
service_data: dict[str, Any],
|
|
system_attr: str,
|
|
raised_exception: Exception | type[Exception],
|
|
expected_exception: type[Exception],
|
|
match: str,
|
|
) -> None:
|
|
"""Test climate action errors are surfaced through the service call."""
|
|
system, _, _, _, entity_id, entity_state = await _setup_thermostat(
|
|
hass,
|
|
config_entry,
|
|
client,
|
|
heater_state=AqualinkState.OFF.value,
|
|
)
|
|
initial_state = entity_state.state
|
|
setattr(system, system_attr, AsyncMock(side_effect=raised_exception))
|
|
|
|
with pytest.raises(expected_exception, match=match):
|
|
await hass.services.async_call(
|
|
CLIMATE_DOMAIN,
|
|
service,
|
|
{ATTR_ENTITY_ID: entity_id, **service_data},
|
|
blocking=True,
|
|
)
|
|
|
|
entity_state = hass.states.get(entity_id)
|
|
assert entity_state is not None
|
|
assert entity_state.state == initial_state
|
|
|
|
|
|
async def test_thermostat_set_unknown_hvac_mode_logs_warning(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
client: AqualinkClient,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
"""Test unsupported HVAC modes log a warning and keep state unchanged."""
|
|
system, _, _, _, entity_id, _ = await _setup_thermostat(
|
|
hass,
|
|
config_entry,
|
|
client,
|
|
heater_state=AqualinkState.OFF.value,
|
|
)
|
|
climate_component = hass.data[CLIMATE_DOMAIN]
|
|
entity = climate_component.get_entity(entity_id)
|
|
assert entity is not None
|
|
|
|
with caplog.at_level(logging.WARNING):
|
|
await entity.async_set_hvac_mode(HVACMode.COOL)
|
|
|
|
entity_state = hass.states.get(entity_id)
|
|
assert entity_state is not None
|
|
assert entity_state.state == HVACMode.OFF
|
|
assert "Unknown operation mode" in caplog.text
|
|
system.set_aux.assert_not_called()
|