Cleanup stale devices on incomfort integration startup (#136566)

This commit is contained in:
Jan Bouwhuis 2025-01-26 21:57:32 +01:00 committed by GitHub
parent 7133eec185
commit 3e0f6562c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 97 additions and 5 deletions

View File

@ -7,12 +7,12 @@ from incomfortclient import InvalidGateway, InvalidHeaterList
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 ConfigEntryAuthFailed
from homeassistant.helpers import device_registry as dr
from .const import DOMAIN
from .coordinator import InComfortDataCoordinator, async_connect_gateway
from .coordinator import InComfortData, InComfortDataCoordinator, async_connect_gateway
from .errors import InComfortTimeout, InComfortUnknownError, NoHeaters, NotFound
PLATFORMS = (
@ -27,6 +27,43 @@ INTEGRATION_TITLE = "Intergas InComfort/Intouch Lan2RF gateway"
type InComfortConfigEntry = ConfigEntry[InComfortDataCoordinator]
@callback
def async_cleanup_stale_devices(
hass: HomeAssistant,
entry: InComfortConfigEntry,
data: InComfortData,
gateway_device: dr.DeviceEntry,
) -> None:
"""Cleanup stale heater devices and climates."""
heater_serial_numbers = {heater.serial_no for heater in data.heaters}
device_registry = dr.async_get(hass)
device_entries = device_registry.devices.get_devices_for_config_entry_id(
entry.entry_id
)
stale_heater_serial_numbers: list[str] = [
device_entry.serial_number
for device_entry in device_entries
if device_entry.id != gateway_device.id
and device_entry.serial_number is not None
and device_entry.serial_number not in heater_serial_numbers
]
if not stale_heater_serial_numbers:
return
cleanup_devices: list[str] = []
# Find stale heater and climate devices
for serial_number in stale_heater_serial_numbers:
cleanup_list = [f"{serial_number}_{index}" for index in range(1, 4)]
cleanup_list.append(serial_number)
cleanup_identifiers = [{(DOMAIN, cleanup_id)} for cleanup_id in cleanup_list]
cleanup_devices.extend(
device_entry.id
for device_entry in device_entries
if device_entry.identifiers in cleanup_identifiers
)
for device_id in cleanup_devices:
device_registry.async_remove_device(device_id)
async def async_setup_entry(hass: HomeAssistant, entry: InComfortConfigEntry) -> bool:
"""Set up a config entry."""
try:
@ -46,7 +83,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: InComfortConfigEntry) ->
# Register discovered gateway device
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
gateway_device = device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, entry.entry_id)},
connections={(dr.CONNECTION_NETWORK_MAC, entry.unique_id)}
@ -55,6 +92,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: InComfortConfigEntry) ->
manufacturer="Intergas",
name="RFGateway",
)
async_cleanup_stale_devices(hass, entry, data, gateway_device)
coordinator = InComfortDataCoordinator(hass, data, entry.entry_id)
entry.runtime_data = coordinator
await coordinator.async_config_entry_first_refresh()

View File

@ -1,6 +1,7 @@
"""Tests for Intergas InComfort integration."""
from datetime import timedelta
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
from aiohttp import ClientResponseError, RequestInfo
@ -8,13 +9,17 @@ from freezegun.api import FrozenDateTimeFactory
from incomfortclient import InvalidGateway, InvalidHeaterList
import pytest
from homeassistant.components.incomfort import DOMAIN
from homeassistant.components.incomfort.coordinator import UPDATE_INTERVAL
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.device_registry import DeviceRegistry
from tests.common import async_fire_time_changed
from .conftest import MOCK_HEATER_STATUS
from tests.common import MockConfigEntry, async_fire_time_changed
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
@ -22,13 +27,62 @@ async def test_setup_platforms(
hass: HomeAssistant,
mock_incomfort: MagicMock,
entity_registry: er.EntityRegistry,
mock_config_entry: ConfigEntry,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the incomfort integration is set up correctly."""
await hass.config_entries.async_setup(mock_config_entry.entry_id)
assert mock_config_entry.state is ConfigEntryState.LOADED
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
@pytest.mark.parametrize(
"mock_heater_status", [MOCK_HEATER_STATUS | {"serial_no": "c01d00c0ffee"}]
)
async def test_stale_devices_cleanup(
hass: HomeAssistant,
device_registry: DeviceRegistry,
mock_incomfort: MagicMock,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
mock_heater_status: dict[str, Any],
) -> None:
"""Test the incomfort integration is cleaning up stale devices."""
# Setup an old heater with serial_no c01d00c0ffee
await hass.config_entries.async_setup(mock_config_entry.entry_id)
assert mock_config_entry.state is ConfigEntryState.LOADED
await hass.config_entries.async_unload(mock_config_entry.entry_id)
old_entries = device_registry.devices.get_devices_for_config_entry_id(
mock_config_entry.entry_id
)
assert len(old_entries) == 3
old_heater = device_registry.async_get_device({(DOMAIN, "c01d00c0ffee")})
assert old_heater is not None
assert old_heater.serial_number == "c01d00c0ffee"
old_climate = device_registry.async_get_device({(DOMAIN, "c01d00c0ffee_1")})
assert old_heater is not None
old_climate = device_registry.async_get_device({(DOMAIN, "c01d00c0ffee_1")})
assert old_climate is not None
mock_heater_status["serial_no"] = "c0ffeec0ffee"
await hass.config_entries.async_setup(mock_config_entry.entry_id)
assert mock_config_entry.state is ConfigEntryState.LOADED
new_entries = device_registry.devices.get_devices_for_config_entry_id(
mock_config_entry.entry_id
)
assert len(new_entries) == 3
new_heater = device_registry.async_get_device({(DOMAIN, "c0ffeec0ffee")})
assert new_heater is not None
assert new_heater.serial_number == "c0ffeec0ffee"
new_climate = device_registry.async_get_device({(DOMAIN, "c0ffeec0ffee_1")})
assert new_climate is not None
old_heater = device_registry.async_get_device({(DOMAIN, "c01d00c0ffee")})
assert old_heater is None
old_climate = device_registry.async_get_device({(DOMAIN, "c01d00c0ffee_1")})
assert old_climate is None
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_coordinator_updates(
hass: HomeAssistant,