"""Test the Teslemetry climate platform.""" from unittest.mock import AsyncMock, patch from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion from tesla_fleet_api.exceptions import InvalidCommand from homeassistant.components.climate import ( ATTR_HVAC_MODE, ATTR_PRESET_MODE, 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.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, VEHICLE_DATA_ASLEEP, WAKE_UP_ASLEEP, WAKE_UP_ONLINE, ) @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 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", ): # Invalid Temp await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, {ATTR_ENTITY_ID: [entity_id], ATTR_TEMPERATURE: 34}, blocking=True, ) @pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_climate_alt( hass: HomeAssistant, snapshot: SnapshotAssertion, entity_registry: er.EntityRegistry, mock_vehicle_data: AsyncMock, ) -> 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) async def test_invalid_error(hass: HomeAssistant, snapshot: SnapshotAssertion) -> 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, ) mock_on.assert_called_once() assert str(error.value) == snapshot(name="error") @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, ) 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: AsyncMock, mock_wake_up: AsyncMock, mock_vehicle: AsyncMock, freezer: FrozenDateTimeFactory, snapshot: SnapshotAssertion, ) -> None: """Tests asleep is handled.""" mock_vehicle_data.return_value = VEHICLE_DATA_ASLEEP await setup_platform(hass, [Platform.CLIMATE]) entity_id = "climate.test_climate" # 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, ) assert str(error.value) == snapshot(name="InvalidCommand") 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.helpers.asyncio.sleep"), pytest.raises(HomeAssistantError) as error, ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: [entity_id]}, blocking=True, ) assert str(error.value) == snapshot(name="HomeAssistantError") 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, snapshot: SnapshotAssertion, entity_registry: er.EntityRegistry, mock_metadata: AsyncMock, ) -> None: """Tests that the climate entity is correct.""" mock_metadata.return_value = METADATA_NOSCOPE entry = await setup_platform(hass, [Platform.CLIMATE]) entity_entries = er.async_entries_for_config_entry(entity_registry, entry.entry_id) assert entity_entries for entity_entry in entity_entries: assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry") 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, )