Remove duplicated device before daikin migration (#99900)

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Aaron Collins 2023-10-03 21:11:21 +13:00 committed by GitHub
parent 69e588b15e
commit 99f227229e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 128 additions and 8 deletions

View File

@ -135,9 +135,11 @@ async def async_migrate_unique_id(
) -> None: ) -> None:
"""Migrate old entry.""" """Migrate old entry."""
dev_reg = dr.async_get(hass) dev_reg = dr.async_get(hass)
ent_reg = er.async_get(hass)
old_unique_id = config_entry.unique_id old_unique_id = config_entry.unique_id
new_unique_id = api.device.mac new_unique_id = api.device.mac
new_name = api.device.values.get("name") new_mac = dr.format_mac(new_unique_id)
new_name = api.name
@callback @callback
def _update_unique_id(entity_entry: er.RegistryEntry) -> dict[str, str] | None: def _update_unique_id(entity_entry: er.RegistryEntry) -> dict[str, str] | None:
@ -147,15 +149,36 @@ async def async_migrate_unique_id(
if new_unique_id == old_unique_id: if new_unique_id == old_unique_id:
return return
duplicate = dev_reg.async_get_device(
connections={(CONNECTION_NETWORK_MAC, new_mac)}, identifiers=None
)
# Remove duplicated device
if duplicate is not None:
if config_entry.entry_id in duplicate.config_entries:
_LOGGER.debug(
"Removing duplicated device %s",
duplicate.name,
)
# The automatic cleanup in entity registry is scheduled as a task, remove
# the entities manually to avoid unique_id collision when the entities
# are migrated.
duplicate_entities = er.async_entries_for_device(
ent_reg, duplicate.id, True
)
for entity in duplicate_entities:
ent_reg.async_remove(entity.entity_id)
dev_reg.async_remove_device(duplicate.id)
# Migrate devices # Migrate devices
for device_entry in dr.async_entries_for_config_entry( for device_entry in dr.async_entries_for_config_entry(
dev_reg, config_entry.entry_id dev_reg, config_entry.entry_id
): ):
for connection in device_entry.connections: for connection in device_entry.connections:
if connection[1] == old_unique_id: if connection[1] == old_unique_id:
new_connections = { new_connections = {(CONNECTION_NETWORK_MAC, new_mac)}
(CONNECTION_NETWORK_MAC, dr.format_mac(new_unique_id))
}
_LOGGER.debug( _LOGGER.debug(
"Migrating device %s connections to %s", "Migrating device %s connections to %s",

View File

@ -1,11 +1,13 @@
"""Define tests for the Daikin init.""" """Define tests for the Daikin init."""
import asyncio import asyncio
from datetime import timedelta
from unittest.mock import AsyncMock, PropertyMock, patch from unittest.mock import AsyncMock, PropertyMock, patch
from aiohttp import ClientConnectionError from aiohttp import ClientConnectionError
from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from homeassistant.components.daikin import update_unique_id from homeassistant.components.daikin import DaikinApi, update_unique_id
from homeassistant.components.daikin.const import DOMAIN, KEY_MAC from homeassistant.components.daikin.const import DOMAIN, KEY_MAC
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
@ -14,7 +16,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er
from .test_config_flow import HOST, MAC from .test_config_flow import HOST, MAC
from tests.common import MockConfigEntry from tests.common import MockConfigEntry, async_fire_time_changed
@pytest.fixture @pytest.fixture
@ -28,6 +30,7 @@ def mock_daikin():
with patch("homeassistant.components.daikin.Appliance") as Appliance: with patch("homeassistant.components.daikin.Appliance") as Appliance:
Appliance.factory.side_effect = mock_daikin_factory Appliance.factory.side_effect = mock_daikin_factory
type(Appliance).update_status = AsyncMock() type(Appliance).update_status = AsyncMock()
type(Appliance).device_ip = PropertyMock(return_value=HOST)
type(Appliance).inside_temperature = PropertyMock(return_value=22) type(Appliance).inside_temperature = PropertyMock(return_value=22)
type(Appliance).target_temperature = PropertyMock(return_value=22) type(Appliance).target_temperature = PropertyMock(return_value=22)
type(Appliance).zones = PropertyMock(return_value=[("Zone 1", "0", 0)]) type(Appliance).zones = PropertyMock(return_value=[("Zone 1", "0", 0)])
@ -47,6 +50,67 @@ DATA = {
INVALID_DATA = {**DATA, "name": None, "mac": HOST} INVALID_DATA = {**DATA, "name": None, "mac": HOST}
async def test_duplicate_removal(hass: HomeAssistant, mock_daikin) -> None:
"""Test duplicate device removal."""
config_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=HOST,
title=None,
data={CONF_HOST: HOST, KEY_MAC: HOST},
)
config_entry.add_to_hass(hass)
entity_registry = er.async_get(hass)
device_registry = dr.async_get(hass)
type(mock_daikin).mac = PropertyMock(return_value=HOST)
type(mock_daikin).values = PropertyMock(return_value=INVALID_DATA)
with patch(
"homeassistant.components.daikin.async_migrate_unique_id", return_value=None
):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.unique_id != MAC
type(mock_daikin).mac = PropertyMock(return_value=MAC)
type(mock_daikin).values = PropertyMock(return_value=DATA)
assert await hass.config_entries.async_reload(config_entry.entry_id)
await hass.async_block_till_done()
assert (
device_registry.async_get_device({}, {(KEY_MAC, MAC)}).name
== "DaikinAP00000"
)
assert device_registry.async_get_device({}, {(KEY_MAC, HOST)}).name is None
assert entity_registry.async_get("climate.daikin_127_0_0_1").unique_id == HOST
assert entity_registry.async_get("switch.none_zone_1").unique_id.startswith(
HOST
)
assert entity_registry.async_get("climate.daikinap00000").unique_id == MAC
assert entity_registry.async_get(
"switch.daikinap00000_zone_1"
).unique_id.startswith(MAC)
assert await hass.config_entries.async_reload(config_entry.entry_id)
await hass.async_block_till_done()
assert (
device_registry.async_get_device({}, {(KEY_MAC, MAC)}).name == "DaikinAP00000"
)
assert entity_registry.async_get("climate.daikinap00000") is None
assert entity_registry.async_get("switch.daikinap00000_zone_1") is None
assert entity_registry.async_get("climate.daikin_127_0_0_1").unique_id == MAC
assert entity_registry.async_get("switch.none_zone_1").unique_id.startswith(MAC)
async def test_unique_id_migrate(hass: HomeAssistant, mock_daikin) -> None: async def test_unique_id_migrate(hass: HomeAssistant, mock_daikin) -> None:
"""Test unique id migration.""" """Test unique id migration."""
config_entry = MockConfigEntry( config_entry = MockConfigEntry(
@ -97,8 +161,41 @@ async def test_unique_id_migrate(hass: HomeAssistant, mock_daikin) -> None:
assert entity_registry.async_get("switch.none_zone_1").unique_id.startswith(MAC) assert entity_registry.async_get("switch.none_zone_1").unique_id.startswith(MAC)
async def test_client_update_connection_error(
hass: HomeAssistant, mock_daikin, freezer: FrozenDateTimeFactory
) -> None:
"""Test client connection error on update."""
config_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=MAC,
data={CONF_HOST: HOST, KEY_MAC: MAC},
)
config_entry.add_to_hass(hass)
er.async_get(hass)
type(mock_daikin).mac = PropertyMock(return_value=MAC)
type(mock_daikin).values = PropertyMock(return_value=DATA)
await hass.config_entries.async_setup(config_entry.entry_id)
api: DaikinApi = hass.data[DOMAIN][config_entry.entry_id]
assert api.available is True
type(mock_daikin).update_status.side_effect = ClientConnectionError
freezer.tick(timedelta(seconds=90))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert api.available is False
assert mock_daikin.update_status.call_count == 2
async def test_client_connection_error(hass: HomeAssistant, mock_daikin) -> None: async def test_client_connection_error(hass: HomeAssistant, mock_daikin) -> None:
"""Test unique id migration.""" """Test client connection error on setup."""
config_entry = MockConfigEntry( config_entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id=MAC, unique_id=MAC,
@ -114,7 +211,7 @@ async def test_client_connection_error(hass: HomeAssistant, mock_daikin) -> None
async def test_timeout_error(hass: HomeAssistant, mock_daikin) -> None: async def test_timeout_error(hass: HomeAssistant, mock_daikin) -> None:
"""Test unique id migration.""" """Test timeout error on setup."""
config_entry = MockConfigEntry( config_entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id=MAC, unique_id=MAC,