Improve robustness of linking homekit yaml to config entries (#79386)

pull/79417/head
J. Nick Koston 2022-10-01 04:44:45 -10:00 committed by GitHub
parent 8ff12eacd4
commit ec8901b9af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 120 additions and 29 deletions

View File

@ -193,14 +193,21 @@ def _async_all_homekit_instances(hass: HomeAssistant) -> list[HomeKit]:
]
def _async_get_entries_by_name(
def _async_get_imported_entries_indices(
current_entries: list[ConfigEntry],
) -> dict[str, ConfigEntry]:
"""Return a dict of the entries by name."""
) -> tuple[dict[str, ConfigEntry], dict[int, ConfigEntry]]:
"""Return a dicts of the entries by name and port."""
# For backwards compat, its possible the first bridge is using the default
# name.
return {entry.data.get(CONF_NAME, BRIDGE_NAME): entry for entry in current_entries}
entries_by_name: dict[str, ConfigEntry] = {}
entries_by_port: dict[int, ConfigEntry] = {}
for entry in current_entries:
if entry.source != SOURCE_IMPORT:
continue
entries_by_name[entry.data.get(CONF_NAME, BRIDGE_NAME)] = entry
entries_by_port[entry.data.get(CONF_PORT, DEFAULT_PORT)] = entry
return entries_by_name, entries_by_port
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
@ -218,10 +225,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
current_entries = hass.config_entries.async_entries(DOMAIN)
entries_by_name = _async_get_entries_by_name(current_entries)
entries_by_name, entries_by_port = _async_get_imported_entries_indices(
current_entries
)
for index, conf in enumerate(config[DOMAIN]):
if _async_update_config_entry_if_from_yaml(hass, entries_by_name, conf):
if _async_update_config_entry_from_yaml(
hass, entries_by_name, entries_by_port, conf
):
continue
conf[CONF_ENTRY_INDEX] = index
@ -237,8 +248,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
@callback
def _async_update_config_entry_if_from_yaml(
hass: HomeAssistant, entries_by_name: dict[str, ConfigEntry], conf: ConfigType
def _async_update_config_entry_from_yaml(
hass: HomeAssistant,
entries_by_name: dict[str, ConfigEntry],
entries_by_port: dict[int, ConfigEntry],
conf: ConfigType,
) -> bool:
"""Update a config entry with the latest yaml.
@ -246,27 +260,24 @@ def _async_update_config_entry_if_from_yaml(
Returns False if there is no matching config entry
"""
bridge_name = conf[CONF_NAME]
if (
bridge_name in entries_by_name
and entries_by_name[bridge_name].source == SOURCE_IMPORT
if not (
matching_entry := entries_by_name.get(conf.get(CONF_NAME, BRIDGE_NAME))
or entries_by_port.get(conf.get(CONF_PORT, DEFAULT_PORT))
):
entry = entries_by_name[bridge_name]
# If they alter the yaml config we import the changes
# since there currently is no practical way to support
# all the options in the UI at this time.
data = conf.copy()
options = {}
for key in CONFIG_OPTIONS:
if key in data:
options[key] = data[key]
del data[key]
return False
hass.config_entries.async_update_entry(entry, data=data, options=options)
return True
# If they alter the yaml config we import the changes
# since there currently is no practical way to support
# all the options in the UI at this time.
data = conf.copy()
options = {}
for key in CONFIG_OPTIONS:
if key in data:
options[key] = data[key]
del data[key]
return False
hass.config_entries.async_update_entry(matching_entry, data=data, options=options)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@ -451,10 +462,14 @@ def _async_register_events_and_services(hass: HomeAssistant) -> None:
return
current_entries = hass.config_entries.async_entries(DOMAIN)
entries_by_name = _async_get_entries_by_name(current_entries)
entries_by_name, entries_by_port = _async_get_imported_entries_indices(
current_entries
)
for conf in config[DOMAIN]:
_async_update_config_entry_if_from_yaml(hass, entries_by_name, conf)
_async_update_config_entry_from_yaml(
hass, entries_by_name, entries_by_port, conf
)
reload_tasks = [
hass.config_entries.async_reload(entry.entry_id)

View File

@ -35,7 +35,7 @@ from homeassistant.components.homekit.const import (
from homeassistant.components.homekit.type_triggers import DeviceTriggerAccessory
from homeassistant.components.homekit.util import get_persist_fullpath_for_entry_id
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_ZEROCONF
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_DEVICE_ID,
@ -1394,6 +1394,82 @@ async def test_yaml_updates_update_config_entry_for_name(hass, mock_async_zeroco
mock_homekit().async_start.assert_called()
async def test_yaml_can_link_with_default_name(hass, mock_async_zeroconf):
"""Test async_setup with imported config linked by default name."""
entry = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_IMPORT,
data={},
options={},
)
entry.add_to_hass(hass)
with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, patch(
"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": {"entity_config": {"camera.back_camera": {"stream_count": 3}}}},
)
await hass.async_block_till_done()
mock_homekit.reset_mock()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert entry.options["entity_config"]["camera.back_camera"]["stream_count"] == 3
async def test_yaml_can_link_with_port(hass, mock_async_zeroconf):
"""Test async_setup with imported config linked by port."""
entry = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_IMPORT,
data={"name": "random", "port": 12345},
options={},
)
entry.add_to_hass(hass)
entry2 = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_IMPORT,
data={"name": "random", "port": 12346},
options={},
)
entry2.add_to_hass(hass)
entry3 = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_ZEROCONF,
data={"name": "random", "port": 12347},
options={},
)
entry3.add_to_hass(hass)
with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, patch(
"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": {
"port": 12345,
"entity_config": {"camera.back_camera": {"stream_count": 3}},
}
},
)
await hass.async_block_till_done()
mock_homekit.reset_mock()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert entry.options["entity_config"]["camera.back_camera"]["stream_count"] == 3
assert entry2.options == {}
assert entry3.options == {}
async def test_homekit_uses_system_zeroconf(hass, hk_driver, mock_async_zeroconf):
"""Test HomeKit uses system zeroconf."""
entry = MockConfigEntry(