diff --git a/homeassistant/components/renault/__init__.py b/homeassistant/components/renault/__init__.py index eecf1354134..48bab1f5c8b 100644 --- a/homeassistant/components/renault/__init__.py +++ b/homeassistant/components/renault/__init__.py @@ -7,7 +7,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.typing import ConfigType from .const import CONF_LOCALE, DOMAIN, PLATFORMS @@ -56,3 +56,12 @@ async def async_unload_entry( ) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) + + +async def async_remove_config_entry_device( + hass: HomeAssistant, config_entry: RenaultConfigEntry, device_entry: dr.DeviceEntry +) -> bool: + """Remove a config entry from a device.""" + return not device_entry.identifiers.intersection( + (DOMAIN, vin) for vin in config_entry.runtime_data.vehicles + ) diff --git a/tests/components/renault/test_init.py b/tests/components/renault/test_init.py index e6c55f99810..5b67d9e31f9 100644 --- a/tests/components/renault/test_init.py +++ b/tests/components/renault/test_init.py @@ -11,6 +11,10 @@ from renault_api.gigya.exceptions import GigyaException, InvalidCredentialsExcep from homeassistant.components.renault.const import DOMAIN from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr +from homeassistant.setup import async_setup_component + +from tests.typing import WebSocketGenerator @pytest.fixture(autouse=True) @@ -108,3 +112,48 @@ async def test_setup_entry_missing_vehicle_details( assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert config_entry.state is ConfigEntryState.SETUP_RETRY + + +@pytest.mark.usefixtures("patch_renault_account", "patch_get_vehicles") +@pytest.mark.parametrize("vehicle_type", ["zoe_40"], indirect=True) +async def test_registry_cleanup( + hass: HomeAssistant, + config_entry: ConfigEntry, + hass_ws_client: WebSocketGenerator, +) -> None: + """Test being able to remove a disconnected device.""" + assert await async_setup_component(hass, "config", {}) + entry_id = config_entry.entry_id + device_registry = dr.async_get(hass) + live_id = "VF1AAAAA555777999" + dead_id = "VF1AAAAA555777888" + + assert len(dr.async_entries_for_config_entry(device_registry, entry_id)) == 0 + device_registry.async_get_or_create( + config_entry_id=entry_id, + identifiers={(DOMAIN, dead_id)}, + manufacturer="Renault", + model="Zoe", + name="REGISTRATION-NUMBER", + sw_version="X101VE", + ) + assert len(dr.async_entries_for_config_entry(device_registry, entry_id)) == 1 + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert len(dr.async_entries_for_config_entry(device_registry, entry_id)) == 2 + + # Try to remove "VF1AAAAA555777999" - fails as it is live + device = device_registry.async_get_device(identifiers={(DOMAIN, live_id)}) + client = await hass_ws_client(hass) + response = await client.remove_device(device.id, entry_id) + assert not response["success"] + assert len(dr.async_entries_for_config_entry(device_registry, entry_id)) == 2 + assert device_registry.async_get_device(identifiers={(DOMAIN, live_id)}) is not None + + # Try to remove "VF1AAAAA555777888" - succeeds as it is dead + device = device_registry.async_get_device(identifiers={(DOMAIN, dead_id)}) + response = await client.remove_device(device.id, entry_id) + assert response["success"] + assert len(dr.async_entries_for_config_entry(device_registry, entry_id)) == 1 + assert device_registry.async_get_device(identifiers={(DOMAIN, dead_id)}) is None