"""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_TEMPERATURE, DOMAIN as CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_TEMPERATURE, 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 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 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) 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, ) mock_on.assert_called_once() assert error.from_exception == InvalidCommand @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() 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, ) assert error 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, ) assert error 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, )