"""Define tests for the Daikin init.""" from datetime import timedelta from unittest.mock import AsyncMock, PropertyMock, patch from aiohttp import ClientConnectionError from freezegun.api import FrozenDateTimeFactory from pydaikin.exceptions import DaikinException import pytest from homeassistant.components.daikin import update_unique_id from homeassistant.components.daikin.const import DOMAIN, KEY_MAC from homeassistant.config_entries import ConfigEntryState from homeassistant.const import CONF_HOST, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er from .test_config_flow import HOST, MAC from tests.common import MockConfigEntry, async_fire_time_changed @pytest.fixture def mock_daikin(): """Mock pydaikin.""" async def mock_daikin_factory(*args, **kwargs): """Mock the init function in pydaikin.""" return Appliance with patch("homeassistant.components.daikin.DaikinFactory") as Appliance: Appliance.side_effect = mock_daikin_factory type(Appliance).update_status = AsyncMock() type(Appliance).device_ip = PropertyMock(return_value=HOST) type(Appliance).inside_temperature = PropertyMock(return_value=22) type(Appliance).target_temperature = PropertyMock(return_value=22) type(Appliance).zones = PropertyMock(return_value=[("Zone 1", "0", 0)]) type(Appliance).fan_rate = PropertyMock(return_value=[]) type(Appliance).swing_modes = PropertyMock(return_value=[]) yield Appliance DATA = { "ver": "1_1_8", "name": "DaikinAP00000", "mac": MAC, "model": "NOTSUPPORT", } INVALID_DATA = {**DATA, "name": None, "mac": HOST} async def test_duplicate_removal( hass: HomeAssistant, entity_registry: er.EntityRegistry, device_registry: dr.DeviceRegistry, mock_daikin, ) -> None: """Test duplicate device removal.""" config_entry = MockConfigEntry( domain=DOMAIN, unique_id=HOST, title=None, data={CONF_HOST: HOST, KEY_MAC: HOST}, ) config_entry.add_to_hass(hass) type(mock_daikin).mac = PropertyMock(return_value=HOST) type(mock_daikin).values = PropertyMock(return_value=INVALID_DATA) with patch( "homeassistant.components.daikin.async_migrate_unique_id", return_value=None ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert config_entry.unique_id != MAC type(mock_daikin).mac = PropertyMock(return_value=MAC) type(mock_daikin).values = PropertyMock(return_value=DATA) assert await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() assert ( device_registry.async_get_device_by_connection( (KEY_MAC, MAC), config_entry.entry_id ).name == "DaikinAP00000" ) assert ( device_registry.async_get_device_by_connection( (KEY_MAC, HOST), config_entry.entry_id ).name is None ) assert entity_registry.async_get("climate.daikin_127_0_0_1").unique_id == HOST assert entity_registry.async_get("switch.zone_1").unique_id.startswith(HOST) assert entity_registry.async_get("climate.daikinap00000").unique_id == MAC assert entity_registry.async_get( "switch.daikinap00000_zone_1" ).unique_id.startswith(MAC) assert await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() assert ( device_registry.async_get_device_by_connection( (KEY_MAC, MAC), config_entry.entry_id ).name == "DaikinAP00000" ) assert entity_registry.async_get("climate.daikinap00000") is None assert entity_registry.async_get("switch.daikinap00000_zone_1") is None assert entity_registry.async_get("climate.daikin_127_0_0_1").unique_id == MAC assert entity_registry.async_get("switch.zone_1").unique_id.startswith(MAC) async def test_unique_id_migrate( hass: HomeAssistant, entity_registry: er.EntityRegistry, device_registry: dr.DeviceRegistry, mock_daikin, ) -> None: """Test unique id migration.""" config_entry = MockConfigEntry( domain=DOMAIN, unique_id=HOST, title=None, data={CONF_HOST: HOST, KEY_MAC: HOST}, ) config_entry.add_to_hass(hass) type(mock_daikin).mac = PropertyMock(return_value=HOST) type(mock_daikin).values = PropertyMock(return_value=INVALID_DATA) assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert config_entry.unique_id == HOST assert ( device_registry.async_get_device_by_connection( (KEY_MAC, HOST), config_entry.entry_id ).name is None ) entity = entity_registry.async_get("climate.daikin_127_0_0_1") assert entity.unique_id == HOST assert update_unique_id(entity, MAC) is not None assert entity_registry.async_get("switch.zone_1").unique_id.startswith(HOST) type(mock_daikin).mac = PropertyMock(return_value=MAC) type(mock_daikin).values = PropertyMock(return_value=DATA) assert config_entry.unique_id != MAC assert await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() assert config_entry.unique_id == MAC assert ( device_registry.async_get_device_by_connection( (KEY_MAC, MAC), config_entry.entry_id ).name == "DaikinAP00000" ) entity = entity_registry.async_get("climate.daikin_127_0_0_1") assert entity.unique_id == MAC assert update_unique_id(entity, MAC) is None assert entity_registry.async_get("switch.zone_1").unique_id.startswith(MAC) async def test_client_update_connection_error( hass: HomeAssistant, mock_daikin, freezer: FrozenDateTimeFactory ) -> None: """Test client connection error on update.""" config_entry = MockConfigEntry( domain=DOMAIN, unique_id=MAC, data={CONF_HOST: HOST, KEY_MAC: MAC}, ) config_entry.add_to_hass(hass) type(mock_daikin).mac = PropertyMock(return_value=MAC) type(mock_daikin).values = PropertyMock(return_value=DATA) await hass.config_entries.async_setup(config_entry.entry_id) assert hass.states.get("climate.daikinap00000").state != STATE_UNAVAILABLE type(mock_daikin).update_status.side_effect = ClientConnectionError freezer.tick(timedelta(seconds=120)) async_fire_time_changed(hass) await hass.async_block_till_done() assert hass.states.get("climate.daikinap00000").state == STATE_UNAVAILABLE assert mock_daikin.update_status.call_count == 2 async def test_client_connection_error(hass: HomeAssistant, mock_daikin) -> None: """Test client connection error on setup.""" config_entry = MockConfigEntry( domain=DOMAIN, unique_id=MAC, data={CONF_HOST: HOST, KEY_MAC: MAC}, ) config_entry.add_to_hass(hass) mock_daikin.side_effect = ClientConnectionError await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert config_entry.state is ConfigEntryState.SETUP_RETRY async def test_timeout_error(hass: HomeAssistant, mock_daikin) -> None: """Test timeout error on setup.""" config_entry = MockConfigEntry( domain=DOMAIN, unique_id=MAC, data={CONF_HOST: HOST, KEY_MAC: MAC}, ) config_entry.add_to_hass(hass) mock_daikin.side_effect = TimeoutError await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert config_entry.state is ConfigEntryState.SETUP_RETRY async def test_daikin_exception_retries(hass: HomeAssistant, mock_daikin) -> None: """Test that a DaikinException during setup triggers SETUP_RETRY. A DaikinException (e.g. "Empty values." from DaikinAirBase.init when the device HTTP endpoint returns an empty response) is a transient condition — the device is not yet ready, not a permanent configuration problem — so the entry must land in SETUP_RETRY rather than SETUP_ERROR. """ config_entry = MockConfigEntry( domain=DOMAIN, unique_id=MAC, data={CONF_HOST: HOST, KEY_MAC: MAC}, ) config_entry.add_to_hass(hass) mock_daikin.side_effect = DaikinException("Empty values.") await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert config_entry.state is ConfigEntryState.SETUP_RETRY