Adjust device disabled_by flag when changing config entry (#151155)

This commit is contained in:
Erik Montnemery
2025-08-26 12:37:56 +02:00
committed by GitHub
parent dfbe42fb21
commit 5bb96f7f06
10 changed files with 390 additions and 56 deletions

View File

@@ -129,9 +129,9 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
entity_disabled_by is er.RegistryEntryDisabler.CONFIG_ENTRY
and not all_disabled
):
# Device and entity registries don't update the disabled_by flag
# when moving a device or entity from one config entry to another,
# so we need to do it manually.
# Device and entity registries will set the disabled_by flag to None
# when moving a device or entity disabled by CONFIG_ENTRY to an enabled
# config entry, but we want to set it to DEVICE or USER instead,
entity_disabled_by = (
er.RegistryEntryDisabler.DEVICE
if device
@@ -146,9 +146,9 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
)
if device is not None:
# Device and entity registries don't update the disabled_by flag when
# moving a device or entity from one config entry to another, so we
# need to do it manually.
# Device and entity registries will set the disabled_by flag to None
# when moving a device or entity disabled by CONFIG_ENTRY to an enabled
# config entry, but we want to set it to USER instead,
device_disabled_by = device.disabled_by
if (
device.disabled_by is dr.DeviceEntryDisabler.CONFIG_ENTRY

View File

@@ -260,9 +260,9 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
entity_disabled_by is er.RegistryEntryDisabler.CONFIG_ENTRY
and not all_disabled
):
# Device and entity registries don't update the disabled_by flag
# when moving a device or entity from one config entry to another,
# so we need to do it manually.
# Device and entity registries will set the disabled_by flag to None
# when moving a device or entity disabled by CONFIG_ENTRY to an enabled
# config entry, but we want to set it to DEVICE or USER instead,
entity_disabled_by = (
er.RegistryEntryDisabler.DEVICE
if device
@@ -277,9 +277,9 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
)
if device is not None:
# Device and entity registries don't update the disabled_by flag when
# moving a device or entity from one config entry to another, so we
# need to do it manually.
# Device and entity registries will set the disabled_by flag to None
# when moving a device or entity disabled by CONFIG_ENTRY to an enabled
# config entry, but we want to set it to USER instead,
device_disabled_by = device.disabled_by
if (
device.disabled_by is dr.DeviceEntryDisabler.CONFIG_ENTRY

View File

@@ -145,9 +145,9 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
entity_disabled_by is er.RegistryEntryDisabler.CONFIG_ENTRY
and not all_disabled
):
# Device and entity registries don't update the disabled_by flag
# when moving a device or entity from one config entry to another,
# so we need to do it manually.
# Device and entity registries will set the disabled_by flag to None
# when moving a device or entity disabled by CONFIG_ENTRY to an enabled
# config entry, but we want to set it to DEVICE or USER instead,
entity_disabled_by = (
er.RegistryEntryDisabler.DEVICE
if device
@@ -162,9 +162,9 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
)
if device is not None:
# Device and entity registries don't update the disabled_by flag when
# moving a device or entity from one config entry to another, so we
# need to do it manually.
# Device and entity registries will set the disabled_by flag to None
# when moving a device or entity disabled by CONFIG_ENTRY to an enabled
# config entry, but we want to set it to USER instead,
device_disabled_by = device.disabled_by
if (
device.disabled_by is dr.DeviceEntryDisabler.CONFIG_ENTRY

View File

@@ -320,9 +320,9 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
entity_disabled_by is er.RegistryEntryDisabler.CONFIG_ENTRY
and not all_disabled
):
# Device and entity registries don't update the disabled_by flag
# when moving a device or entity from one config entry to another,
# so we need to do it manually.
# Device and entity registries will set the disabled_by flag to None
# when moving a device or entity disabled by CONFIG_ENTRY to an enabled
# config entry, but we want to set it to DEVICE or USER instead,
entity_disabled_by = (
er.RegistryEntryDisabler.DEVICE
if device
@@ -337,9 +337,9 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
)
if device is not None:
# Device and entity registries don't update the disabled_by flag when
# moving a device or entity from one config entry to another, so we
# need to do it manually.
# Device and entity registries will set the disabled_by flag to None
# when moving a device or entity disabled by CONFIG_ENTRY to an enabled
# config entry, but we want to set it to USER instead,
device_disabled_by = device.disabled_by
if (
device.disabled_by is dr.DeviceEntryDisabler.CONFIG_ENTRY

View File

@@ -1115,6 +1115,16 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
config_entries_subentries = old.config_entries_subentries | {
add_config_entry_id: {add_config_subentry_id}
}
# Enable the device if it was disabled by config entry and we're adding
# a non disabled config entry
if (
# mypy says add_config_entry can be None. That's impossible, because we
# raise above if that happens
not add_config_entry.disabled_by # type: ignore[union-attr]
and old.disabled_by is DeviceEntryDisabler.CONFIG_ENTRY
):
new_values["disabled_by"] = None
old_values["disabled_by"] = old.disabled_by
elif (
add_config_subentry_id
not in old.config_entries_subentries[add_config_entry_id]
@@ -1157,6 +1167,22 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
config_entries = config_entries - {remove_config_entry_id}
# Disable the device if it is enabled and all remaining config entries
# are disabled
has_enabled_config_entries = any(
config_entry.disabled_by is None
for config_entry_id in config_entries
if (
config_entry := self.hass.config_entries.async_get_entry(
config_entry_id
)
)
is not None
)
if not has_enabled_config_entries and old.disabled_by is None:
new_values["disabled_by"] = DeviceEntryDisabler.CONFIG_ENTRY
old_values["disabled_by"] = old.disabled_by
if config_entries != old.config_entries:
new_values["config_entries"] = config_entries
old_values["config_entries"] = old.config_entries

View File

@@ -156,6 +156,8 @@ async def test_migration_from_v1_to_v2(
@pytest.mark.parametrize(
(
"config_entry_disabled_by",
"device_disabled_by",
"entity_disabled_by",
"merged_config_entry_disabled_by",
"conversation_subentry_data",
"main_config_entry",
@@ -163,6 +165,8 @@ async def test_migration_from_v1_to_v2(
[
(
[ConfigEntryDisabler.USER, None],
[DeviceEntryDisabler.CONFIG_ENTRY, None],
[RegistryEntryDisabler.CONFIG_ENTRY, None],
None,
[
{
@@ -182,18 +186,20 @@ async def test_migration_from_v1_to_v2(
),
(
[None, ConfigEntryDisabler.USER],
[None, DeviceEntryDisabler.CONFIG_ENTRY],
[None, RegistryEntryDisabler.CONFIG_ENTRY],
None,
[
{
"conversation_entity_id": "conversation.claude",
"device_disabled_by": DeviceEntryDisabler.USER,
"entity_disabled_by": RegistryEntryDisabler.DEVICE,
"device_disabled_by": None,
"entity_disabled_by": None,
"device": 0,
},
{
"conversation_entity_id": "conversation.claude_2",
"device_disabled_by": None,
"entity_disabled_by": None,
"device_disabled_by": DeviceEntryDisabler.USER,
"entity_disabled_by": RegistryEntryDisabler.DEVICE,
"device": 1,
},
],
@@ -201,6 +207,8 @@ async def test_migration_from_v1_to_v2(
),
(
[ConfigEntryDisabler.USER, ConfigEntryDisabler.USER],
[DeviceEntryDisabler.CONFIG_ENTRY, DeviceEntryDisabler.CONFIG_ENTRY],
[RegistryEntryDisabler.CONFIG_ENTRY, RegistryEntryDisabler.CONFIG_ENTRY],
ConfigEntryDisabler.USER,
[
{
@@ -211,8 +219,8 @@ async def test_migration_from_v1_to_v2(
},
{
"conversation_entity_id": "conversation.claude_2",
"device_disabled_by": None,
"entity_disabled_by": None,
"device_disabled_by": DeviceEntryDisabler.CONFIG_ENTRY,
"entity_disabled_by": RegistryEntryDisabler.CONFIG_ENTRY,
"device": 1,
},
],
@@ -225,6 +233,8 @@ async def test_migration_from_v1_disabled(
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
config_entry_disabled_by: list[ConfigEntryDisabler | None],
device_disabled_by: list[DeviceEntryDisabler | None],
entity_disabled_by: list[RegistryEntryDisabler | None],
merged_config_entry_disabled_by: ConfigEntryDisabler | None,
conversation_subentry_data: list[dict[str, Any]],
main_config_entry: int,
@@ -264,7 +274,7 @@ async def test_migration_from_v1_disabled(
manufacturer="Anthropic",
model="Claude",
entry_type=dr.DeviceEntryType.SERVICE,
disabled_by=DeviceEntryDisabler.CONFIG_ENTRY,
disabled_by=device_disabled_by[0],
)
entity_registry.async_get_or_create(
"conversation",
@@ -273,7 +283,7 @@ async def test_migration_from_v1_disabled(
config_entry=mock_config_entry,
device_id=device_1.id,
suggested_object_id="claude",
disabled_by=RegistryEntryDisabler.CONFIG_ENTRY,
disabled_by=entity_disabled_by[0],
)
device_2 = device_registry.async_get_or_create(
@@ -283,6 +293,7 @@ async def test_migration_from_v1_disabled(
manufacturer="Anthropic",
model="Claude",
entry_type=dr.DeviceEntryType.SERVICE,
disabled_by=device_disabled_by[1],
)
entity_registry.async_get_or_create(
"conversation",
@@ -291,6 +302,7 @@ async def test_migration_from_v1_disabled(
config_entry=mock_config_entry_2,
device_id=device_2.id,
suggested_object_id="claude",
disabled_by=entity_disabled_by[1],
)
devices = [device_1, device_2]

View File

@@ -576,6 +576,8 @@ async def test_migration_from_v1(
@pytest.mark.parametrize(
(
"config_entry_disabled_by",
"device_disabled_by",
"entity_disabled_by",
"merged_config_entry_disabled_by",
"conversation_subentry_data",
"main_config_entry",
@@ -583,6 +585,8 @@ async def test_migration_from_v1(
[
(
[ConfigEntryDisabler.USER, None],
[DeviceEntryDisabler.CONFIG_ENTRY, None],
[RegistryEntryDisabler.CONFIG_ENTRY, None],
None,
[
{
@@ -602,18 +606,20 @@ async def test_migration_from_v1(
),
(
[None, ConfigEntryDisabler.USER],
[None, DeviceEntryDisabler.CONFIG_ENTRY],
[None, RegistryEntryDisabler.CONFIG_ENTRY],
None,
[
{
"conversation_entity_id": "conversation.google_generative_ai_conversation",
"device_disabled_by": DeviceEntryDisabler.USER,
"entity_disabled_by": RegistryEntryDisabler.DEVICE,
"device_disabled_by": None,
"entity_disabled_by": None,
"device": 0,
},
{
"conversation_entity_id": "conversation.google_generative_ai_conversation_2",
"device_disabled_by": None,
"entity_disabled_by": None,
"device_disabled_by": DeviceEntryDisabler.USER,
"entity_disabled_by": RegistryEntryDisabler.DEVICE,
"device": 1,
},
],
@@ -621,6 +627,8 @@ async def test_migration_from_v1(
),
(
[ConfigEntryDisabler.USER, ConfigEntryDisabler.USER],
[DeviceEntryDisabler.CONFIG_ENTRY, DeviceEntryDisabler.CONFIG_ENTRY],
[RegistryEntryDisabler.CONFIG_ENTRY, RegistryEntryDisabler.CONFIG_ENTRY],
ConfigEntryDisabler.USER,
[
{
@@ -631,8 +639,8 @@ async def test_migration_from_v1(
},
{
"conversation_entity_id": "conversation.google_generative_ai_conversation_2",
"device_disabled_by": None,
"entity_disabled_by": None,
"device_disabled_by": DeviceEntryDisabler.CONFIG_ENTRY,
"entity_disabled_by": RegistryEntryDisabler.CONFIG_ENTRY,
"device": 1,
},
],
@@ -645,6 +653,8 @@ async def test_migration_from_v1_disabled(
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
config_entry_disabled_by: list[ConfigEntryDisabler | None],
device_disabled_by: list[DeviceEntryDisabler | None],
entity_disabled_by: list[RegistryEntryDisabler | None],
merged_config_entry_disabled_by: ConfigEntryDisabler | None,
conversation_subentry_data: list[dict[str, Any]],
main_config_entry: int,
@@ -684,7 +694,7 @@ async def test_migration_from_v1_disabled(
manufacturer="Google",
model="Generative AI",
entry_type=dr.DeviceEntryType.SERVICE,
disabled_by=DeviceEntryDisabler.CONFIG_ENTRY,
disabled_by=device_disabled_by[0],
)
entity_registry.async_get_or_create(
"conversation",
@@ -693,7 +703,7 @@ async def test_migration_from_v1_disabled(
config_entry=mock_config_entry,
device_id=device_1.id,
suggested_object_id="google_generative_ai_conversation",
disabled_by=RegistryEntryDisabler.CONFIG_ENTRY,
disabled_by=entity_disabled_by[0],
)
device_2 = device_registry.async_get_or_create(
@@ -703,6 +713,7 @@ async def test_migration_from_v1_disabled(
manufacturer="Google",
model="Generative AI",
entry_type=dr.DeviceEntryType.SERVICE,
disabled_by=device_disabled_by[1],
)
entity_registry.async_get_or_create(
"conversation",
@@ -711,6 +722,7 @@ async def test_migration_from_v1_disabled(
config_entry=mock_config_entry_2,
device_id=device_2.id,
suggested_object_id="google_generative_ai_conversation_2",
disabled_by=entity_disabled_by[1],
)
devices = [device_1, device_2]

View File

@@ -372,6 +372,8 @@ async def test_migration_from_v1_with_same_urls(
@pytest.mark.parametrize(
(
"config_entry_disabled_by",
"device_disabled_by",
"entity_disabled_by",
"merged_config_entry_disabled_by",
"conversation_subentry_data",
"main_config_entry",
@@ -379,6 +381,8 @@ async def test_migration_from_v1_with_same_urls(
[
(
[ConfigEntryDisabler.USER, None],
[DeviceEntryDisabler.CONFIG_ENTRY, None],
[RegistryEntryDisabler.CONFIG_ENTRY, None],
None,
[
{
@@ -398,18 +402,20 @@ async def test_migration_from_v1_with_same_urls(
),
(
[None, ConfigEntryDisabler.USER],
[None, DeviceEntryDisabler.CONFIG_ENTRY],
[None, RegistryEntryDisabler.CONFIG_ENTRY],
None,
[
{
"conversation_entity_id": "conversation.ollama",
"device_disabled_by": DeviceEntryDisabler.USER,
"entity_disabled_by": RegistryEntryDisabler.DEVICE,
"device_disabled_by": None,
"entity_disabled_by": None,
"device": 0,
},
{
"conversation_entity_id": "conversation.ollama_2",
"device_disabled_by": None,
"entity_disabled_by": None,
"device_disabled_by": DeviceEntryDisabler.USER,
"entity_disabled_by": RegistryEntryDisabler.DEVICE,
"device": 1,
},
],
@@ -417,6 +423,8 @@ async def test_migration_from_v1_with_same_urls(
),
(
[ConfigEntryDisabler.USER, ConfigEntryDisabler.USER],
[DeviceEntryDisabler.CONFIG_ENTRY, DeviceEntryDisabler.CONFIG_ENTRY],
[RegistryEntryDisabler.CONFIG_ENTRY, RegistryEntryDisabler.CONFIG_ENTRY],
ConfigEntryDisabler.USER,
[
{
@@ -427,8 +435,8 @@ async def test_migration_from_v1_with_same_urls(
},
{
"conversation_entity_id": "conversation.ollama_2",
"device_disabled_by": None,
"entity_disabled_by": None,
"device_disabled_by": DeviceEntryDisabler.CONFIG_ENTRY,
"entity_disabled_by": RegistryEntryDisabler.CONFIG_ENTRY,
"device": 1,
},
],
@@ -441,6 +449,8 @@ async def test_migration_from_v1_disabled(
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
config_entry_disabled_by: list[ConfigEntryDisabler | None],
device_disabled_by: list[DeviceEntryDisabler | None],
entity_disabled_by: list[RegistryEntryDisabler | None],
merged_config_entry_disabled_by: ConfigEntryDisabler | None,
conversation_subentry_data: list[dict[str, Any]],
main_config_entry: int,
@@ -474,7 +484,7 @@ async def test_migration_from_v1_disabled(
manufacturer="Ollama",
model="Ollama",
entry_type=dr.DeviceEntryType.SERVICE,
disabled_by=DeviceEntryDisabler.CONFIG_ENTRY,
disabled_by=device_disabled_by[0],
)
entity_registry.async_get_or_create(
"conversation",
@@ -483,7 +493,7 @@ async def test_migration_from_v1_disabled(
config_entry=mock_config_entry,
device_id=device_1.id,
suggested_object_id="ollama",
disabled_by=RegistryEntryDisabler.CONFIG_ENTRY,
disabled_by=entity_disabled_by[0],
)
device_2 = device_registry.async_get_or_create(
@@ -493,6 +503,7 @@ async def test_migration_from_v1_disabled(
manufacturer="Ollama",
model="Ollama",
entry_type=dr.DeviceEntryType.SERVICE,
disabled_by=device_disabled_by[1],
)
entity_registry.async_get_or_create(
"conversation",
@@ -501,6 +512,7 @@ async def test_migration_from_v1_disabled(
config_entry=mock_config_entry_2,
device_id=device_2.id,
suggested_object_id="ollama_2",
disabled_by=entity_disabled_by[1],
)
devices = [device_1, device_2]

View File

@@ -868,6 +868,8 @@ async def test_migration_from_v1_with_same_keys(
@pytest.mark.parametrize(
(
"config_entry_disabled_by",
"device_disabled_by",
"entity_disabled_by",
"merged_config_entry_disabled_by",
"conversation_subentry_data",
"main_config_entry",
@@ -875,6 +877,8 @@ async def test_migration_from_v1_with_same_keys(
[
(
[ConfigEntryDisabler.USER, None],
[DeviceEntryDisabler.CONFIG_ENTRY, None],
[RegistryEntryDisabler.CONFIG_ENTRY, None],
None,
[
{
@@ -894,18 +898,20 @@ async def test_migration_from_v1_with_same_keys(
),
(
[None, ConfigEntryDisabler.USER],
[None, DeviceEntryDisabler.CONFIG_ENTRY],
[None, RegistryEntryDisabler.CONFIG_ENTRY],
None,
[
{
"conversation_entity_id": "conversation.chatgpt",
"device_disabled_by": DeviceEntryDisabler.USER,
"entity_disabled_by": RegistryEntryDisabler.DEVICE,
"device_disabled_by": None,
"entity_disabled_by": None,
"device": 0,
},
{
"conversation_entity_id": "conversation.chatgpt_2",
"device_disabled_by": None,
"entity_disabled_by": None,
"device_disabled_by": DeviceEntryDisabler.USER,
"entity_disabled_by": RegistryEntryDisabler.DEVICE,
"device": 1,
},
],
@@ -913,6 +919,8 @@ async def test_migration_from_v1_with_same_keys(
),
(
[ConfigEntryDisabler.USER, ConfigEntryDisabler.USER],
[DeviceEntryDisabler.CONFIG_ENTRY, DeviceEntryDisabler.CONFIG_ENTRY],
[RegistryEntryDisabler.CONFIG_ENTRY, RegistryEntryDisabler.CONFIG_ENTRY],
ConfigEntryDisabler.USER,
[
{
@@ -923,8 +931,8 @@ async def test_migration_from_v1_with_same_keys(
},
{
"conversation_entity_id": "conversation.chatgpt_2",
"device_disabled_by": None,
"entity_disabled_by": None,
"device_disabled_by": DeviceEntryDisabler.CONFIG_ENTRY,
"entity_disabled_by": RegistryEntryDisabler.CONFIG_ENTRY,
"device": 1,
},
],
@@ -937,6 +945,8 @@ async def test_migration_from_v1_disabled(
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
config_entry_disabled_by: list[ConfigEntryDisabler | None],
device_disabled_by: list[DeviceEntryDisabler | None],
entity_disabled_by: list[RegistryEntryDisabler | None],
merged_config_entry_disabled_by: ConfigEntryDisabler | None,
conversation_subentry_data: list[dict[str, Any]],
main_config_entry: int,
@@ -976,7 +986,7 @@ async def test_migration_from_v1_disabled(
manufacturer="OpenAI",
model="ChatGPT",
entry_type=dr.DeviceEntryType.SERVICE,
disabled_by=DeviceEntryDisabler.CONFIG_ENTRY,
disabled_by=device_disabled_by[0],
)
entity_registry.async_get_or_create(
"conversation",
@@ -985,7 +995,7 @@ async def test_migration_from_v1_disabled(
config_entry=mock_config_entry,
device_id=device_1.id,
suggested_object_id="chatgpt",
disabled_by=RegistryEntryDisabler.CONFIG_ENTRY,
disabled_by=entity_disabled_by[0],
)
device_2 = device_registry.async_get_or_create(
@@ -995,6 +1005,7 @@ async def test_migration_from_v1_disabled(
manufacturer="OpenAI",
model="ChatGPT",
entry_type=dr.DeviceEntryType.SERVICE,
disabled_by=device_disabled_by[1],
)
entity_registry.async_get_or_create(
"conversation",
@@ -1003,6 +1014,7 @@ async def test_migration_from_v1_disabled(
config_entry=mock_config_entry_2,
device_id=device_2.id,
suggested_object_id="chatgpt_2",
disabled_by=entity_disabled_by[1],
)
devices = [device_1, device_2]

View File

@@ -3279,6 +3279,266 @@ async def test_update_suggested_area(
assert updated_entry.area_id == device_area_id
@pytest.mark.parametrize(
(
"new_config_entry_disabled_by",
"device_disabled_by_initial",
"device_disabled_by_updated",
"extra_changes",
),
[
(
None,
None,
None,
{},
),
# Config entry not disabled, device was disabled by config entry.
# Device not disabled when updated.
(
None,
dr.DeviceEntryDisabler.CONFIG_ENTRY,
None,
{"disabled_by": dr.DeviceEntryDisabler.CONFIG_ENTRY},
),
(
None,
dr.DeviceEntryDisabler.INTEGRATION,
dr.DeviceEntryDisabler.INTEGRATION,
{},
),
(
None,
dr.DeviceEntryDisabler.USER,
dr.DeviceEntryDisabler.USER,
{},
),
(
config_entries.ConfigEntryDisabler.USER,
None,
None,
{},
),
(
config_entries.ConfigEntryDisabler.USER,
dr.DeviceEntryDisabler.CONFIG_ENTRY,
dr.DeviceEntryDisabler.CONFIG_ENTRY,
{},
),
(
config_entries.ConfigEntryDisabler.USER,
dr.DeviceEntryDisabler.INTEGRATION,
dr.DeviceEntryDisabler.INTEGRATION,
{},
),
(
config_entries.ConfigEntryDisabler.USER,
dr.DeviceEntryDisabler.USER,
dr.DeviceEntryDisabler.USER,
{},
),
],
)
@pytest.mark.usefixtures("freezer")
async def test_update_add_config_entry_disabled_by(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
new_config_entry_disabled_by: config_entries.ConfigEntryDisabler | None,
device_disabled_by_initial: dr.DeviceEntryDisabler | None,
device_disabled_by_updated: dr.DeviceEntryDisabler | None,
extra_changes: dict[str, Any],
) -> None:
"""Check how the disabled_by flag is treated when adding a config entry."""
config_entry_1 = MockConfigEntry(title=None)
config_entry_1.add_to_hass(hass)
config_entry_2 = MockConfigEntry(
title=None, disabled_by=new_config_entry_disabled_by
)
config_entry_2.add_to_hass(hass)
update_events = async_capture_events(hass, dr.EVENT_DEVICE_REGISTRY_UPDATED)
entry = device_registry.async_get_or_create(
config_entry_id=config_entry_1.entry_id,
config_subentry_id=None,
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
disabled_by=device_disabled_by_initial,
)
assert entry.disabled_by == device_disabled_by_initial
entry2 = device_registry.async_update_device(
entry.id, add_config_entry_id=config_entry_2.entry_id
)
assert entry2 == dr.DeviceEntry(
config_entries={config_entry_1.entry_id, config_entry_2.entry_id},
config_entries_subentries={
config_entry_1.entry_id: {None},
config_entry_2.entry_id: {None},
},
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:ab:cd:ef")},
created_at=utcnow(),
disabled_by=device_disabled_by_updated,
id=entry.id,
modified_at=utcnow(),
primary_config_entry=None,
)
await hass.async_block_till_done()
assert len(update_events) == 2
assert update_events[0].data == {
"action": "create",
"device_id": entry.id,
}
assert update_events[1].data == {
"action": "update",
"device_id": entry.id,
"changes": {
"config_entries": {config_entry_1.entry_id},
"config_entries_subentries": {config_entry_1.entry_id: {None}},
}
| extra_changes,
}
@pytest.mark.parametrize(
(
"removed_config_entry_disabled_by",
"device_disabled_by_initial",
"device_disabled_by_updated",
"extra_changes",
),
[
# The non-disabled config entry is removed, device changed to
# disabled by config entry.
(
None,
None,
dr.DeviceEntryDisabler.CONFIG_ENTRY,
{"disabled_by": None},
),
(
None,
dr.DeviceEntryDisabler.CONFIG_ENTRY,
dr.DeviceEntryDisabler.CONFIG_ENTRY,
{},
),
(
None,
dr.DeviceEntryDisabler.INTEGRATION,
dr.DeviceEntryDisabler.INTEGRATION,
{},
),
(
None,
dr.DeviceEntryDisabler.USER,
dr.DeviceEntryDisabler.USER,
{},
),
# In this test, the device is in an invalid state: config entry disabled,
# device not disabled. After removing the config entry, the device is disabled
# by checking the remaining config entry.
(
config_entries.ConfigEntryDisabler.USER,
None,
dr.DeviceEntryDisabler.CONFIG_ENTRY,
{"disabled_by": None},
),
(
config_entries.ConfigEntryDisabler.USER,
dr.DeviceEntryDisabler.CONFIG_ENTRY,
dr.DeviceEntryDisabler.CONFIG_ENTRY,
{},
),
(
config_entries.ConfigEntryDisabler.USER,
dr.DeviceEntryDisabler.INTEGRATION,
dr.DeviceEntryDisabler.INTEGRATION,
{},
),
(
config_entries.ConfigEntryDisabler.USER,
dr.DeviceEntryDisabler.USER,
dr.DeviceEntryDisabler.USER,
{},
),
],
)
@pytest.mark.usefixtures("freezer")
async def test_update_remove_config_entry_disabled_by(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
removed_config_entry_disabled_by: config_entries.ConfigEntryDisabler | None,
device_disabled_by_initial: dr.DeviceEntryDisabler | None,
device_disabled_by_updated: dr.DeviceEntryDisabler | None,
extra_changes: dict[str, Any],
) -> None:
"""Check how the disabled_by flag is treated when removing a config entry."""
config_entry_1 = MockConfigEntry(
title=None, disabled_by=removed_config_entry_disabled_by
)
config_entry_1.add_to_hass(hass)
config_entry_2 = MockConfigEntry(
title=None, disabled_by=config_entries.ConfigEntryDisabler.USER
)
config_entry_2.add_to_hass(hass)
update_events = async_capture_events(hass, dr.EVENT_DEVICE_REGISTRY_UPDATED)
entry = device_registry.async_get_or_create(
config_entry_id=config_entry_1.entry_id,
config_subentry_id=None,
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
disabled_by=device_disabled_by_initial,
)
assert entry.disabled_by == device_disabled_by_initial
entry2 = device_registry.async_update_device(
entry.id, add_config_entry_id=config_entry_2.entry_id
)
assert entry2.disabled_by == device_disabled_by_initial
entry3 = device_registry.async_update_device(
entry.id, remove_config_entry_id=config_entry_1.entry_id
)
assert entry3 == dr.DeviceEntry(
config_entries={config_entry_2.entry_id},
config_entries_subentries={config_entry_2.entry_id: {None}},
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:ab:cd:ef")},
created_at=utcnow(),
disabled_by=device_disabled_by_updated,
id=entry.id,
modified_at=utcnow(),
primary_config_entry=None,
)
await hass.async_block_till_done()
assert len(update_events) == 3
assert update_events[0].data == {
"action": "create",
"device_id": entry.id,
}
assert update_events[1].data == {
"action": "update",
"device_id": entry.id,
"changes": {
"config_entries": {config_entry_1.entry_id},
"config_entries_subentries": {config_entry_1.entry_id: {None}},
},
}
assert update_events[2].data == {
"action": "update",
"device_id": entry.id,
"changes": {
"config_entries": {config_entry_1.entry_id, config_entry_2.entry_id},
"config_entries_subentries": {
config_entry_1.entry_id: {None},
config_entry_2.entry_id: {None},
},
}
| extra_changes,
}
async def test_cleanup_device_registry(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,