diff --git a/homeassistant/components/voip/devices.py b/homeassistant/components/voip/devices.py index 163cb445340..c33ec048cbd 100644 --- a/homeassistant/components/voip/devices.py +++ b/homeassistant/components/voip/devices.py @@ -4,6 +4,7 @@ from __future__ import annotations from collections.abc import Callable, Iterator from dataclasses import dataclass, field +from typing import Any from voip_utils import CallInfo, VoipDatagramProtocol @@ -144,19 +145,39 @@ class VoIPDevices: if voip_device is None: # If we couldn't find the device based on SIP URI, see if we can # find an old device based on just the host/IP and migrate it - voip_device = self.devices.get(call_info.caller_endpoint.host) + old_id = call_info.caller_endpoint.host + voip_device = self.devices.get(old_id) if voip_device is not None: voip_device.voip_id = voip_id self.devices[voip_id] = voip_device dev_reg.async_update_device( voip_device.device_id, new_identifiers={(DOMAIN, voip_id)} ) + # Migrate entities + old_prefix = f"{old_id}-" + + def entity_migrator(entry: er.RegistryEntry) -> dict[str, Any] | None: + """Migrate entities.""" + if not entry.unique_id.startswith(old_prefix): + return None + key = entry.unique_id[len(old_prefix) :] + return { + "new_unique_id": f"{voip_id}-{key}", + } + + self.config_entry.async_create_task( + self.hass, + er.async_migrate_entries( + self.hass, self.config_entry.entry_id, entity_migrator + ), + f"voip migrating entities {voip_id}", + ) # Update device with latest info device = dev_reg.async_get_or_create( config_entry_id=self.config_entry.entry_id, identifiers={(DOMAIN, voip_id)}, - name=voip_id, + name=call_info.caller_endpoint.host, manufacturer=manuf, model=model, sw_version=fw_version, diff --git a/tests/components/voip/test_binary_sensor.py b/tests/components/voip/test_binary_sensor.py index 55d8ac4473c..44ac8e4d77f 100644 --- a/tests/components/voip/test_binary_sensor.py +++ b/tests/components/voip/test_binary_sensor.py @@ -22,18 +22,18 @@ async def test_call_in_progress( voip_device: VoIPDevice, ) -> None: """Test call in progress.""" - state = hass.states.get("binary_sensor.sip_192_168_1_210_5060_call_in_progress") + state = hass.states.get("binary_sensor.192_168_1_210_call_in_progress") assert state is not None assert state.state == "off" voip_device.set_is_active(True) - state = hass.states.get("binary_sensor.sip_192_168_1_210_5060_call_in_progress") + state = hass.states.get("binary_sensor.192_168_1_210_call_in_progress") assert state.state == "on" voip_device.set_is_active(False) - state = hass.states.get("binary_sensor.sip_192_168_1_210_5060_call_in_progress") + state = hass.states.get("binary_sensor.192_168_1_210_call_in_progress") assert state.state == "off" @@ -45,9 +45,9 @@ async def test_assist_in_progress_disabled_by_default( ) -> None: """Test assist in progress binary sensor is added disabled.""" - assert not hass.states.get("binary_sensor.sip_192_168_1_210_5060_call_in_progress") + assert not hass.states.get("binary_sensor.192_168_1_210_call_in_progress") entity_entry = entity_registry.async_get( - "binary_sensor.sip_192_168_1_210_5060_call_in_progress" + "binary_sensor.192_168_1_210_call_in_progress" ) assert entity_entry assert entity_entry.disabled @@ -63,7 +63,7 @@ async def test_assist_in_progress_issue( ) -> None: """Test assist in progress binary sensor.""" - call_in_progress_entity_id = "binary_sensor.sip_192_168_1_210_5060_call_in_progress" + call_in_progress_entity_id = "binary_sensor.192_168_1_210_call_in_progress" state = hass.states.get(call_in_progress_entity_id) assert state is not None @@ -96,7 +96,7 @@ async def test_assist_in_progress_repair_flow( ) -> None: """Test assist in progress binary sensor deprecation issue flow.""" - call_in_progress_entity_id = "binary_sensor.sip_192_168_1_210_5060_call_in_progress" + call_in_progress_entity_id = "binary_sensor.192_168_1_210_call_in_progress" state = hass.states.get(call_in_progress_entity_id) assert state is not None diff --git a/tests/components/voip/test_devices.py b/tests/components/voip/test_devices.py index d16ac76d290..4e2e129d4be 100644 --- a/tests/components/voip/test_devices.py +++ b/tests/components/voip/test_devices.py @@ -8,7 +8,7 @@ from voip_utils import CallInfo from homeassistant.components.voip import DOMAIN from homeassistant.components.voip.devices import VoIPDevice, VoIPDevices from homeassistant.core import HomeAssistant -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import MockConfigEntry @@ -27,7 +27,7 @@ async def test_device_registry_info( identifiers={(DOMAIN, call_info.caller_endpoint.uri)} ) assert device is not None - assert device.name == call_info.caller_endpoint.uri + assert device.name == call_info.caller_endpoint.host assert device.manufacturer == "Grandstream" assert device.model == "HT801" assert device.sw_version == "1.0.17.5" @@ -71,47 +71,61 @@ async def test_remove_device_registry_entry( ) -> None: """Test removing a device registry entry.""" assert voip_device.voip_id in voip_devices.devices - assert hass.states.get("switch.sip_192_168_1_210_5060_allow_calls") is not None + assert hass.states.get("switch.192_168_1_210_allow_calls") is not None device_registry.async_remove_device(voip_device.device_id) await hass.async_block_till_done() await hass.async_block_till_done() - assert hass.states.get("switch.sip_192_168_1_210_5060_allow_calls") is None + assert hass.states.get("switch.192_168_1_210_allow_calls") is None assert voip_device.voip_id not in voip_devices.devices @pytest.fixture async def legacy_dev_reg_entry( + entity_registry: er.EntityRegistry, device_registry: dr.DeviceRegistry, config_entry: MockConfigEntry, call_info: CallInfo, ) -> None: """Fixture to run before we set up the VoIP integration via fixture.""" - return device_registry.async_get_or_create( + device = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={(DOMAIN, call_info.caller_ip)}, ) + entity_registry.async_get_or_create( + "switch", + DOMAIN, + f"{call_info.caller_ip}-allow_calls", + device_id=device.id, + config_entry=config_entry, + ) + return device -async def test_device_registry_migation( +async def test_device_registry_migration( hass: HomeAssistant, legacy_dev_reg_entry: dr.DeviceEntry, voip_devices: VoIPDevices, call_info: CallInfo, + entity_registry: er.EntityRegistry, device_registry: dr.DeviceRegistry, ) -> None: """Test info in device registry migrates old devices.""" voip_device = voip_devices.async_get_or_create(call_info) - assert voip_device.voip_id == call_info.caller_endpoint.uri + new_id = call_info.caller_endpoint.uri + assert voip_device.voip_id == new_id - device = device_registry.async_get_device( - identifiers={(DOMAIN, call_info.caller_endpoint.uri)} - ) + device = device_registry.async_get_device(identifiers={(DOMAIN, new_id)}) assert device is not None assert device.id == legacy_dev_reg_entry.id - assert device.identifiers == {(DOMAIN, call_info.caller_endpoint.uri)} - assert device.name == call_info.caller_endpoint.uri + assert device.identifiers == {(DOMAIN, new_id)} + assert device.name == call_info.caller_endpoint.host assert device.manufacturer == "Grandstream" assert device.model == "HT801" assert device.sw_version == "1.0.17.5" + + assert ( + entity_registry.async_get_entity_id("switch", DOMAIN, f"{new_id}-allow_calls") + is not None + ) diff --git a/tests/components/voip/test_select.py b/tests/components/voip/test_select.py index 1b45c739535..78bb8d6c6b4 100644 --- a/tests/components/voip/test_select.py +++ b/tests/components/voip/test_select.py @@ -15,7 +15,7 @@ async def test_pipeline_select( Functionality is tested in assist_pipeline/test_select.py. This test is only to ensure it is set up. """ - state = hass.states.get("select.sip_192_168_1_210_5060_assistant") + state = hass.states.get("select.192_168_1_210_assistant") assert state is not None assert state.state == "preferred" @@ -30,6 +30,6 @@ async def test_vad_sensitivity_select( Functionality is tested in assist_pipeline/test_select.py. This test is only to ensure it is set up. """ - state = hass.states.get("select.sip_192_168_1_210_5060_finished_speaking_detection") + state = hass.states.get("select.192_168_1_210_finished_speaking_detection") assert state is not None assert state.state == "default" diff --git a/tests/components/voip/test_switch.py b/tests/components/voip/test_switch.py index ac331ed01a7..8b3cd03f2ac 100644 --- a/tests/components/voip/test_switch.py +++ b/tests/components/voip/test_switch.py @@ -13,41 +13,41 @@ async def test_allow_call( """Test allow call.""" assert not voip_device.async_allow_call(hass) - state = hass.states.get("switch.sip_192_168_1_210_5060_allow_calls") + state = hass.states.get("switch.192_168_1_210_allow_calls") assert state is not None assert state.state == "off" await hass.config_entries.async_reload(config_entry.entry_id) - state = hass.states.get("switch.sip_192_168_1_210_5060_allow_calls") + state = hass.states.get("switch.192_168_1_210_allow_calls") assert state.state == "off" await hass.services.async_call( "switch", "turn_on", - {"entity_id": "switch.sip_192_168_1_210_5060_allow_calls"}, + {"entity_id": "switch.192_168_1_210_allow_calls"}, blocking=True, ) assert voip_device.async_allow_call(hass) - state = hass.states.get("switch.sip_192_168_1_210_5060_allow_calls") + state = hass.states.get("switch.192_168_1_210_allow_calls") assert state.state == "on" await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() - state = hass.states.get("switch.sip_192_168_1_210_5060_allow_calls") + state = hass.states.get("switch.192_168_1_210_allow_calls") assert state.state == "on" await hass.services.async_call( "switch", "turn_off", - {"entity_id": "switch.sip_192_168_1_210_5060_allow_calls"}, + {"entity_id": "switch.192_168_1_210_allow_calls"}, blocking=True, ) assert not voip_device.async_allow_call(hass) - state = hass.states.get("switch.sip_192_168_1_210_5060_allow_calls") + state = hass.states.get("switch.192_168_1_210_allow_calls") assert state.state == "off"