Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reolink fix dev/entity id migration #130836

Merged
merged 5 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions homeassistant/components/reolink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,19 @@ def migrate_entity_ids(
else:
new_device_id = f"{device_uid[0]}_{host.api.camera_uid(ch)}"
new_identifiers = {(DOMAIN, new_device_id)}
device_reg.async_update_device(device.id, new_identifiers=new_identifiers)
existing_device = device_reg.async_get_device(identifiers=new_identifiers)
if existing_device is None:
device_reg.async_update_device(
device.id, new_identifiers=new_identifiers
)
else:
_LOGGER.warning(
"Reolink device with uid %s already exists, "
"removing device with uid %s",
new_device_id,
device_uid,
)
device_reg.async_remove_device(device.id)

entity_reg = er.async_get(hass)
entities = er.async_entries_for_config_entry(entity_reg, config_entry_id)
Expand All @@ -352,4 +364,18 @@ def migrate_entity_ids(
id_parts = entity.unique_id.split("_", 2)
if host.api.supported(ch, "UID") and id_parts[1] != host.api.camera_uid(ch):
new_id = f"{host.unique_id}_{host.api.camera_uid(ch)}_{id_parts[2]}"
entity_reg.async_update_entity(entity.entity_id, new_unique_id=new_id)
existing_entity = entity_reg.async_get_entity_id(
entity.domain, entity.platform, new_id
)
if existing_entity is None:
entity_reg.async_update_entity(
entity.entity_id, new_unique_id=new_id
)
else:
_LOGGER.warning(
"Reolink entity with unique_id %s already exists, "
"removing device with unique_id %s",
new_id,
entity.unique_id,
)
entity_reg.async_remove(entity.entity_id)
110 changes: 110 additions & 0 deletions tests/components/reolink/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,116 @@ def mock_supported(ch, capability):
assert device_registry.async_get_device(identifiers={(DOMAIN, new_dev_id)})


async def test_migrate_with_already_existing_device(
hass: HomeAssistant,
config_entry: MockConfigEntry,
reolink_connect: MagicMock,
entity_registry: er.EntityRegistry,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test device ids that need to be migrated while the new ids already exist."""
original_dev_id = f"{TEST_MAC}_ch0"
new_dev_id = f"{TEST_UID}_{TEST_UID_CAM}"
domain = Platform.SWITCH

def mock_supported(ch, capability):
if capability == "UID" and ch is None:
return True
if capability == "UID":
return True
return True

reolink_connect.channels = [0]
reolink_connect.supported = mock_supported

device_registry.async_get_or_create(
identifiers={(DOMAIN, new_dev_id)},
config_entry_id=config_entry.entry_id,
disabled_by=None,
)

device_registry.async_get_or_create(
identifiers={(DOMAIN, original_dev_id)},
config_entry_id=config_entry.entry_id,
disabled_by=None,
)

assert device_registry.async_get_device(identifiers={(DOMAIN, original_dev_id)})
assert device_registry.async_get_device(identifiers={(DOMAIN, new_dev_id)})

# setup CH 0 and host entities/device
with patch("homeassistant.components.reolink.PLATFORMS", [domain]):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

assert (
device_registry.async_get_device(identifiers={(DOMAIN, original_dev_id)})
is None
)
assert device_registry.async_get_device(identifiers={(DOMAIN, new_dev_id)})


async def test_migrate_with_already_existing_entity(
hass: HomeAssistant,
config_entry: MockConfigEntry,
reolink_connect: MagicMock,
entity_registry: er.EntityRegistry,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test entity ids that need to be migrated while the new ids already exist."""
original_id = f"{TEST_UID}_0_record_audio"
new_id = f"{TEST_UID}_{TEST_UID_CAM}_record_audio"
dev_id = f"{TEST_UID}_{TEST_UID_CAM}"
domain = Platform.SWITCH

def mock_supported(ch, capability):
if capability == "UID" and ch is None:
return True
if capability == "UID":
return True
return True

reolink_connect.channels = [0]
reolink_connect.supported = mock_supported

dev_entry = device_registry.async_get_or_create(
identifiers={(DOMAIN, dev_id)},
config_entry_id=config_entry.entry_id,
disabled_by=None,
)

entity_registry.async_get_or_create(
domain=domain,
platform=DOMAIN,
unique_id=new_id,
config_entry=config_entry,
suggested_object_id=new_id,
disabled_by=None,
device_id=dev_entry.id,
)

entity_registry.async_get_or_create(
domain=domain,
platform=DOMAIN,
unique_id=original_id,
config_entry=config_entry,
suggested_object_id=original_id,
disabled_by=None,
device_id=dev_entry.id,
)

assert entity_registry.async_get_entity_id(domain, DOMAIN, original_id)
assert entity_registry.async_get_entity_id(domain, DOMAIN, new_id)

# setup CH 0 and host entities/device
with patch("homeassistant.components.reolink.PLATFORMS", [domain]):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

assert entity_registry.async_get_entity_id(domain, DOMAIN, original_id) is None
assert entity_registry.async_get_entity_id(domain, DOMAIN, new_id)


async def test_no_repair_issue(
hass: HomeAssistant, config_entry: MockConfigEntry, issue_registry: ir.IssueRegistry
) -> None:
Expand Down
Loading