Allow linking template helpers to child devices (#179590)

This commit is contained in:
Erik Montnemery
2026-08-19 22:56:19 +02:00
committed by GitHub
parent a7b855ed07
commit fc946f747e
4 changed files with 106 additions and 8 deletions
+6 -5
View File
@@ -88,12 +88,13 @@ class AbstractTemplateEntity(Entity):
)
device_registry = dr.async_get(hass)
# Allow linking to a main or child device, but not to a composite device.
if (
device_id := config.get(CONF_DEVICE_ID)
) is not None and device_registry.async_is_composite_device_id(
device_id
) is False:
self.device_entry = device_registry.async_get(device_id)
(device_id := config.get(CONF_DEVICE_ID)) is not None
and (device_entry := device_registry.async_get(device_id)) is not None
and not device_registry.async_is_composite_device_id(device_id)
):
self.device_entry = device_entry
@property
@abstractmethod
+3 -3
View File
@@ -41,9 +41,9 @@ class CompositeDeviceIdRepairFlow(RepairsFlow):
errors: dict[str, str] = {}
if user_input is not None:
device_id = user_input.get(CONF_DEVICE_ID)
if (
device_id is None
or device_registry.async_is_composite_device_id(device_id) is False
if device_id is None or (
device_registry.async_get(device_id) is not None
and not device_registry.async_is_composite_device_id(device_id)
):
options = {**entry.options}
if device_id:
+50
View File
@@ -492,6 +492,56 @@ async def test_change_device(
)
@pytest.mark.parametrize(
"linked_device",
[
pytest.param("main", id="main_device"),
pytest.param("child", id="child_device"),
],
)
async def test_link_to_main_or_child_device(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
linked_device: str,
) -> None:
"""Test a template entity links to a selected main or child device."""
source_entry = MockConfigEntry()
source_entry.add_to_hass(hass)
main_device = device_registry.async_get_or_create(
config_entry_id=source_entry.entry_id,
identifiers={("test", "main")},
)
child_device = device_registry.async_get_or_create_child(
config_entry_id=source_entry.entry_id,
identifiers={("test", "child")},
parent_device_id=main_device.id,
)
selected_device_id = {"main": main_device, "child": child_device}[linked_device].id
template_config_entry = MockConfigEntry(
domain=DOMAIN,
options={
"name": "My template",
"state": "{{10}}",
"template_type": "sensor",
"device_id": selected_device_id,
},
title="Template",
)
template_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(template_config_entry.entry_id)
await hass.async_block_till_done()
template_entities = list(
entity_registry.entities.get_entries_for_config_entry_id(
template_config_entry.entry_id
)
)
assert len(template_entities) == 1
assert template_entities[0].device_id == selected_device_id
async def test_setup_removes_stale_helper_device(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
+47
View File
@@ -176,6 +176,53 @@ async def test_composite_device_id_repair_flow(
assert entity_entry.device_id == picked_device_id
@pytest.mark.usefixtures("split_devices")
async def test_composite_device_id_repair_flow_links_child_device(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
issue_registry: ir.IssueRegistry,
) -> None:
"""Test the repair flow accepts a child device and links the entity to it."""
source_entry = MockConfigEntry(domain="itg3")
source_entry.add_to_hass(hass)
parent_device = device_registry.async_get_or_create(
config_entry_id=source_entry.entry_id,
identifiers={("itg3", "parent")},
)
child_device = device_registry.async_get_or_create_child(
config_entry_id=source_entry.entry_id,
identifiers={("itg3", "child")},
parent_device_id=parent_device.id,
)
entry = await _setup_template_entry(hass, COMPOSITE_ID)
issue_id = f"composite_device_id_{entry.entry_id}"
assert issue_registry.async_get_issue(DOMAIN, issue_id)
assert await async_setup_component(hass, "repairs", {})
await hass.async_block_till_done()
client = await hass_client()
result = await start_repair_fix_flow(client, DOMAIN, issue_id)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "select_device"
result = await process_repair_fix_flow(
client, result["flow_id"], json={CONF_DEVICE_ID: child_device.id}
)
assert result["type"] == FlowResultType.CREATE_ENTRY
await hass.async_block_till_done()
assert entry.options[CONF_DEVICE_ID] == child_device.id
assert not issue_registry.async_get_issue(DOMAIN, issue_id)
entity_entry = entity_registry.async_get(TEMPLATE_ENTITY_ID)
assert entity_entry is not None
assert entity_entry.device_id == child_device.id
async def test_composite_device_id_repair_flow_ambiguity_not_resolved(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,