"""Tests for the Fumis climate entity.""" from datetime import timedelta from unittest.mock import MagicMock from freezegun.api import FrozenDateTimeFactory from fumis import ( FumisAuthenticationError, FumisConnectionError, FumisError, FumisStoveOfflineError, ) import pytest from syrupy.assertion import SnapshotAssertion from homeassistant.components.climate import ( ATTR_HVAC_MODE, ATTR_TEMPERATURE, DOMAIN as CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, SERVICE_SET_TEMPERATURE, SERVICE_TURN_OFF, SERVICE_TURN_ON, HVACMode, ) from homeassistant.components.fumis.const import DOMAIN from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import async_fire_time_changed pytestmark = pytest.mark.usefixtures("init_integration") async def test_climate_entity( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion, ) -> None: """Test the Fumis climate entity.""" assert (state := hass.states.get("climate.clou_duo")) assert state == snapshot assert (entity_entry := entity_registry.async_get(state.entity_id)) assert entity_entry == snapshot assert entity_entry.device_id assert (device_entry := device_registry.async_get(entity_entry.device_id)) assert device_entry == snapshot async def test_set_hvac_mode_heat( hass: HomeAssistant, mock_fumis: MagicMock, ) -> None: """Test setting HVAC mode to heat.""" await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, {ATTR_ENTITY_ID: "climate.clou_duo", ATTR_HVAC_MODE: HVACMode.HEAT}, blocking=True, ) mock_fumis.turn_on.assert_called_once() async def test_set_hvac_mode_off( hass: HomeAssistant, mock_fumis: MagicMock, ) -> None: """Test setting HVAC mode to off.""" await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, {ATTR_ENTITY_ID: "climate.clou_duo", ATTR_HVAC_MODE: HVACMode.OFF}, blocking=True, ) mock_fumis.turn_off.assert_called_once() async def test_set_temperature( hass: HomeAssistant, mock_fumis: MagicMock, ) -> None: """Test setting the target temperature.""" await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, {ATTR_ENTITY_ID: "climate.clou_duo", ATTR_TEMPERATURE: 22.5}, blocking=True, ) mock_fumis.set_target_temperature.assert_called_once_with(22.5) async def test_turn_on( hass: HomeAssistant, mock_fumis: MagicMock, ) -> None: """Test turning on the stove.""" await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: "climate.clou_duo"}, blocking=True, ) mock_fumis.turn_on.assert_called_once() async def test_turn_off( hass: HomeAssistant, mock_fumis: MagicMock, ) -> None: """Test turning off the stove.""" await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "climate.clou_duo"}, blocking=True, ) mock_fumis.turn_off.assert_called_once() @pytest.mark.parametrize( ("side_effect", "expected_translation_key"), [ (FumisAuthenticationError, "authentication_error"), (FumisStoveOfflineError, "stove_offline"), (FumisConnectionError, "communication_error"), (FumisError, "unknown_error"), ], ) async def test_climate_error_handling( hass: HomeAssistant, mock_fumis: MagicMock, side_effect: type[Exception], expected_translation_key: str, ) -> None: """Test error handling for climate actions.""" mock_fumis.turn_on.side_effect = side_effect with pytest.raises(HomeAssistantError) as exc_info: await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: "climate.clou_duo"}, blocking=True, ) assert exc_info.value.translation_domain == DOMAIN assert exc_info.value.translation_key == expected_translation_key @pytest.mark.parametrize( "side_effect", [ FumisStoveOfflineError, FumisConnectionError, FumisError, ], ) async def test_climate_unavailable_on_update_error( hass: HomeAssistant, mock_fumis: MagicMock, freezer: FrozenDateTimeFactory, side_effect: type[Exception], ) -> None: """Test climate entity becomes unavailable on update error and recovers.""" mock_fumis.update_info.side_effect = side_effect freezer.tick(timedelta(seconds=35)) async_fire_time_changed(hass) await hass.async_block_till_done() assert (state := hass.states.get("climate.clou_duo")) assert state.state == STATE_UNAVAILABLE mock_fumis.update_info.side_effect = None freezer.tick(timedelta(seconds=35)) async_fire_time_changed(hass) await hass.async_block_till_done() assert (state := hass.states.get("climate.clou_duo")) assert state.state != STATE_UNAVAILABLE