From 6e59d1cb29fcb764072e4df03af67c521481340d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 14:08:02 -1000 Subject: [PATCH] Migrate homekit to use async_at_started (#113102) --- homeassistant/components/homekit/__init__.py | 11 +- tests/components/homekit/test_config_flow.py | 186 ++++++++++--------- tests/components/homekit/test_homekit.py | 12 +- tests/components/homekit/test_init.py | 6 +- 4 files changed, 112 insertions(+), 103 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 376962f376e..3f7b210832d 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -45,13 +45,11 @@ from homeassistant.const import ( CONF_IP_ADDRESS, CONF_NAME, CONF_PORT, - EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, SERVICE_RELOAD, ) from homeassistant.core import ( CALLBACK_TYPE, - CoreState, HomeAssistant, ServiceCall, State, @@ -75,6 +73,7 @@ from homeassistant.helpers.service import ( async_extract_referenced_entity_ids, async_register_admin_service, ) +from homeassistant.helpers.start import async_at_started from homeassistant.helpers.typing import ConfigType from homeassistant.loader import IntegrationNotFound, async_get_integration @@ -358,10 +357,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) hass.data[DOMAIN][entry.entry_id] = entry_data - if hass.state is CoreState.running: + async def _async_start_homekit(hass: HomeAssistant) -> None: await homekit.async_start() - else: - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, homekit.async_start) + + entry.async_on_unload(async_at_started(hass, _async_start_homekit)) return True @@ -553,7 +552,6 @@ class HomeKit: """Set up bridge and accessory driver.""" assert self.iid_storage is not None persist_file = get_persist_fullpath_for_entry_id(self.hass, self._entry_id) - self.driver = HomeDriver( self.hass, self._entry_id, @@ -569,7 +567,6 @@ class HomeKit: loader=get_loader(), iid_storage=self.iid_storage, ) - # If we do not load the mac address will be wrong # as pyhap uses a random one until state is restored if os.path.exists(persist_file): diff --git a/tests/components/homekit/test_config_flow.py b/tests/components/homekit/test_config_flow.py index dd389207b8d..fbcc33b9c7d 100644 --- a/tests/components/homekit/test_config_flow.py +++ b/tests/components/homekit/test_config_flow.py @@ -1,6 +1,6 @@ """Test the HomeKit config flow.""" -from unittest.mock import patch +from unittest.mock import AsyncMock, Mock, patch import pytest import voluptuous as vol @@ -428,62 +428,68 @@ async def test_options_flow_devices( demo_config_entry = MockConfigEntry(domain="domain") demo_config_entry.add_to_hass(hass) - assert await async_setup_component(hass, "homeassistant", {}) - assert await async_setup_component(hass, "demo", {"demo": {}}) - assert await async_setup_component(hass, "homekit", {"homekit": {}}) + with patch("homeassistant.components.homekit.HomeKit") as mock_homekit: + mock_homekit.return_value = homekit = Mock() + type(homekit).async_start = AsyncMock() + assert await async_setup_component(hass, "homekit", {"homekit": {}}) + assert await async_setup_component(hass, "homeassistant", {}) + assert await async_setup_component(hass, "demo", {"demo": {}}) + assert await async_setup_component(hass, "homekit", {"homekit": {}}) - hass.states.async_set("climate.old", "off") - await hass.async_block_till_done() + hass.states.async_set("climate.old", "off") + await hass.async_block_till_done() - result = await hass.config_entries.options.async_init( - config_entry.entry_id, context={"show_advanced_options": True} - ) - - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "init" - - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - "domains": ["fan", "vacuum", "climate"], - "include_exclude_mode": "exclude", - }, - ) - - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "exclude" - - entry = entity_registry.async_get("light.ceiling_lights") - assert entry is not None - device_id = entry.device_id - - result2 = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - "entities": ["climate.old"], - }, - ) - - with patch("homeassistant.components.homekit.async_setup_entry", return_value=True): - result3 = await hass.config_entries.options.async_configure( - result2["flow_id"], - user_input={"devices": [device_id]}, + result = await hass.config_entries.options.async_init( + config_entry.entry_id, context={"show_advanced_options": True} ) - assert result3["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY - assert config_entry.options == { - "devices": [device_id], - "mode": "bridge", - "filter": { - "exclude_domains": [], - "exclude_entities": ["climate.old"], - "include_domains": ["fan", "vacuum", "climate"], - "include_entities": [], - }, - } + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "init" - await hass.async_block_till_done() - await hass.config_entries.async_unload(config_entry.entry_id) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + "domains": ["fan", "vacuum", "climate"], + "include_exclude_mode": "exclude", + }, + ) + + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "exclude" + + entry = entity_registry.async_get("light.ceiling_lights") + assert entry is not None + device_id = entry.device_id + + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + "entities": ["climate.old"], + }, + ) + + with patch( + "homeassistant.components.homekit.async_setup_entry", return_value=True + ): + result3 = await hass.config_entries.options.async_configure( + result2["flow_id"], + user_input={"devices": [device_id]}, + ) + + assert result3["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert config_entry.options == { + "devices": [device_id], + "mode": "bridge", + "filter": { + "exclude_domains": [], + "exclude_entities": ["climate.old"], + "include_domains": ["fan", "vacuum", "climate"], + "include_entities": [], + }, + } + + await hass.async_block_till_done() + await hass.config_entries.async_unload(config_entry.entry_id) @patch(f"{PATH_HOMEKIT}.async_port_is_available", return_value=True) @@ -514,49 +520,53 @@ async def test_options_flow_devices_preserved_when_advanced_off( demo_config_entry = MockConfigEntry(domain="domain") demo_config_entry.add_to_hass(hass) - assert await async_setup_component(hass, "homekit", {"homekit": {}}) + with patch("homeassistant.components.homekit.HomeKit") as mock_homekit: + mock_homekit.return_value = homekit = Mock() + type(homekit).async_start = AsyncMock() + assert await async_setup_component(hass, "homekit", {"homekit": {}}) - hass.states.async_set("climate.old", "off") - await hass.async_block_till_done() + hass.states.async_set("climate.old", "off") + await hass.async_block_till_done() - result = await hass.config_entries.options.async_init( - config_entry.entry_id, context={"show_advanced_options": False} - ) + result = await hass.config_entries.options.async_init( + config_entry.entry_id, context={"show_advanced_options": False} + ) - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "init" + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "init" - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - "domains": ["fan", "vacuum", "climate"], - "include_exclude_mode": "exclude", - }, - ) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + "domains": ["fan", "vacuum", "climate"], + "include_exclude_mode": "exclude", + }, + ) - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "exclude" + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "exclude" - result2 = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - "entities": ["climate.old"], - }, - ) + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + "entities": ["climate.old"], + }, + ) - assert result2["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY - assert config_entry.options == { - "devices": ["1fabcabcabcabcabcabcabcabcabc"], - "mode": "bridge", - "filter": { - "exclude_domains": [], - "exclude_entities": ["climate.old"], - "include_domains": ["fan", "vacuum", "climate"], - "include_entities": [], - }, - } - await hass.async_block_till_done() - await hass.config_entries.async_unload(config_entry.entry_id) + assert result2["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert config_entry.options == { + "devices": ["1fabcabcabcabcabcabcabcabcabc"], + "mode": "bridge", + "filter": { + "exclude_domains": [], + "exclude_entities": ["climate.old"], + "include_domains": ["fan", "vacuum", "climate"], + "include_entities": [], + }, + } + await hass.async_block_till_done() + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() async def test_options_flow_include_mode_with_non_existant_entity( diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index da5f8e4f5e5..c1dcd28ce68 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -347,7 +347,7 @@ async def test_homekit_with_single_advertise_ips( ) entry.add_to_hass(hass) with patch(f"{PATH_HOMEKIT}.HomeDriver", return_value=hk_driver) as mock_driver: - mock_driver.async_start = AsyncMock() + hk_driver.async_start = AsyncMock() await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -386,7 +386,7 @@ async def test_homekit_with_many_advertise_ips( ) entry.add_to_hass(hass) with patch(f"{PATH_HOMEKIT}.HomeDriver", return_value=hk_driver) as mock_driver: - mock_driver.async_start = AsyncMock() + hk_driver.async_start = AsyncMock() await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -2043,6 +2043,7 @@ async def test_reload(hass: HomeAssistant, mock_async_zeroconf: None) -> None: "homeassistant.components.network.async_get_source_ip", return_value="1.2.3.4" ): mock_homekit.return_value = homekit = Mock() + type(homekit).async_start = AsyncMock() assert await async_setup_component( hass, "homekit", {"homekit": {CONF_NAME: "reloadable", CONF_PORT: 12345}} ) @@ -2065,16 +2066,15 @@ async def test_reload(hass: HomeAssistant, mock_async_zeroconf: None) -> None: yaml_path = get_fixture_path("configuration.yaml", "homekit") with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path), patch( f"{PATH_HOMEKIT}.HomeKit" - ) as mock_homekit2, patch.object(homekit.bridge, "add_accessory"), patch( - f"{PATH_HOMEKIT}.async_show_setup_message" - ), patch( + ) as mock_homekit2, patch(f"{PATH_HOMEKIT}.async_show_setup_message"), patch( f"{PATH_HOMEKIT}.get_accessory", - ), patch( + ), patch(f"{PATH_HOMEKIT}.async_port_is_available", return_value=True), patch( "pyhap.accessory_driver.AccessoryDriver.async_start", ), patch( "homeassistant.components.network.async_get_source_ip", return_value="1.2.3.4" ): mock_homekit2.return_value = homekit = Mock() + type(homekit).async_start = AsyncMock() await hass.services.async_call( "homekit", SERVICE_RELOAD, diff --git a/tests/components/homekit/test_init.py b/tests/components/homekit/test_init.py index 068f13c0e54..b56d01bd871 100644 --- a/tests/components/homekit/test_init.py +++ b/tests/components/homekit/test_init.py @@ -1,6 +1,6 @@ """Test HomeKit initialization.""" -from unittest.mock import patch +from unittest.mock import AsyncMock, Mock, patch import pytest @@ -31,7 +31,9 @@ async def test_humanify_homekit_changed_event( ) -> None: """Test humanifying HomeKit changed event.""" hass.config.components.add("recorder") - with patch("homeassistant.components.homekit.HomeKit"): + with patch("homeassistant.components.homekit.HomeKit") as mock_homekit: + mock_homekit.return_value = homekit = Mock() + type(homekit).async_start = AsyncMock() assert await async_setup_component(hass, "homekit", {"homekit": {}}) assert await async_setup_component(hass, "logbook", {}) await hass.async_block_till_done()