Files
core/tests/components/airobot/test_climate.py

270 lines
8.3 KiB
Python

"""Test the Airobot climate platform."""
from datetime import timedelta
from unittest.mock import AsyncMock
from freezegun.api import FrozenDateTimeFactory
from pyairobotrest.const import MODE_AWAY, MODE_HOME
from pyairobotrest.exceptions import AirobotConnectionError, AirobotError
from pyairobotrest.models import ThermostatSettings, ThermostatStatus
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.climate import (
ATTR_PRESET_MODE,
ATTR_TEMPERATURE,
DOMAIN as CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
SERVICE_SET_TEMPERATURE,
)
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
import homeassistant.helpers.entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
@pytest.fixture
def platforms() -> list[Platform]:
"""Fixture to specify platforms to test."""
return [Platform.CLIMATE]
@pytest.mark.usefixtures("init_integration")
async def test_climate_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
platforms: list[Platform],
) -> None:
"""Test climate entities."""
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
("mode", "temperature", "method"),
[
(1, 24.0, "set_home_temperature"), # Home mode
(0, 18.0, "set_away_temperature"), # Away mode
],
)
async def test_climate_set_temperature(
hass: HomeAssistant,
mock_airobot_client: AsyncMock,
mock_settings: ThermostatSettings,
mock_config_entry: MockConfigEntry,
mode: int,
temperature: float,
method: str,
) -> None:
"""Test setting temperature in different modes."""
# Set device mode
mock_settings.mode = mode
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: "climate.test_thermostat",
ATTR_TEMPERATURE: temperature,
},
blocking=True,
)
getattr(mock_airobot_client, method).assert_called_once_with(temperature)
@pytest.mark.usefixtures("init_integration")
async def test_climate_set_temperature_error(
hass: HomeAssistant,
mock_airobot_client: AsyncMock,
) -> None:
"""Test error handling when setting temperature fails."""
mock_airobot_client.set_home_temperature.side_effect = AirobotError("Device error")
with pytest.raises(
ServiceValidationError, match="Failed to set temperature"
) as exc_info:
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: "climate.test_thermostat",
ATTR_TEMPERATURE: 24.0,
},
blocking=True,
)
assert exc_info.value.translation_domain == "airobot"
assert exc_info.value.translation_key == "set_temperature_failed"
assert exc_info.value.translation_placeholders == {"temperature": "24.0"}
@pytest.mark.parametrize(
("preset_mode", "method", "arg"),
[
("home", "set_mode", MODE_HOME),
("away", "set_mode", MODE_AWAY),
("boost", "set_boost_mode", True),
],
)
@pytest.mark.usefixtures("init_integration")
async def test_climate_set_preset_mode(
hass: HomeAssistant,
mock_airobot_client: AsyncMock,
preset_mode: str,
method: str,
arg: int | bool,
) -> None:
"""Test setting different preset modes."""
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{
ATTR_ENTITY_ID: "climate.test_thermostat",
ATTR_PRESET_MODE: preset_mode,
},
blocking=True,
)
getattr(mock_airobot_client, method).assert_called_once_with(arg)
async def test_climate_set_preset_mode_from_boost_to_home(
hass: HomeAssistant,
mock_airobot_client: AsyncMock,
mock_settings: ThermostatSettings,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test disabling boost when switching to home mode."""
# Set boost mode enabled
mock_settings.setting_flags.boost_enabled = True
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{
ATTR_ENTITY_ID: "climate.test_thermostat",
ATTR_PRESET_MODE: "home",
},
blocking=True,
)
# Should disable boost first, then set mode to home
mock_airobot_client.set_boost_mode.assert_called_once_with(False)
mock_airobot_client.set_mode.assert_called_once_with(MODE_HOME)
@pytest.mark.usefixtures("init_integration")
async def test_climate_set_preset_mode_error(
hass: HomeAssistant,
mock_airobot_client: AsyncMock,
) -> None:
"""Test error handling when setting preset mode fails."""
mock_airobot_client.set_boost_mode.side_effect = AirobotError("Device error")
with pytest.raises(
ServiceValidationError, match="Failed to set preset mode"
) as exc_info:
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{
ATTR_ENTITY_ID: "climate.test_thermostat",
ATTR_PRESET_MODE: "boost",
},
blocking=True,
)
assert exc_info.value.translation_domain == "airobot"
assert exc_info.value.translation_key == "set_preset_mode_failed"
assert exc_info.value.translation_placeholders == {"preset_mode": "boost"}
async def test_climate_heating_state(
hass: HomeAssistant,
mock_airobot_client: AsyncMock,
mock_status: ThermostatStatus,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test climate entity shows heating action when heating."""
# Set heating on
mock_status.status_flags.heating_on = True
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("climate.test_thermostat")
assert state
assert state.attributes.get("hvac_action") == "heating"
@pytest.mark.usefixtures("init_integration")
async def test_climate_unavailable_on_update_failure(
hass: HomeAssistant,
mock_airobot_client: AsyncMock,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test climate entity becomes unavailable when coordinator update fails."""
# Initially available
state = hass.states.get("climate.test_thermostat")
assert state
assert state.state != "unavailable"
# Simulate connection error during update
mock_airobot_client.get_statuses.side_effect = AirobotConnectionError(
"Connection lost"
)
mock_airobot_client.get_settings.side_effect = AirobotConnectionError(
"Connection lost"
)
# Advance time to trigger coordinator update (30 second interval)
freezer.tick(timedelta(seconds=35))
await hass.async_block_till_done()
# Entity should now be unavailable
state = hass.states.get("climate.test_thermostat")
assert state
assert state.state == "unavailable"
@pytest.mark.parametrize(
("temp_floor", "temp_air", "expected_temp"),
[
(25.0, 22.0, 25.0), # Floor sensor available - should use floor temp
(None, 22.0, 22.0), # Floor sensor not available - should use air temp
],
)
async def test_climate_current_temperature(
hass: HomeAssistant,
mock_airobot_client: AsyncMock,
mock_status: ThermostatStatus,
mock_config_entry: MockConfigEntry,
temp_floor: float | None,
temp_air: float,
expected_temp: float,
) -> None:
"""Test current temperature prioritizes floor sensor when available."""
mock_status.temp_floor = temp_floor
mock_status.temp_air = temp_air
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("climate.test_thermostat")
assert state
assert state.attributes.get("current_temperature") == expected_temp