mirror of
https://github.com/home-assistant/core.git
synced 2025-09-08 06:11:31 +02:00
Add stale device cleanup to Teslemetry (#144523)
This commit is contained in:
@@ -97,6 +97,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
|
|||||||
# Create the stream
|
# Create the stream
|
||||||
stream: TeslemetryStream | None = None
|
stream: TeslemetryStream | None = None
|
||||||
|
|
||||||
|
# Remember each device identifier we create
|
||||||
|
current_devices: set[tuple[str, str]] = set()
|
||||||
|
|
||||||
for product in products:
|
for product in products:
|
||||||
if (
|
if (
|
||||||
"vin" in product
|
"vin" in product
|
||||||
@@ -116,6 +119,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
|
|||||||
model=api.model,
|
model=api.model,
|
||||||
serial_number=vin,
|
serial_number=vin,
|
||||||
)
|
)
|
||||||
|
current_devices.add((DOMAIN, vin))
|
||||||
|
|
||||||
# Create stream if required
|
# Create stream if required
|
||||||
if not stream:
|
if not stream:
|
||||||
@@ -171,6 +175,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
|
|||||||
name=product.get("site_name", "Energy Site"),
|
name=product.get("site_name", "Energy Site"),
|
||||||
serial_number=str(site_id),
|
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
|
# Check live status endpoint works before creating its coordinator
|
||||||
try:
|
try:
|
||||||
@@ -235,6 +244,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
|
|||||||
config_entry_id=entry.entry_id, **energysite.device
|
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
|
# Setup Platforms
|
||||||
entry.runtime_data = TeslemetryData(vehicles, energysites, scopes, stream)
|
entry.runtime_data = TeslemetryData(vehicles, energysites, scopes, stream)
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
"""Test the Teslemetry init."""
|
"""Test the Teslemetry init."""
|
||||||
|
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
import pytest
|
||||||
@@ -11,6 +11,7 @@ from tesla_fleet_api.exceptions import (
|
|||||||
TeslaFleetError,
|
TeslaFleetError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from homeassistant.components.teslemetry.const import DOMAIN
|
||||||
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
|
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
|
||||||
from homeassistant.components.teslemetry.models import TeslemetryData
|
from homeassistant.components.teslemetry.models import TeslemetryData
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
@@ -187,3 +188,94 @@ async def test_modern_no_poll(
|
|||||||
assert mock_vehicle_data.called is False
|
assert mock_vehicle_data.called is False
|
||||||
freezer.tick(VEHICLE_INTERVAL)
|
freezer.tick(VEHICLE_INTERVAL)
|
||||||
assert mock_vehicle_data.called is False
|
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
|
||||||
|
Reference in New Issue
Block a user