Adjust entity disabled_by flag when moving entity to another config entry (#151151)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Erik Montnemery
2025-08-25 23:35:28 +02:00
committed by GitHub
parent b67d34d428
commit 1d2599184b
2 changed files with 265 additions and 0 deletions

View File

@@ -1280,6 +1280,20 @@ class EntityRegistry(BaseRegistry):
unique_id=new_unique_id, unique_id=new_unique_id,
) )
if disabled_by is UNDEFINED and config_entry_id is not UNDEFINED:
if config_entry_id:
config_entry = self.hass.config_entries.async_get_entry(config_entry_id)
if TYPE_CHECKING:
# We've checked the config_entry exists in _validate_item
assert config_entry is not None
if config_entry.disabled_by:
if old.disabled_by is None:
new_values["disabled_by"] = RegistryEntryDisabler.CONFIG_ENTRY
elif old.disabled_by == RegistryEntryDisabler.CONFIG_ENTRY:
new_values["disabled_by"] = None
elif old.disabled_by == RegistryEntryDisabler.CONFIG_ENTRY:
new_values["disabled_by"] = None
if new_entity_id is not UNDEFINED and new_entity_id != old.entity_id: if new_entity_id is not UNDEFINED and new_entity_id != old.entity_id:
if not self._entity_id_available(new_entity_id): if not self._entity_id_available(new_entity_id):
raise ValueError("Entity with this ID is already registered") raise ValueError("Entity with this ID is already registered")

View File

