From a7088e767ee7519448829651cddc6eeb16f3b8e1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 1 May 2023 22:46:38 +0200 Subject: [PATCH] Migrate unique IDs of Rituals Perfume Genie (#92342) * Migrate unique IDs of Rituals Perfume Genie * Fix doc string --- .../rituals_perfume_genie/__init__.py | 43 +++++++++++- .../rituals_perfume_genie/binary_sensor.py | 1 + .../rituals_perfume_genie/entity.py | 1 - .../rituals_perfume_genie/number.py | 1 + .../rituals_perfume_genie/select.py | 1 + .../rituals_perfume_genie/sensor.py | 4 ++ .../rituals_perfume_genie/switch.py | 1 + .../test_binary_sensor.py | 3 +- .../rituals_perfume_genie/test_init.py | 69 ++++++++++++++++++- .../rituals_perfume_genie/test_number.py | 3 +- .../rituals_perfume_genie/test_select.py | 3 +- .../rituals_perfume_genie/test_sensor.py | 16 ++--- .../rituals_perfume_genie/test_switch.py | 2 +- 13 files changed, 126 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/rituals_perfume_genie/__init__.py b/homeassistant/components/rituals_perfume_genie/__init__.py index b607a84b5d1..e0fac0abfcf 100644 --- a/homeassistant/components/rituals_perfume_genie/__init__.py +++ b/homeassistant/components/rituals_perfume_genie/__init__.py @@ -2,12 +2,13 @@ import asyncio import aiohttp -from pyrituals import Account +from pyrituals import Account, Diffuser from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ACCOUNT_HASH, DOMAIN @@ -32,6 +33,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except aiohttp.ClientError as err: raise ConfigEntryNotReady from err + # Migrate old unique_ids to the new format + async_migrate_entities_unique_ids(hass, entry, account_devices) + # Create a coordinator for each diffuser coordinators = { diffuser.hublot: RitualsDataUpdateCoordinator(hass, diffuser) @@ -59,3 +63,38 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN].pop(entry.entry_id) return unload_ok + + +@callback +def async_migrate_entities_unique_ids( + hass: HomeAssistant, config_entry: ConfigEntry, diffusers: list[Diffuser] +) -> None: + """Migrate unique_ids in the entity registry to the new format.""" + entity_registry = er.async_get(hass) + registry_entries = er.async_entries_for_config_entry( + entity_registry, config_entry.entry_id + ) + + conversion: dict[tuple[str, str], str] = { + (Platform.BINARY_SENSOR, " Battery Charging"): "charging", + (Platform.NUMBER, " Perfume Amount"): "perfume_amount", + (Platform.SELECT, " Room Size"): "room_size_square_meter", + (Platform.SENSOR, " Battery"): "battery_percentage", + (Platform.SENSOR, " Fill"): "fill", + (Platform.SENSOR, " Perfume"): "perfume", + (Platform.SENSOR, " Wifi"): "wifi_percentage", + (Platform.SWITCH, ""): "is_on", + } + + for diffuser in diffusers: + for registry_entry in registry_entries: + if new_unique_id := conversion.get( + ( + registry_entry.domain, + registry_entry.unique_id.removeprefix(diffuser.hublot), + ) + ): + entity_registry.async_update_entity( + registry_entry.entity_id, + new_unique_id=f"{diffuser.hublot}-{new_unique_id}", + ) diff --git a/homeassistant/components/rituals_perfume_genie/binary_sensor.py b/homeassistant/components/rituals_perfume_genie/binary_sensor.py index 95d28c9797b..d6c5eaeaca1 100644 --- a/homeassistant/components/rituals_perfume_genie/binary_sensor.py +++ b/homeassistant/components/rituals_perfume_genie/binary_sensor.py @@ -43,6 +43,7 @@ class DiffuserBatteryChargingBinarySensor(DiffuserEntity, BinarySensorEntity): def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None: """Initialize the battery charging binary sensor.""" super().__init__(coordinator, CHARGING_SUFFIX) + self._attr_unique_id = f"{coordinator.diffuser.hublot}-charging" @property def is_on(self) -> bool: diff --git a/homeassistant/components/rituals_perfume_genie/entity.py b/homeassistant/components/rituals_perfume_genie/entity.py index b5e016c2066..2cb1eccd4e9 100644 --- a/homeassistant/components/rituals_perfume_genie/entity.py +++ b/homeassistant/components/rituals_perfume_genie/entity.py @@ -26,7 +26,6 @@ class DiffuserEntity(CoordinatorEntity[RitualsDataUpdateCoordinator]): hubname = coordinator.diffuser.name self._attr_name = f"{hubname}{entity_suffix}" - self._attr_unique_id = f"{hublot}{entity_suffix}" self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, hublot)}, manufacturer=MANUFACTURER, diff --git a/homeassistant/components/rituals_perfume_genie/number.py b/homeassistant/components/rituals_perfume_genie/number.py index 3da28f8b6f4..6d7eb53cb59 100644 --- a/homeassistant/components/rituals_perfume_genie/number.py +++ b/homeassistant/components/rituals_perfume_genie/number.py @@ -40,6 +40,7 @@ class DiffuserPerfumeAmount(DiffuserEntity, NumberEntity): def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None: """Initialize the diffuser perfume amount number.""" super().__init__(coordinator, PERFUME_AMOUNT_SUFFIX) + self._attr_unique_id = f"{coordinator.diffuser.hublot}-perfume_amount" @property def native_value(self) -> int: diff --git a/homeassistant/components/rituals_perfume_genie/select.py b/homeassistant/components/rituals_perfume_genie/select.py index b40c3045577..a6ac10eadec 100644 --- a/homeassistant/components/rituals_perfume_genie/select.py +++ b/homeassistant/components/rituals_perfume_genie/select.py @@ -43,6 +43,7 @@ class DiffuserRoomSize(DiffuserEntity, SelectEntity): self._attr_entity_registry_enabled_default = ( self.coordinator.diffuser.has_battery ) + self._attr_unique_id = f"{coordinator.diffuser.hublot}-room_size_square_meter" @property def current_option(self) -> str: diff --git a/homeassistant/components/rituals_perfume_genie/sensor.py b/homeassistant/components/rituals_perfume_genie/sensor.py index 1f301d3538d..e2a1be63adf 100644 --- a/homeassistant/components/rituals_perfume_genie/sensor.py +++ b/homeassistant/components/rituals_perfume_genie/sensor.py @@ -48,6 +48,7 @@ class DiffuserPerfumeSensor(DiffuserEntity, SensorEntity): def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None: """Initialize the perfume sensor.""" super().__init__(coordinator, PERFUME_SUFFIX) + self._attr_unique_id = f"{coordinator.diffuser.hublot}-perfume" @property def icon(self) -> str: @@ -68,6 +69,7 @@ class DiffuserFillSensor(DiffuserEntity, SensorEntity): def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None: """Initialize the fill sensor.""" super().__init__(coordinator, FILL_SUFFIX) + self._attr_unique_id = f"{coordinator.diffuser.hublot}-fill" @property def icon(self) -> str: @@ -92,6 +94,7 @@ class DiffuserBatterySensor(DiffuserEntity, SensorEntity): def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None: """Initialize the battery sensor.""" super().__init__(coordinator, BATTERY_SUFFIX) + self._attr_unique_id = f"{coordinator.diffuser.hublot}-battery_percentage" @property def native_value(self) -> int: @@ -108,6 +111,7 @@ class DiffuserWifiSensor(DiffuserEntity, SensorEntity): def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None: """Initialize the wifi sensor.""" super().__init__(coordinator, WIFI_SUFFIX) + self._attr_unique_id = f"{coordinator.diffuser.hublot}-wifi_percentage" @property def native_value(self) -> int: diff --git a/homeassistant/components/rituals_perfume_genie/switch.py b/homeassistant/components/rituals_perfume_genie/switch.py index 92513d9a83b..180abb13576 100644 --- a/homeassistant/components/rituals_perfume_genie/switch.py +++ b/homeassistant/components/rituals_perfume_genie/switch.py @@ -37,6 +37,7 @@ class DiffuserSwitch(DiffuserEntity, SwitchEntity): """Initialize the diffuser switch.""" super().__init__(coordinator, "") self._attr_is_on = self.coordinator.diffuser.is_on + self._attr_unique_id = f"{coordinator.diffuser.hublot}-is_on" async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" diff --git a/tests/components/rituals_perfume_genie/test_binary_sensor.py b/tests/components/rituals_perfume_genie/test_binary_sensor.py index ea4d8021eba..082725a8f99 100644 --- a/tests/components/rituals_perfume_genie/test_binary_sensor.py +++ b/tests/components/rituals_perfume_genie/test_binary_sensor.py @@ -1,6 +1,5 @@ """Tests for the Rituals Perfume Genie binary sensor platform.""" from homeassistant.components.binary_sensor import BinarySensorDeviceClass -from homeassistant.components.rituals_perfume_genie.binary_sensor import CHARGING_SUFFIX from homeassistant.const import ATTR_DEVICE_CLASS, STATE_ON, EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -30,5 +29,5 @@ async def test_binary_sensors( entry = entity_registry.async_get("binary_sensor.genie_battery_charging") assert entry - assert entry.unique_id == f"{hublot}{CHARGING_SUFFIX}" + assert entry.unique_id == f"{hublot}-charging" assert entry.entity_category == EntityCategory.DIAGNOSTIC diff --git a/tests/components/rituals_perfume_genie/test_init.py b/tests/components/rituals_perfume_genie/test_init.py index 0f1c2a230d1..8766e99f542 100644 --- a/tests/components/rituals_perfume_genie/test_init.py +++ b/tests/components/rituals_perfume_genie/test_init.py @@ -6,8 +6,13 @@ import aiohttp from homeassistant.components.rituals_perfume_genie.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er -from .common import init_integration, mock_config_entry +from .common import ( + init_integration, + mock_config_entry, + mock_diffuser_v1_battery_cartridge, +) async def test_config_entry_not_ready(hass: HomeAssistant) -> None: @@ -32,3 +37,65 @@ async def test_config_entry_unload(hass: HomeAssistant) -> None: assert config_entry.state is ConfigEntryState.NOT_LOADED assert config_entry.entry_id not in hass.data[DOMAIN] + + +async def test_entity_id_migration( + hass: HomeAssistant, entity_registry: er.RegistryEntry +) -> None: + """Test the migration of unique IDs on config entry setup.""" + config_entry = mock_config_entry(unique_id="binary_sensor_test_diffuser_v1") + + # Pre-create old style unique IDs + charging = entity_registry.async_get_or_create( + "binary_sensor", DOMAIN, "lot123v1 Battery Charging", config_entry=config_entry + ) + perfume_amount = entity_registry.async_get_or_create( + "number", DOMAIN, "lot123v1 Perfume Amount", config_entry=config_entry + ) + room_size = entity_registry.async_get_or_create( + "select", DOMAIN, "lot123v1 Room Size", config_entry=config_entry + ) + battery = entity_registry.async_get_or_create( + "sensor", DOMAIN, "lot123v1 Battery", config_entry=config_entry + ) + fill = entity_registry.async_get_or_create( + "sensor", DOMAIN, "lot123v1 Fill", config_entry=config_entry + ) + perfume = entity_registry.async_get_or_create( + "sensor", DOMAIN, "lot123v1 Perfume", config_entry=config_entry + ) + wifi = entity_registry.async_get_or_create( + "sensor", DOMAIN, "lot123v1 Wifi", config_entry=config_entry + ) + switch = entity_registry.async_get_or_create( + "switch", DOMAIN, "lot123v1", config_entry=config_entry + ) + + # Set up integration + diffuser = mock_diffuser_v1_battery_cartridge() + await init_integration(hass, config_entry, [diffuser]) + + # Check that old style unique IDs have been migrated + entry = entity_registry.async_get(charging.entity_id) + assert entry.unique_id == "lot123v1-charging" + + entry = entity_registry.async_get(perfume_amount.entity_id) + assert entry.unique_id == "lot123v1-perfume_amount" + + entry = entity_registry.async_get(room_size.entity_id) + assert entry.unique_id == "lot123v1-room_size_square_meter" + + entry = entity_registry.async_get(battery.entity_id) + assert entry.unique_id == "lot123v1-battery_percentage" + + entry = entity_registry.async_get(fill.entity_id) + assert entry.unique_id == "lot123v1-fill" + + entry = entity_registry.async_get(perfume.entity_id) + assert entry.unique_id == "lot123v1-perfume" + + entry = entity_registry.async_get(wifi.entity_id) + assert entry.unique_id == "lot123v1-wifi_percentage" + + entry = entity_registry.async_get(switch.entity_id) + assert entry.unique_id == "lot123v1-is_on" diff --git a/tests/components/rituals_perfume_genie/test_number.py b/tests/components/rituals_perfume_genie/test_number.py index 028ab40ed76..550f09202ac 100644 --- a/tests/components/rituals_perfume_genie/test_number.py +++ b/tests/components/rituals_perfume_genie/test_number.py @@ -14,7 +14,6 @@ from homeassistant.components.number import ( from homeassistant.components.rituals_perfume_genie.number import ( MAX_PERFUME_AMOUNT, MIN_PERFUME_AMOUNT, - PERFUME_AMOUNT_SUFFIX, ) from homeassistant.const import ATTR_ENTITY_ID, ATTR_ICON from homeassistant.core import HomeAssistant @@ -46,7 +45,7 @@ async def test_number_entity( entry = entity_registry.async_get("number.genie_perfume_amount") assert entry - assert entry.unique_id == f"{diffuser.hublot}{PERFUME_AMOUNT_SUFFIX}" + assert entry.unique_id == f"{diffuser.hublot}-perfume_amount" async def test_set_number_value(hass: HomeAssistant) -> None: diff --git a/tests/components/rituals_perfume_genie/test_select.py b/tests/components/rituals_perfume_genie/test_select.py index 00147b9073c..3153005d094 100644 --- a/tests/components/rituals_perfume_genie/test_select.py +++ b/tests/components/rituals_perfume_genie/test_select.py @@ -2,7 +2,6 @@ import pytest from homeassistant.components.homeassistant import SERVICE_UPDATE_ENTITY -from homeassistant.components.rituals_perfume_genie.select import ROOM_SIZE_SUFFIX from homeassistant.components.select import ( ATTR_OPTION, ATTR_OPTIONS, @@ -38,7 +37,7 @@ async def test_select_entity( entry = entity_registry.async_get("select.genie_room_size") assert entry - assert entry.unique_id == f"{diffuser.hublot}{ROOM_SIZE_SUFFIX}" + assert entry.unique_id == f"{diffuser.hublot}-room_size_square_meter" assert entry.unit_of_measurement == AREA_SQUARE_METERS assert entry.entity_category == EntityCategory.CONFIG diff --git a/tests/components/rituals_perfume_genie/test_sensor.py b/tests/components/rituals_perfume_genie/test_sensor.py index 5573ddc6332..dbbd83d1df3 100644 --- a/tests/components/rituals_perfume_genie/test_sensor.py +++ b/tests/components/rituals_perfume_genie/test_sensor.py @@ -1,11 +1,5 @@ """Tests for the Rituals Perfume Genie sensor platform.""" -from homeassistant.components.rituals_perfume_genie.sensor import ( - BATTERY_SUFFIX, - FILL_SUFFIX, - PERFUME_SUFFIX, - WIFI_SUFFIX, - SensorDeviceClass, -) +from homeassistant.components.rituals_perfume_genie.sensor import SensorDeviceClass from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ICON, @@ -40,7 +34,7 @@ async def test_sensors_diffuser_v1_battery_cartridge( entry = entity_registry.async_get("sensor.genie_perfume") assert entry - assert entry.unique_id == f"{hublot}{PERFUME_SUFFIX}" + assert entry.unique_id == f"{hublot}-perfume" state = hass.states.get("sensor.genie_fill") assert state @@ -49,7 +43,7 @@ async def test_sensors_diffuser_v1_battery_cartridge( entry = entity_registry.async_get("sensor.genie_fill") assert entry - assert entry.unique_id == f"{hublot}{FILL_SUFFIX}" + assert entry.unique_id == f"{hublot}-fill" state = hass.states.get("sensor.genie_battery") assert state @@ -59,7 +53,7 @@ async def test_sensors_diffuser_v1_battery_cartridge( entry = entity_registry.async_get("sensor.genie_battery") assert entry - assert entry.unique_id == f"{hublot}{BATTERY_SUFFIX}" + assert entry.unique_id == f"{hublot}-battery_percentage" assert entry.entity_category == EntityCategory.DIAGNOSTIC state = hass.states.get("sensor.genie_wifi") @@ -70,7 +64,7 @@ async def test_sensors_diffuser_v1_battery_cartridge( entry = entity_registry.async_get("sensor.genie_wifi") assert entry - assert entry.unique_id == f"{hublot}{WIFI_SUFFIX}" + assert entry.unique_id == f"{hublot}-wifi_percentage" assert entry.entity_category == EntityCategory.DIAGNOSTIC diff --git a/tests/components/rituals_perfume_genie/test_switch.py b/tests/components/rituals_perfume_genie/test_switch.py index 7f33e238118..69c2dc01923 100644 --- a/tests/components/rituals_perfume_genie/test_switch.py +++ b/tests/components/rituals_perfume_genie/test_switch.py @@ -38,7 +38,7 @@ async def test_switch_entity( entry = entity_registry.async_get("switch.genie") assert entry - assert entry.unique_id == diffuser.hublot + assert entry.unique_id == f"{diffuser.hublot}-is_on" async def test_switch_handle_coordinator_update(hass: HomeAssistant) -> None: