Validate config entry when adding or updating entity registry entry (#135067)

This commit is contained in:
Erik Montnemery 2025-01-17 19:18:13 +01:00 committed by GitHub
parent 028a0d4eec
commit 235fda55fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 71 additions and 7 deletions

View File

@ -648,6 +648,7 @@ def _validate_item(
domain: str,
platform: str,
*,
config_entry_id: str | None | UndefinedType = None,
device_id: str | None | UndefinedType = None,
disabled_by: RegistryEntryDisabler | None | UndefinedType = None,
entity_category: EntityCategory | None | UndefinedType = None,
@ -672,6 +673,11 @@ def _validate_item(
unique_id,
report_issue,
)
if config_entry_id and config_entry_id is not UNDEFINED:
if not hass.config_entries.async_get_entry(config_entry_id):
raise ValueError(
f"Can't link entity to unknown config entry {config_entry_id}"
)
if device_id and device_id is not UNDEFINED:
device_registry = dr.async_get(hass)
if not device_registry.async_get(device_id):
@ -864,6 +870,7 @@ class EntityRegistry(BaseRegistry):
self.hass,
domain,
platform,
config_entry_id=config_entry_id,
device_id=device_id,
disabled_by=disabled_by,
entity_category=entity_category,
@ -1096,6 +1103,7 @@ class EntityRegistry(BaseRegistry):
self.hass,
old.domain,
old.platform,
config_entry_id=config_entry_id,
device_id=device_id,
disabled_by=disabled_by,
entity_category=entity_category,

View File

@ -82,6 +82,7 @@ def _setup_entry(hass: HomeAssistant, config, sensors, unique_id=None):
options={CONF_CONSIDER_HOME: 60},
unique_id=unique_id,
)
config_entry.add_to_hass(hass)
# init variable
obj_prefix = slugify(HOST)
@ -131,8 +132,6 @@ async def _test_sensors(
disabled_by=None,
)
config_entry.add_to_hass(hass)
# initial devices setup
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

View File

@ -751,6 +751,7 @@ async def test_unique_id_migration(
old_unique_id,
) -> None:
"""Test that old unique id format is migrated to the new format that supports multiple accounts."""
config_entry.add_to_hass(hass)
# Create an entity using the old unique id format
entity_registry.async_get_or_create(
DOMAIN,
@ -805,6 +806,7 @@ async def test_invalid_unique_id_cleanup(
mock_calendars_yaml,
) -> None:
"""Test that old unique id format that is not actually unique is removed."""
config_entry.add_to_hass(hass)
# Create an entity using the old unique id format
entity_registry.async_get_or_create(
DOMAIN,

View File

@ -1200,6 +1200,7 @@ async def test_unique_id(
entity_registry: er.EntityRegistry,
) -> None:
"""Test unique id convert to string."""
config_entry.add_to_hass(hass)
entity_registry.async_get_or_create(
Platform.CLIMATE,
DOMAIN,

View File

@ -166,6 +166,7 @@ async def test_group_entity_migration_with_v1_id(
) -> None:
"""Test if entity schema for grouped_lights migrates from v1 to v2."""
config_entry = mock_bridge_v2.config_entry = mock_config_entry_v2
config_entry.add_to_hass(hass)
# create (deviceless) entity with V1 schema in registry
# using the legacy style group id as unique id
@ -201,6 +202,7 @@ async def test_group_entity_migration_with_v2_group_id(
) -> None:
"""Test if entity schema for grouped_lights migrates from v1 to v2."""
config_entry = mock_bridge_v2.config_entry = mock_config_entry_v2
config_entry.add_to_hass(hass)
# create (deviceless) entity with V1 schema in registry
# using the V2 group id as unique id

View File

@ -368,6 +368,7 @@ async def test_migrate_entry(
version=1,
minor_version=1,
)
entry.add_to_hass(hass)
# Add entries with int unique_id
entity_registry.async_get_or_create(
@ -387,7 +388,6 @@ async def test_migrate_entry(
assert entry.version == 1
assert entry.minor_version == 1
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()

View File

@ -444,6 +444,7 @@ async def test_no_listen_start(
version=1,
data={"username": "foo", "token": {}},
)
mock_entry.add_to_hass(hass)
# Create a binary sensor entity so it is not ignored by the deprecation check
# and the listener will start
entity_registry.async_get_or_create(
@ -457,7 +458,6 @@ async def test_no_listen_start(
mock_ring_event_listener_class.return_value.started = False
mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()

View File

@ -46,6 +46,7 @@ async def test_entity_id_migration(
) -> None:
"""Test the migration of unique IDs on config entry setup."""
config_entry = mock_config_entry(unique_id="binary_sensor_test_diffuser_v1")
config_entry.add_to_hass(hass)
# Pre-create old style unique IDs
charging = entity_registry.async_get_or_create(

View File

@ -100,6 +100,7 @@ async def test_entity_entry_migration(
entity_registry: er.EntityRegistry,
) -> None:
"""Test successful migration of entry data."""
mock_config_entry.add_to_hass(hass)
entity = entity_registry.async_get_or_create(
suggested_object_id="advice",
disabled_by=None,

View File

@ -374,6 +374,7 @@ async def test_change_device_source(
# Configure source entity 3 (without a device)
source_config_entry_3 = MockConfigEntry()
source_config_entry_3.add_to_hass(hass)
source_entity_3 = entity_registry.async_get_or_create(
"sensor",
"test",

View File

@ -869,6 +869,7 @@ async def test_setup_entry(
platform = MockPlatform(async_setup_entry=async_setup_entry)
config_entry = MockConfigEntry(entry_id="super-mock-id")
config_entry.add_to_hass(hass)
entity_platform = MockEntityPlatform(
hass, platform_name=config_entry.domain, platform=platform
)
@ -1886,6 +1887,7 @@ async def test_setup_entry_with_entities_that_block_forever(
platform = MockPlatform(async_setup_entry=async_setup_entry)
config_entry = MockConfigEntry(entry_id="super-mock-id")
config_entry.add_to_hass(hass)
platform = MockEntityPlatform(
hass, platform_name=config_entry.domain, platform=platform
)
@ -1934,6 +1936,7 @@ async def test_cancellation_is_not_blocked(
platform = MockPlatform(async_setup_entry=async_setup_entry)
config_entry = MockConfigEntry(entry_id="super-mock-id")
config_entry.add_to_hass(hass)
platform = MockEntityPlatform(
hass, platform_name=config_entry.domain, platform=platform
)

View File

@ -616,11 +616,13 @@ async def test_updating_config_entry_id(
"""Test that we update config entry id in registry."""
update_events = async_capture_events(hass, er.EVENT_ENTITY_REGISTRY_UPDATED)
mock_config_1 = MockConfigEntry(domain="light", entry_id="mock-id-1")
mock_config_1.add_to_hass(hass)
entry = entity_registry.async_get_or_create(
"light", "hue", "5678", config_entry=mock_config_1
)
mock_config_2 = MockConfigEntry(domain="light", entry_id="mock-id-2")
mock_config_2.add_to_hass(hass)
entry2 = entity_registry.async_get_or_create(
"light", "hue", "5678", config_entry=mock_config_2
)
@ -647,6 +649,7 @@ async def test_removing_config_entry_id(
"""Test that we update config entry id in registry."""
update_events = async_capture_events(hass, er.EVENT_ENTITY_REGISTRY_UPDATED)
mock_config = MockConfigEntry(domain="light", entry_id="mock-id-1")
mock_config.add_to_hass(hass)
entry = entity_registry.async_get_or_create(
"light", "hue", "5678", config_entry=mock_config
@ -670,11 +673,14 @@ async def test_removing_config_entry_id(
async def test_deleted_entity_removing_config_entry_id(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
) -> None:
"""Test that we update config entry id in registry on deleted entity."""
mock_config1 = MockConfigEntry(domain="light", entry_id="mock-id-1")
mock_config2 = MockConfigEntry(domain="light", entry_id="mock-id-2")
mock_config1.add_to_hass(hass)
mock_config2.add_to_hass(hass)
entry1 = entity_registry.async_get_or_create(
"light", "hue", "5678", config_entry=mock_config1
@ -979,9 +985,12 @@ async def test_migration_1_11(
}
async def test_update_entity_unique_id(entity_registry: er.EntityRegistry) -> None:
async def test_update_entity_unique_id(
hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
"""Test entity's unique_id is updated."""
mock_config = MockConfigEntry(domain="light", entry_id="mock-id-1")
mock_config.add_to_hass(hass)
entry = entity_registry.async_get_or_create(
"light", "hue", "5678", config_entry=mock_config
@ -1007,10 +1016,12 @@ async def test_update_entity_unique_id(entity_registry: er.EntityRegistry) -> No
async def test_update_entity_unique_id_conflict(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
) -> None:
"""Test migration raises when unique_id already in use."""
mock_config = MockConfigEntry(domain="light", entry_id="mock-id-1")
mock_config.add_to_hass(hass)
entry = entity_registry.async_get_or_create(
"light", "hue", "5678", config_entry=mock_config
)
@ -1099,9 +1110,12 @@ async def test_update_entity_entity_id_entity_id(
assert entity_registry.async_get(state_entity_id) is None
async def test_update_entity(entity_registry: er.EntityRegistry) -> None:
async def test_update_entity(
hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
"""Test updating entity."""
mock_config = MockConfigEntry(domain="light", entry_id="mock-id-1")
mock_config.add_to_hass(hass)
entry = entity_registry.async_get_or_create(
"light", "hue", "5678", config_entry=mock_config
)
@ -1126,9 +1140,12 @@ async def test_update_entity(entity_registry: er.EntityRegistry) -> None:
entry = updated_entry
async def test_update_entity_options(entity_registry: er.EntityRegistry) -> None:
async def test_update_entity_options(
hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
"""Test updating entity."""
mock_config = MockConfigEntry(domain="light", entry_id="mock-id-1")
mock_config.add_to_hass(hass)
entry = entity_registry.async_get_or_create(
"light", "hue", "5678", config_entry=mock_config
)
@ -1181,6 +1198,7 @@ async def test_disabled_by(entity_registry: er.EntityRegistry) -> None:
async def test_disabled_by_config_entry_pref(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
) -> None:
"""Test config entry preference setting disabled_by."""
@ -1189,6 +1207,7 @@ async def test_disabled_by_config_entry_pref(
entry_id="mock-id-1",
pref_disable_new_entities=True,
)
mock_config.add_to_hass(hass)
entry = entity_registry.async_get_or_create(
"light", "hue", "AAAA", config_entry=mock_config
)
@ -1761,6 +1780,25 @@ def test_entity_registry_items() -> None:
assert entities.get_entry(entry2.id) is None
async def test_config_entry_does_not_exist(entity_registry: er.EntityRegistry) -> None:
"""Test adding an entity linked to an unknown config entry."""
mock_config = MockConfigEntry(
domain="light",
entry_id="mock-id-1",
pref_disable_new_entities=True,
)
with pytest.raises(ValueError):
entity_registry.async_get_or_create(
"light", "hue", "1234", config_entry=mock_config
)
entity_id = entity_registry.async_get_or_create("light", "hue", "1234").entity_id
with pytest.raises(ValueError):
entity_registry.async_update_entity(
entity_id, config_entry_id=mock_config.entry_id
)
async def test_device_does_not_exist(entity_registry: er.EntityRegistry) -> None:
"""Test adding an entity linked to an unknown device."""
with pytest.raises(ValueError):
@ -1848,6 +1886,7 @@ def test_migrate_entity_to_new_platform(
) -> None:
"""Test migrate_entity_to_new_platform."""
orig_config_entry = MockConfigEntry(domain="light")
orig_config_entry.add_to_hass(hass)
orig_unique_id = "5678"
orig_entry = entity_registry.async_get_or_create(
@ -1870,6 +1909,7 @@ def test_migrate_entity_to_new_platform(
)
new_config_entry = MockConfigEntry(domain="light")
new_config_entry.add_to_hass(hass)
new_unique_id = "1234"
assert entity_registry.async_update_entity_platform(
@ -1924,6 +1964,7 @@ async def test_restore_entity(
"""Make sure entity registry id is stable and entity_id is reused if possible."""
update_events = async_capture_events(hass, er.EVENT_ENTITY_REGISTRY_UPDATED)
config_entry = MockConfigEntry(domain="light")
config_entry.add_to_hass(hass)
entry1 = entity_registry.async_get_or_create(
"light", "hue", "1234", config_entry=config_entry
)
@ -2018,6 +2059,8 @@ async def test_async_migrate_entry_delete_self(
"""Test async_migrate_entry."""
config_entry1 = MockConfigEntry(domain="test1")
config_entry2 = MockConfigEntry(domain="test2")
config_entry1.add_to_hass(hass)
config_entry2.add_to_hass(hass)
entry1 = entity_registry.async_get_or_create(
"light", "hue", "1234", config_entry=config_entry1, original_name="Entry 1"
)
@ -2053,6 +2096,8 @@ async def test_async_migrate_entry_delete_other(
"""Test async_migrate_entry."""
config_entry1 = MockConfigEntry(domain="test1")
config_entry2 = MockConfigEntry(domain="test2")
config_entry1.add_to_hass(hass)
config_entry2.add_to_hass(hass)
entry1 = entity_registry.async_get_or_create(
"light", "hue", "1234", config_entry=config_entry1, original_name="Entry 1"
)

View File

@ -2126,6 +2126,7 @@ async def test_state_translated(
hass.states.async_set("domain.is_unknown", "unknown", attributes={})
config_entry = MockConfigEntry(domain="light")
config_entry.add_to_hass(hass)
entity_registry.async_get_or_create(
"light",
"hue",