Add stale device cleanup to Teslemetry (#144523)

This commit is contained in:
Brett Adams
2025-08-11 21:54:44 +10:00
committed by GitHub
parent d54f979612
commit 9595759fd1
2 changed files with 115 additions and 1 deletions

View File

@@ -97,6 +97,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
# Create the stream
stream: TeslemetryStream | None = None
# Remember each device identifier we create
current_devices: set[tuple[str, str]] = set()
for product in products:
if (
"vin" in product
@@ -116,6 +119,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
model=api.model,
serial_number=vin,
)
current_devices.add((DOMAIN, vin))
# Create stream if required
if not stream:
@@ -171,6 +175,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
name=product.get("site_name", "Energy Site"),
serial_number=str(site_id),
)
current_devices.add((DOMAIN, str(site_id)))
if wall_connector:
for connector in product["components"]["wall_connectors"]:
current_devices.add((DOMAIN, connector["din"]))
# Check live status endpoint works before creating its coordinator
try:
@@ -235,6 +244,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
config_entry_id=entry.entry_id, **energysite.device
)
# Remove devices that are no longer present
for device_entry in dr.async_entries_for_config_entry(
device_registry, entry.entry_id
):
if not any(
identifier in current_devices for identifier in device_entry.identifiers
):
LOGGER.debug("Removing stale device %s", device_entry.id)
device_registry.async_update_device(
device_id=device_entry.id,
remove_config_entry_id=entry.entry_id,
)
# Setup Platforms
entry.runtime_data = TeslemetryData(vehicles, energysites, scopes, stream)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

View File

@@ -1,6 +1,6 @@
"""Test the Teslemetry init."""
from unittest.mock import AsyncMock
from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory
import pytest
@@ -11,6 +11,7 @@ from tesla_fleet_api.exceptions import (
TeslaFleetError,
)
from homeassistant.components.teslemetry.const import DOMAIN
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
from homeassistant.components.teslemetry.models import TeslemetryData
from homeassistant.config_entries import ConfigEntryState
@@ -187,3 +188,94 @@ async def test_modern_no_poll(
assert mock_vehicle_data.called is False
freezer.tick(VEHICLE_INTERVAL)
assert mock_vehicle_data.called is False
async def test_stale_device_removal(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
mock_products: AsyncMock,
) -> None:
"""Test removal of stale devices."""
# Setup the entry first to get a valid config_entry_id
entry = await setup_platform(hass)
# Create a device that should be removed (with the valid entry_id)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, "stale-vin")},
manufacturer="Tesla",
name="Stale Vehicle",
)
# Verify the stale device exists
pre_devices = dr.async_entries_for_config_entry(device_registry, entry.entry_id)
stale_identifiers = {
identifier for device in pre_devices for identifier in device.identifiers
}
assert (DOMAIN, "stale-vin") in stale_identifiers
# Update products with an empty response (no devices) and reload entry
with patch(
"tesla_fleet_api.teslemetry.Teslemetry.products",
return_value={"response": []},
):
await hass.config_entries.async_reload(entry.entry_id)
await hass.async_block_till_done()
# Get updated devices after reload
post_devices = dr.async_entries_for_config_entry(
device_registry, entry.entry_id
)
post_identifiers = {
identifier for device in post_devices for identifier in device.identifiers
}
# Verify the stale device has been removed
assert (DOMAIN, "stale-vin") not in post_identifiers
# Verify the device itself has been completely removed from the registry
# since it had no other config entries
updated_device = device_registry.async_get_device(
identifiers={(DOMAIN, "stale-vin")}
)
assert updated_device is None
async def test_device_retention_during_reload(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
mock_products: AsyncMock,
) -> None:
"""Test that valid devices are retained during a config entry reload."""
# Setup entry with normal devices
entry = await setup_platform(hass)
# Get initial device count and identifiers
pre_devices = dr.async_entries_for_config_entry(device_registry, entry.entry_id)
pre_count = len(pre_devices)
pre_identifiers = {
identifier for device in pre_devices for identifier in device.identifiers
}
# Make sure we have some devices
assert pre_count > 0
# Save the original identifiers to compare after reload
original_identifiers = pre_identifiers.copy()
# Reload the config entry with the same products data
# The mock_products fixture will return the same data as during setup
await hass.config_entries.async_reload(entry.entry_id)
await hass.async_block_till_done()
# Verify device count and identifiers after reload match pre-reload
post_devices = dr.async_entries_for_config_entry(device_registry, entry.entry_id)
post_count = len(post_devices)
post_identifiers = {
identifier for device in post_devices for identifier in device.identifiers
}
# Since the products data didn't change, we should have the same devices
assert post_count == pre_count
assert post_identifiers == original_identifiers