Skip to content

Commit

Permalink
Migrate unique ID in vesync switches (home-assistant#137099)
Browse files Browse the repository at this point in the history
  • Loading branch information
cdnninja authored Feb 3, 2025
1 parent b5662de commit 37461d7
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 2 deletions.
35 changes: 35 additions & 0 deletions homeassistant/components/vesync/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.dispatcher import async_dispatcher_send

from .common import async_generate_device_list
Expand Down Expand Up @@ -91,3 +92,37 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.pop(DOMAIN)

return unload_ok


async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate old entry."""
_LOGGER.debug(
"Migrating VeSync config entry: %s minor version: %s",
config_entry.version,
config_entry.minor_version,
)
if config_entry.minor_version == 1:
# Migrate switch/outlets entity to a new unique ID
_LOGGER.debug("Migrating VeSync config entry from version 1 to version 2")
entity_registry = er.async_get(hass)
registry_entries = er.async_entries_for_config_entry(
entity_registry, config_entry.entry_id
)
for reg_entry in registry_entries:
if "-" not in reg_entry.unique_id and reg_entry.entity_id.startswith(
Platform.SWITCH
):
_LOGGER.debug(
"Migrating switch/outlet entity from unique_id: %s to unique_id: %s",
reg_entry.unique_id,
reg_entry.unique_id + "-device_status",
)
entity_registry.async_update_entity(
reg_entry.entity_id,
new_unique_id=reg_entry.unique_id + "-device_status",
)
else:
_LOGGER.debug("Skipping entity with unique_id: %s", reg_entry.unique_id)
hass.config_entries.async_update_entry(config_entry, minor_version=2)

return True
1 change: 1 addition & 0 deletions homeassistant/components/vesync/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class VeSyncFlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle a config flow."""

VERSION = 1
MINOR_VERSION = 2

@callback
def _show_form(self, errors: dict[str, str] | None = None) -> ConfigFlowResult:
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/vesync/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def __init__(
) -> None:
"""Initialize the VeSync switch device."""
super().__init__(plug, coordinator)
self._attr_unique_id = f"{super().unique_id}-device_status"
self.smartplug = plug


Expand All @@ -94,4 +95,5 @@ def __init__(
) -> None:
"""Initialize Light Switch device class."""
super().__init__(switch, coordinator)
self._attr_unique_id = f"{super().unique_id}-device_status"
self.switch = switch
22 changes: 22 additions & 0 deletions tests/components/vesync/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,25 @@ async def humidifier_config_entry(
await hass.async_block_till_done()

return entry


@pytest.fixture(name="switch_old_id_config_entry")
async def switch_old_id_config_entry(
hass: HomeAssistant, requests_mock: requests_mock.Mocker, config
) -> MockConfigEntry:
"""Create a mock VeSync config entry for `switch` with the old unique ID approach."""
entry = MockConfigEntry(
title="VeSync",
domain=DOMAIN,
data=config[DOMAIN],
version=1,
minor_version=1,
)
entry.add_to_hass(hass)

wall_switch = "Wall Switch"
humidifer = "Humidifier 200s"

mock_multiple_device_responses(requests_mock, [wall_switch, humidifer])

return entry
4 changes: 2 additions & 2 deletions tests/components/vesync/snapshots/test_switch.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'outlet',
'unique_id': 'outlet-device_status',
'unit_of_measurement': None,
}),
])
Expand Down Expand Up @@ -525,7 +525,7 @@
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'switch',
'unique_id': 'switch-device_status',
'unit_of_measurement': None,
}),
])
Expand Down
55 changes: 55 additions & 0 deletions tests/components/vesync/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er

from tests.common import MockConfigEntry


async def test_async_setup_entry__not_login(
Expand Down Expand Up @@ -125,3 +128,55 @@ async def test_async_new_device_discovery(
assert manager.login.call_count == 1
assert hass.data[DOMAIN][VS_MANAGER] == manager
assert hass.data[DOMAIN][VS_DEVICES] == [fan, humidifier]


async def test_migrate_config_entry(
hass: HomeAssistant,
switch_old_id_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test migration of config entry. Only migrates switches to a new unique_id."""
switch: er.RegistryEntry = entity_registry.async_get_or_create(
domain="switch",
platform="vesync",
unique_id="switch",
config_entry=switch_old_id_config_entry,
suggested_object_id="switch",
)

humidifer: er.RegistryEntry = entity_registry.async_get_or_create(
domain="humidifer",
platform="vesync",
unique_id="humidifer",
config_entry=switch_old_id_config_entry,
suggested_object_id="humidifer",
)

assert switch.unique_id == "switch"
assert switch_old_id_config_entry.minor_version == 1
assert humidifer.unique_id == "humidifer"

await hass.config_entries.async_setup(switch_old_id_config_entry.entry_id)
await hass.async_block_till_done()

assert switch_old_id_config_entry.minor_version == 2

migrated_switch = entity_registry.async_get(switch.entity_id)
assert migrated_switch is not None
assert migrated_switch.entity_id.startswith("switch")
assert migrated_switch.unique_id == "switch-device_status"
# Confirm humidifer was not impacted
migrated_humidifer = entity_registry.async_get(humidifer.entity_id)
assert migrated_humidifer is not None
assert migrated_humidifer.unique_id == "humidifer"

# Assert that only one entity exists in the switch domain
switch_entities = [
e for e in entity_registry.entities.values() if e.domain == "switch"
]
assert len(switch_entities) == 1

humidifer_entities = [
e for e in entity_registry.entities.values() if e.domain == "humidifer"
]
assert len(humidifer_entities) == 1

0 comments on commit 37461d7

Please sign in to comment.