From 1091a089b426f04c67c3c62fd39356bdb67d28fc Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Sat, 18 Apr 2026 00:43:45 +0200 Subject: [PATCH] Allow removing devices that are no longer available in fjaraskupan (#167937) --- .../components/fjaraskupan/__init__.py | 15 ++++ tests/components/fjaraskupan/test_init.py | 90 +++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 tests/components/fjaraskupan/test_init.py diff --git a/homeassistant/components/fjaraskupan/__init__.py b/homeassistant/components/fjaraskupan/__init__.py index 961be04fd8d5..f086bc3dd23c 100644 --- a/homeassistant/components/fjaraskupan/__init__.py +++ b/homeassistant/components/fjaraskupan/__init__.py @@ -12,6 +12,7 @@ from homeassistant.components.bluetooth import ( BluetoothChange, BluetoothScanningMode, BluetoothServiceInfoBleak, + async_discovered_service_info, async_rediscover_address, async_register_callback, ) @@ -131,3 +132,17 @@ async def async_unload_entry( async_rediscover_address(hass, conn[1]) return unload_ok + + +async def async_remove_config_entry_device( + hass: HomeAssistant, + config_entry: FjaraskupanConfigEntry, + device_entry: dr.DeviceEntry, +) -> bool: + """Remove a config entry from a device.""" + for service_info in async_discovered_service_info(hass, False): + if (DOMAIN, service_info.address) in device_entry.identifiers: + return False + + # No matching service info, so allow removal. + return True diff --git a/tests/components/fjaraskupan/test_init.py b/tests/components/fjaraskupan/test_init.py new file mode 100644 index 000000000000..c6fe1090394d --- /dev/null +++ b/tests/components/fjaraskupan/test_init.py @@ -0,0 +1,90 @@ +"""Test the Fjäråskupan integration init.""" + +from fjaraskupan import ANNOUNCE_MANUFACTURER, DEVICE_NAME +from habluetooth import BluetoothServiceInfo + +from homeassistant.components.fjaraskupan.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry +from tests.components.bluetooth import ( + inject_bluetooth_service_info, + patch_discovered_devices, +) +from tests.typing import WebSocketGenerator + +MOCK_SERVICE_INFO = BluetoothServiceInfo( + address="11:11:11:11:11:11", + name=DEVICE_NAME, + service_uuids=[], + rssi=-60, + manufacturer_data={ANNOUNCE_MANUFACTURER: b"ODFJAR\x01\x02\x00\x00\x00\x30\x04"}, + service_data={}, + source="local", +) + + +async def test_setup( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, +) -> None: + """Test setup creates expected device.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={}, + ) + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) is True + + inject_bluetooth_service_info( + hass, + MOCK_SERVICE_INFO, + ) + + await hass.async_block_till_done() + device_entry = device_registry.async_get_device( + identifiers={(DOMAIN, MOCK_SERVICE_INFO.address)} + ) + assert device_entry is not None + assert device_entry.manufacturer == "Fjäråskupan" + assert device_entry.name == "Fjäråskupan" + + +async def test_remove_device( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + hass_ws_client: WebSocketGenerator, +) -> None: + """Test we can remove devices that are not available.""" + assert await async_setup_component(hass, "config", {}) + + config_entry = MockConfigEntry( + domain=DOMAIN, + data={}, + ) + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) is True + + inject_bluetooth_service_info(hass, MOCK_SERVICE_INFO) + + await hass.async_block_till_done() + device_entry = device_registry.async_get_device( + identifiers={(DOMAIN, MOCK_SERVICE_INFO.address)} + ) + assert device_entry + + client = await hass_ws_client(hass) + response = await client.remove_device(device_entry.id, config_entry.entry_id) + assert not response["success"] + + await hass.async_block_till_done() + assert device_registry.async_get(device_entry.id) + + with patch_discovered_devices([]): + response = await client.remove_device(device_entry.id, config_entry.entry_id) + assert response["success"] + + await hass.async_block_till_done() + assert not device_registry.async_get(device_entry.id)