"""Tests for Intergas InComfort integration.""" from datetime import timedelta from typing import Any from unittest.mock import MagicMock, patch from aiohttp import ClientResponseError, RequestInfo from freezegun.api import FrozenDateTimeFactory from incomfortclient import InvalidGateway, InvalidHeaterList import pytest from homeassistant.components.incomfort.const import DOMAIN from homeassistant.components.incomfort.coordinator import UPDATE_INTERVAL from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.const import STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.device_registry import DeviceRegistry from .conftest import MOCK_HEATER_STATUS from tests.common import MockConfigEntry, async_fire_time_changed @pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_setup_platforms( hass: HomeAssistant, mock_incomfort: MagicMock, entity_registry: er.EntityRegistry, mock_config_entry: MockConfigEntry, ) -> None: """Test the incomfort integration is set up correctly.""" await hass.config_entries.async_setup(mock_config_entry.entry_id) assert mock_config_entry.state is ConfigEntryState.LOADED @pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.parametrize("mock_entry_unique_id", ["00:04:a3:de:ad:ff"]) async def test_device_via_device_links( hass: HomeAssistant, device_registry: DeviceRegistry, mock_incomfort: MagicMock, mock_config_entry: MockConfigEntry, ) -> None: """Test that child devices link to the gateway via via_device_id.""" await hass.config_entries.async_setup(mock_config_entry.entry_id) assert mock_config_entry.state is ConfigEntryState.LOADED gateway_device = device_registry.async_get_device_by_identifier( (DOMAIN, mock_config_entry.entry_id), mock_config_entry.entry_id ) assert gateway_device is not None boiler_device = device_registry.async_get_device_by_identifier( (DOMAIN, "c0ffeec0ffee"), mock_config_entry.entry_id ) assert boiler_device is not None assert boiler_device.via_device_id == gateway_device.id thermostat_device = device_registry.async_get_device_by_identifier( (DOMAIN, "c0ffeec0ffee_1"), mock_config_entry.entry_id ) assert thermostat_device is not None assert thermostat_device.via_device_id == gateway_device.id @pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.parametrize( "mock_heater_status", [MOCK_HEATER_STATUS | {"serial_no": "c01d00c0ffee"}] ) async def test_stale_devices_cleanup( hass: HomeAssistant, device_registry: DeviceRegistry, mock_incomfort: MagicMock, entity_registry: er.EntityRegistry, mock_config_entry: MockConfigEntry, mock_heater_status: dict[str, Any], ) -> None: """Test the incomfort integration is cleaning up stale devices.""" # Setup an old heater with serial_no c01d00c0ffee await hass.config_entries.async_setup(mock_config_entry.entry_id) assert mock_config_entry.state is ConfigEntryState.LOADED await hass.config_entries.async_unload(mock_config_entry.entry_id) old_entries = dr.async_entries_for_config_entry( device_registry, mock_config_entry.entry_id ) assert len(old_entries) == 3 old_heater = device_registry.async_get_device_by_identifier( (DOMAIN, "c01d00c0ffee"), mock_config_entry.entry_id ) assert old_heater is not None assert old_heater.serial_number == "c01d00c0ffee" old_climate = device_registry.async_get_device_by_identifier( (DOMAIN, "c01d00c0ffee_1"), mock_config_entry.entry_id ) assert old_heater is not None old_climate = device_registry.async_get_device_by_identifier( (DOMAIN, "c01d00c0ffee_1"), mock_config_entry.entry_id ) assert old_climate is not None mock_heater_status["serial_no"] = "c0ffeec0ffee" await hass.config_entries.async_setup(mock_config_entry.entry_id) assert mock_config_entry.state is ConfigEntryState.LOADED new_entries = dr.async_entries_for_config_entry( device_registry, mock_config_entry.entry_id ) assert len(new_entries) == 3 new_heater = device_registry.async_get_device_by_identifier( (DOMAIN, "c0ffeec0ffee"), mock_config_entry.entry_id ) assert new_heater is not None assert new_heater.serial_number == "c0ffeec0ffee" new_climate = device_registry.async_get_device_by_identifier( (DOMAIN, "c0ffeec0ffee_1"), mock_config_entry.entry_id ) assert new_climate is not None old_heater = device_registry.async_get_device_by_identifier( (DOMAIN, "c01d00c0ffee"), mock_config_entry.entry_id ) assert old_heater is None old_climate = device_registry.async_get_device_by_identifier( (DOMAIN, "c01d00c0ffee_1"), mock_config_entry.entry_id ) assert old_climate is None @pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_coordinator_updates( hass: HomeAssistant, mock_incomfort: MagicMock, freezer: FrozenDateTimeFactory, mock_config_entry: ConfigEntry, ) -> None: """Test the incomfort coordinator is updating.""" await hass.config_entries.async_setup(mock_config_entry.entry_id) state = hass.states.get("climate.thermostat_1") assert state is not None assert state.attributes["current_temperature"] == 21.4 mock_incomfort().mock_room_status["room_temp"] = 20.91 state = hass.states.get("sensor.boiler_pressure") assert state is not None assert state.state == "1.86" mock_incomfort().mock_heater_status["pressure"] = 1.84 freezer.tick(timedelta(seconds=UPDATE_INTERVAL + 5)) async_fire_time_changed(hass) await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("climate.thermostat_1") assert state is not None assert state.attributes["current_temperature"] == 20.9 state = hass.states.get("sensor.boiler_pressure") assert state is not None assert state.state == "1.84" @pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.parametrize( "exc", [ ClientResponseError( RequestInfo( url="http://example.com", method="GET", headers=[], real_url="http://example.com", ), None, status=401, ), InvalidHeaterList, ClientResponseError( RequestInfo( url="http://example.com", method="GET", headers=[], real_url="http://example.com", ), None, status=500, ), TimeoutError, ], ) async def test_coordinator_update_fails( hass: HomeAssistant, mock_incomfort: MagicMock, freezer: FrozenDateTimeFactory, exc: Exception, mock_config_entry: ConfigEntry, ) -> None: """Test the incomfort coordinator update fails.""" await hass.config_entries.async_setup(mock_config_entry.entry_id) state = hass.states.get("sensor.boiler_pressure") assert state is not None assert state.state == "1.86" with patch.object( mock_incomfort().heaters.return_value[0], "update", side_effect=exc ): freezer.tick(timedelta(seconds=UPDATE_INTERVAL + 5)) async_fire_time_changed(hass) await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.boiler_pressure") assert state is not None assert state.state == STATE_UNAVAILABLE @pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.parametrize( ("exc", "config_entry_state"), [ ( InvalidGateway, ConfigEntryState.SETUP_ERROR, ), (InvalidHeaterList, ConfigEntryState.SETUP_RETRY), ( ClientResponseError( RequestInfo( url="http://example.com", method="GET", headers=[], real_url="http://example.com", ), None, status=404, ), ConfigEntryState.SETUP_RETRY, ), ( ClientResponseError( RequestInfo( url="http://example.com", method="GET", headers=[], real_url="http://example.com", ), None, status=500, ), ConfigEntryState.SETUP_RETRY, ), (TimeoutError, ConfigEntryState.SETUP_RETRY), ], ) async def test_entry_setup_fails( hass: HomeAssistant, mock_incomfort: MagicMock, freezer: FrozenDateTimeFactory, mock_config_entry: ConfigEntry, exc: Exception, config_entry_state: ConfigEntryState, ) -> None: """Test the incomfort coordinator entry setup fails.""" mock_incomfort().heaters.side_effect = exc await hass.config_entries.async_setup(mock_config_entry.entry_id) state = hass.states.get("sensor.boiler_pressure") assert state is None assert mock_config_entry.state is config_entry_state