hass-core/tests/components/teslemetry/test_climate.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

398 lines
12 KiB
Python
Raw Normal View History

"""Test the Teslemetry climate platform."""
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy import SnapshotAssertion
from tesla_fleet_api.exceptions import InvalidCommand, VehicleOffline
from homeassistant.components.climate import (
ATTR_HVAC_MODE,
ATTR_PRESET_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
ATTR_TEMPERATURE,
DOMAIN as CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
SERVICE_SET_PRESET_MODE,
SERVICE_SET_TEMPERATURE,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
HVACMode,
)
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import entity_registry as er
from . import assert_entities, setup_platform
from .const import (
COMMAND_ERRORS,
COMMAND_IGNORED_REASON,
METADATA_NOSCOPE,
VEHICLE_DATA_ALT,
WAKE_UP_ASLEEP,
WAKE_UP_ONLINE,
)
from tests.common import async_fire_time_changed
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_climate(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
) -> None:
"""Tests that the climate entity is correct."""
entry = await setup_platform(hass, [Platform.CLIMATE])
assert_entities(hass, entry.entry_id, entity_registry, snapshot)
entity_id = "climate.test_climate"
# Turn On and Set Temp
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: [entity_id],
ATTR_TEMPERATURE: 20,
ATTR_HVAC_MODE: HVACMode.HEAT_COOL,
},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TEMPERATURE] == 20
assert state.state == HVACMode.HEAT_COOL
# Set Temp
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: [entity_id],
ATTR_TEMPERATURE: 21,
},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TEMPERATURE] == 21
# Set Preset
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: [entity_id], ATTR_PRESET_MODE: "keep"},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_PRESET_MODE] == "keep"
# Set Preset
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: [entity_id], ATTR_PRESET_MODE: "off"},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_PRESET_MODE] == "off"
# Turn Off
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{ATTR_ENTITY_ID: [entity_id], ATTR_HVAC_MODE: HVACMode.OFF},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.state == HVACMode.OFF
entity_id = "climate.test_cabin_overheat_protection"
# Turn On and Set Low
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: [entity_id],
ATTR_TEMPERATURE: 30,
ATTR_HVAC_MODE: HVACMode.FAN_ONLY,
},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TEMPERATURE] == 30
assert state.state == HVACMode.FAN_ONLY
# Set Temp Medium
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: [entity_id],
ATTR_TEMPERATURE: 35,
},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TEMPERATURE] == 35
# Set Temp High
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: [entity_id],
ATTR_TEMPERATURE: 40,
},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TEMPERATURE] == 40
# Turn Off
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: [entity_id]},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.state == HVACMode.OFF
# Turn On
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: [entity_id]},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.state == HVACMode.COOL
# Set Temp do nothing
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: [entity_id],
ATTR_TARGET_TEMP_HIGH: 30,
ATTR_TARGET_TEMP_LOW: 30,
},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TEMPERATURE] == 40
assert state.state == HVACMode.COOL
# pytest raises ServiceValidationError
with pytest.raises(
ServiceValidationError,
match="Cabin overheat protection does not support that temperature",
) as error:
# Invalid Temp
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{ATTR_ENTITY_ID: [entity_id], ATTR_TEMPERATURE: 25},
blocking=True,
)
assert error
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_climate_alt(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_vehicle_data,
) -> None:
"""Tests that the climate entity is correct."""
mock_vehicle_data.return_value = VEHICLE_DATA_ALT
entry = await setup_platform(hass, [Platform.CLIMATE])
assert_entities(hass, entry.entry_id, entity_registry, snapshot)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_climate_offline(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_vehicle_data,
) -> None:
"""Tests that the climate entity is correct."""
mock_vehicle_data.side_effect = VehicleOffline
entry = await setup_platform(hass, [Platform.CLIMATE])
assert_entities(hass, entry.entry_id, entity_registry, snapshot)
async def test_invalid_error(hass: HomeAssistant) -> None:
"""Tests service error is handled."""
await setup_platform(hass, platforms=[Platform.CLIMATE])
entity_id = "climate.test_climate"
with (
patch(
"homeassistant.components.teslemetry.VehicleSpecific.auto_conditioning_start",
side_effect=InvalidCommand,
) as mock_on,
pytest.raises(HomeAssistantError) as error,
):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: [entity_id]},
blocking=True,
)
2024-06-08 17:59:08 +02:00
mock_on.assert_called_once()
assert (
str(error.value)
== "Teslemetry command failed, The data request or command is unknown."
)
@pytest.mark.parametrize("response", COMMAND_ERRORS)
async def test_errors(hass: HomeAssistant, response: str) -> None:
"""Tests service reason is handled."""
await setup_platform(hass, platforms=[Platform.CLIMATE])
entity_id = "climate.test_climate"
with (
patch(
"homeassistant.components.teslemetry.VehicleSpecific.auto_conditioning_start",
return_value=response,
) as mock_on,
pytest.raises(HomeAssistantError),
):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: [entity_id]},
blocking=True,
)
2024-06-08 17:59:08 +02:00
mock_on.assert_called_once()
async def test_ignored_error(
hass: HomeAssistant,
) -> None:
"""Tests ignored error is handled."""
await setup_platform(hass, [Platform.CLIMATE])
entity_id = "climate.test_climate"
with patch(
"homeassistant.components.teslemetry.VehicleSpecific.auto_conditioning_start",
return_value=COMMAND_IGNORED_REASON,
) as mock_on:
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: [entity_id]},
blocking=True,
)
mock_on.assert_called_once()
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_asleep_or_offline(
hass: HomeAssistant,
mock_vehicle_data,
mock_wake_up,
mock_vehicle,
freezer: FrozenDateTimeFactory,
) -> None:
"""Tests asleep is handled."""
await setup_platform(hass, [Platform.CLIMATE])
entity_id = "climate.test_climate"
mock_vehicle_data.assert_called_once()
# Put the vehicle alseep
mock_vehicle_data.reset_mock()
mock_vehicle_data.side_effect = VehicleOffline
freezer.tick(VEHICLE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
mock_vehicle_data.assert_called_once()
mock_wake_up.reset_mock()
# Run a command but fail trying to wake up the vehicle
mock_wake_up.side_effect = InvalidCommand
with pytest.raises(HomeAssistantError) as error:
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: [entity_id]},
blocking=True,
)
2024-06-08 17:59:08 +02:00
assert str(error.value) == "The data request or command is unknown."
mock_wake_up.assert_called_once()
mock_wake_up.side_effect = None
mock_wake_up.reset_mock()
# Run a command but timeout trying to wake up the vehicle
mock_wake_up.return_value = WAKE_UP_ASLEEP
mock_vehicle.return_value = WAKE_UP_ASLEEP
with (
patch("homeassistant.components.teslemetry.entity.asyncio.sleep"),
pytest.raises(HomeAssistantError) as error,
):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: [entity_id]},
blocking=True,
)
2024-06-08 17:59:08 +02:00
assert str(error.value) == "Could not wake up vehicle"
mock_wake_up.assert_called_once()
mock_vehicle.assert_called()
mock_wake_up.reset_mock()
mock_vehicle.reset_mock()
mock_wake_up.return_value = WAKE_UP_ONLINE
mock_vehicle.return_value = WAKE_UP_ONLINE
# Run a command and wake up the vehicle immediately
await hass.services.async_call(
CLIMATE_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: [entity_id]}, blocking=True
)
await hass.async_block_till_done()
mock_wake_up.assert_called_once()
async def test_climate_noscope(
hass: HomeAssistant,
mock_metadata,
) -> None:
"""Tests that the climate entity is correct."""
mock_metadata.return_value = METADATA_NOSCOPE
await setup_platform(hass, [Platform.CLIMATE])
entity_id = "climate.test_climate"
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{ATTR_ENTITY_ID: [entity_id], ATTR_HVAC_MODE: HVACMode.HEAT_COOL},
blocking=True,
)
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{ATTR_ENTITY_ID: [entity_id], ATTR_TEMPERATURE: 20},
blocking=True,
)