Fix offline devices being removed in Xbox integration (#181045)

This commit is contained in:
Manu
2026-09-02 10:01:34 +00:00
committed by Franck Nijhof
parent 2c2631d29a
commit 8f57e2b81b
3 changed files with 42 additions and 29 deletions
+21
View File
@@ -88,6 +88,27 @@ async def async_unload_entry(hass: HomeAssistant, entry: XboxConfigEntry) -> boo
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_remove_config_entry_device(
hass: HomeAssistant,
config_entry: XboxConfigEntry,
device_entry: dr.AnyDeviceEntry,
) -> bool:
"""Remove a stale device from a config entry."""
return not any(
identifier
for identifier in device_entry.identifiers
if identifier[0] == DOMAIN
and (
(
isinstance(device_entry, dr.DeviceEntry)
and device_entry.entry_type == dr.DeviceEntryType.SERVICE
)
or identifier[1] in config_entry.runtime_data.consoles.data
)
)
async def async_migrate_entry(hass: HomeAssistant, entry: XboxConfigEntry) -> bool:
"""Migrate config entry."""
@@ -20,8 +20,6 @@ from pythonxbox.api.provider.titlehub.models import Title
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN
@@ -117,18 +115,6 @@ class XboxConsolesCoordinator(XboxBaseCoordinator[dict[str, SmartglassConsole]])
"Found %d consoles: %s", len(consoles.result), consoles.model_dump()
)
device_reg = dr.async_get(self.hass)
identifiers = {(DOMAIN, console.id) for console in consoles.result}
for device in dr.async_entries_for_config_entry(
device_reg, self.config_entry.entry_id
):
if (
device.entry_type is not DeviceEntryType.SERVICE
and not set(device.identifiers) & identifiers
):
_LOGGER.debug("Removing stale device %s", device.name)
device_reg.async_remove_device(device.id)
return {console.id: console for console in consoles.result}
+21 -15
View File
@@ -23,12 +23,14 @@ from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.config_entry_oauth2_flow import (
ImplementationUnavailableError,
)
from homeassistant.setup import async_setup_component
from tests.common import (
MockConfigEntry,
async_fire_time_changed,
async_load_json_object_fixture,
)
from tests.typing import WebSocketGenerator
@pytest.mark.usefixtures("xbox_live_client")
@@ -204,9 +206,11 @@ async def test_dynamic_devices(
xbox_live_client: AsyncMock,
device_registry: dr.DeviceRegistry,
freezer: FrozenDateTimeFactory,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test adding of new and removal of stale devices at runtime."""
assert await async_setup_component(hass, "config", {})
client = await hass_ws_client(hass)
xbox_live_client.smartglass.get_console_list.return_value = SmartglassConsoleList(
**await async_load_json_object_fixture(
hass, "smartglass_console_list_empty.json", DOMAIN
@@ -225,12 +229,6 @@ async def test_dynamic_devices(
)
is None
)
assert (
device_registry.async_get_device_by_identifier(
(DOMAIN, "HIJKLMN"), config_entry.entry_id
)
is None
)
xbox_live_client.smartglass.get_console_list.return_value = SmartglassConsoleList(
**await async_load_json_object_fixture(
@@ -242,13 +240,15 @@ async def test_dynamic_devices(
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert device_registry.async_get_device_by_identifier(
(DOMAIN, "ABCDEFG"), config_entry.entry_id
)
assert device_registry.async_get_device_by_identifier(
(DOMAIN, "HIJKLMN"), config_entry.entry_id
assert (
device := device_registry.async_get_device_by_identifier(
(DOMAIN, "ABCDEFG"), config_entry.entry_id
)
)
response = await client.remove_device(device.id)
assert not response["success"]
xbox_live_client.smartglass.get_console_list.return_value = SmartglassConsoleList(
**await async_load_json_object_fixture(
hass, "smartglass_console_list_empty.json", DOMAIN
@@ -259,15 +259,21 @@ async def test_dynamic_devices(
async_fire_time_changed(hass)
await hass.async_block_till_done()
response = await client.remove_device(device.id)
assert response["success"]
assert (
device_registry.async_get_device_by_identifier(
(DOMAIN, "ABCDEFG"), config_entry.entry_id
)
is None
)
# Test that service devices cannot be removed
assert (
device_registry.async_get_device_by_identifier(
(DOMAIN, "HIJKLMN"), config_entry.entry_id
account := device_registry.async_get_device_by_identifier(
(DOMAIN, "271958441785640"), config_entry.entry_id
)
is None
)
response = await client.remove_device(account.id)
assert not response["success"]