@@ -1365,6 +1365,257 @@ async def test_update_entity(
entry = updated_entry entry = updated_entry
@pytest.mark.parametrize(
(
"new_config_entry_disabled_by",
"entity_disabled_by_initial",
"entity_disabled_by_updated",
),
[
(
None,
None,
None,
),
# Config entry not disabled, entity was disabled by config entry.
# Entity not disabled when updated.
(
None,
er.RegistryEntryDisabler.CONFIG_ENTRY,
None,
),
(
None,
er.RegistryEntryDisabler.DEVICE,
er.RegistryEntryDisabler.DEVICE,
),
(
None,
er.RegistryEntryDisabler.HASS,
er.RegistryEntryDisabler.HASS,
),
(
None,
er.RegistryEntryDisabler.INTEGRATION,
er.RegistryEntryDisabler.INTEGRATION,
),
(
None,
er.RegistryEntryDisabler.USER,
er.RegistryEntryDisabler.USER,
),
# Config entry disabled, entity not disabled.
# Entity disabled by config entry when updated.
(
config_entries.ConfigEntryDisabler.USER,
None,
er.RegistryEntryDisabler.CONFIG_ENTRY,
),
(
config_entries.ConfigEntryDisabler.USER,
er.RegistryEntryDisabler.CONFIG_ENTRY,
er.RegistryEntryDisabler.CONFIG_ENTRY,
),
(
config_entries.ConfigEntryDisabler.USER,
er.RegistryEntryDisabler.DEVICE,
er.RegistryEntryDisabler.DEVICE,
),
(
config_entries.ConfigEntryDisabler.USER,
er.RegistryEntryDisabler.HASS,
er.RegistryEntryDisabler.HASS,
),
(
config_entries.ConfigEntryDisabler.USER,
er.RegistryEntryDisabler.INTEGRATION,
er.RegistryEntryDisabler.INTEGRATION,
),
(
config_entries.ConfigEntryDisabler.USER,
er.RegistryEntryDisabler.USER,
er.RegistryEntryDisabler.USER,
),
],
)
@pytest.mark.usefixtures("freezer")
async def test_update_entity_disabled_by(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
new_config_entry_disabled_by: config_entries.ConfigEntryDisabler | None,
entity_disabled_by_initial: er.RegistryEntryDisabler | None,
entity_disabled_by_updated: er.RegistryEntryDisabler | None,
) -> None:
"""Check how the disabled_by flag is treated when updating an entity."""
config_entry_1 = MockConfigEntry(domain="light")
config_entry_1.add_to_hass(hass)
config_entry_2 = MockConfigEntry(
domain="light", disabled_by=new_config_entry_disabled_by
)
config_entry_2.add_to_hass(hass)
device_entry = device_registry.async_get_or_create(
config_entry_id=config_entry_1.entry_id,
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entry = entity_registry.async_get_or_create(
"light",
"hue",
"1234",
capabilities={"key1": "value1"},
config_entry=config_entry_1,
config_subentry_id=None,
device_id=device_entry.id,
disabled_by=entity_disabled_by_initial,
entity_category=EntityCategory.DIAGNOSTIC,
get_initial_options=lambda: {"test_domain": {"key1": "value1"}},
has_entity_name=True,
hidden_by=er.RegistryEntryHider.INTEGRATION,
original_device_class="device_class_1",
original_icon="original_icon_1",
original_name="original_name_1",
suggested_object_id="hue_5678",
supported_features=1,
translation_key="translation_key_1",
unit_of_measurement="unit_1",
)
# Update entity
entry_updated = entity_registry.async_update_entity(
entry.entity_id,
capabilities={"key2": "value2"},
config_entry_id=config_entry_2.entry_id,
)
assert entry != entry_updated
assert entry_updated == er.RegistryEntry(
entity_id="light.hue_5678",
unique_id="1234",
platform="hue",
aliases=set(),
area_id=None,
categories={},
capabilities={"key2": "value2"},
config_entry_id=config_entry_2.entry_id,
config_subentry_id=None,
created_at=utcnow(),
device_class=None,
device_id=device_entry.id,
disabled_by=entity_disabled_by_updated,
entity_category=EntityCategory.DIAGNOSTIC,
has_entity_name=True,
hidden_by=er.RegistryEntryHider.INTEGRATION,
icon=None,
id=entry.id,
labels=set(),
modified_at=utcnow(),
name=None,
options={"test_domain": {"key1": "value1"}},
original_device_class="device_class_1",
original_icon="original_icon_1",
original_name="original_name_1",
suggested_object_id="hue_5678",
supported_features=1,
translation_key="translation_key_1",
unit_of_measurement="unit_1",
)
@pytest.mark.parametrize(
("entity_disabled_by_initial", "entity_disabled_by_updated"),
[
(None, None),
# Entity was disabled by config entry, entity not disabled when updated.
(er.RegistryEntryDisabler.CONFIG_ENTRY, None),
(er.RegistryEntryDisabler.DEVICE, er.RegistryEntryDisabler.DEVICE),
(er.RegistryEntryDisabler.HASS, er.RegistryEntryDisabler.HASS),
(er.RegistryEntryDisabler.INTEGRATION, er.RegistryEntryDisabler.INTEGRATION),
(er.RegistryEntryDisabler.USER, er.RegistryEntryDisabler.USER),
],
)
@pytest.mark.usefixtures("freezer")
async def test_update_entity_disabled_by_2(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
entity_disabled_by_initial: er.RegistryEntryDisabler | None,
entity_disabled_by_updated: er.RegistryEntryDisabler | None,
) -> None:
"""Check how the disabled_by flag is treated when updating an entity.
In this test, the entity is updated without a config entry.
"""
config_entry = MockConfigEntry(domain="light")
config_entry.add_to_hass(hass)
device_entry = device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entry = entity_registry.async_get_or_create(
"light",
"hue",
"1234",
capabilities={"key1": "value1"},
config_entry=config_entry,
config_subentry_id=None,
device_id=device_entry.id,
disabled_by=entity_disabled_by_initial,
entity_category=EntityCategory.DIAGNOSTIC,
get_initial_options=lambda: {"test_domain": {"key1": "value1"}},
has_entity_name=True,
hidden_by=er.RegistryEntryHider.INTEGRATION,
original_device_class="device_class_1",
original_icon="original_icon_1",
original_name="original_name_1",
suggested_object_id="hue_5678",
supported_features=1,
translation_key="translation_key_1",
unit_of_measurement="unit_1",
)
# Update entity
entry_updated = entity_registry.async_update_entity(
entry.entity_id,
capabilities={"key2": "value2"},
config_entry_id=None,
)
assert entry != entry_updated
# entity_id and user customizations are restored. new integration options are
# respected.
assert entry_updated == er.RegistryEntry(
entity_id="light.hue_5678",
unique_id="1234",
platform="hue",
aliases=set(),
area_id=None,
categories={},
capabilities={"key2": "value2"},
config_entry_id=None,
config_subentry_id=None,
created_at=utcnow(),
device_class=None,
device_id=device_entry.id,
disabled_by=entity_disabled_by_updated,
entity_category=EntityCategory.DIAGNOSTIC,
has_entity_name=True,
hidden_by=er.RegistryEntryHider.INTEGRATION,
icon=None,
id=entry.id,
labels=set(),
modified_at=utcnow(),
name=None,
options={"test_domain": {"key1": "value1"}},
original_device_class="device_class_1",
original_icon="original_icon_1",
original_name="original_name_1",
suggested_object_id="hue_5678",
supported_features=1,
translation_key="translation_key_1",
unit_of_measurement="unit_1",
)
async def test_update_entity_options( async def test_update_entity_options(
hass: HomeAssistant, entity_registry: er.EntityRegistry hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None: ) -> None: