Fix data in old SkyConnect integration config entries or delete them (#141959)

* Delete old SkyConnect integration config entries

* Try migrating, if possible

* Do not delete config entries, log a failure
This commit is contained in:
puddly 2025-04-01 14:27:06 -04:00 committed by Franck Nijhof
parent a7c43f9b49
commit 2b9c903429
No known key found for this signature in database
GPG Key ID: D62583BA8AB11CA3
3 changed files with 166 additions and 7 deletions

View File

@ -6,14 +6,29 @@ import logging
import os.path
from homeassistant.components.homeassistant_hardware.util import guess_firmware_info
from homeassistant.components.usb import USBDevice, async_register_port_event_callback
from homeassistant.components.usb import (
USBDevice,
async_register_port_event_callback,
scan_serial_ports,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType
from .const import DESCRIPTION, DEVICE, DOMAIN, FIRMWARE, FIRMWARE_VERSION, PRODUCT
from .const import (
DESCRIPTION,
DEVICE,
DOMAIN,
FIRMWARE,
FIRMWARE_VERSION,
MANUFACTURER,
PID,
PRODUCT,
SERIAL_NUMBER,
VID,
)
_LOGGER = logging.getLogger(__name__)
@ -73,7 +88,7 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
"""Migrate old entry."""
_LOGGER.debug(
"Migrating from version %s:%s", config_entry.version, config_entry.minor_version
"Migrating from version %s.%s", config_entry.version, config_entry.minor_version
)
if config_entry.version == 1:
@ -108,6 +123,43 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
minor_version=3,
)
if config_entry.minor_version == 3:
# Old SkyConnect config entries were missing keys
if any(
key not in config_entry.data
for key in (VID, PID, MANUFACTURER, PRODUCT, SERIAL_NUMBER)
):
serial_ports = await hass.async_add_executor_job(scan_serial_ports)
serial_ports_info = {port.device: port for port in serial_ports}
device = config_entry.data[DEVICE]
if not (usb_info := serial_ports_info.get(device)):
raise HomeAssistantError(
f"USB device {device} is missing, cannot migrate"
)
hass.config_entries.async_update_entry(
config_entry,
data={
**config_entry.data,
VID: usb_info.vid,
PID: usb_info.pid,
MANUFACTURER: usb_info.manufacturer,
PRODUCT: usb_info.description,
DESCRIPTION: usb_info.description,
SERIAL_NUMBER: usb_info.serial_number,
},
version=1,
minor_version=4,
)
else:
# Existing entries are migrated by just incrementing the version
hass.config_entries.async_update_entry(
config_entry,
version=1,
minor_version=4,
)
_LOGGER.debug(
"Migration to version %s.%s successful",
config_entry.version,

View File

@ -81,7 +81,7 @@ class HomeAssistantSkyConnectConfigFlow(
"""Handle a config flow for Home Assistant SkyConnect."""
VERSION = 1
MINOR_VERSION = 3
MINOR_VERSION = 4
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Initialize the config flow."""

View File

@ -9,7 +9,15 @@ from homeassistant.components.homeassistant_hardware.util import (
ApplicationType,
FirmwareInfo,
)
from homeassistant.components.homeassistant_sky_connect.const import DOMAIN
from homeassistant.components.homeassistant_sky_connect.const import (
DESCRIPTION,
DOMAIN,
MANUFACTURER,
PID,
PRODUCT,
SERIAL_NUMBER,
VID,
)
from homeassistant.components.usb import USBDevice
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
@ -57,7 +65,7 @@ async def test_config_entry_migration_v2(hass: HomeAssistant) -> None:
await hass.config_entries.async_setup(config_entry.entry_id)
assert config_entry.version == 1
assert config_entry.minor_version == 3
assert config_entry.minor_version == 4
assert config_entry.data == {
"description": "SkyConnect v1.0",
"device": "/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_9e2adbd75b8beb119fe564a0f320645d-if00-port0",
@ -187,3 +195,102 @@ async def test_usb_device_reactivity(hass: HomeAssistant) -> None:
# The integration has reloaded and is now in a failed state
await hass.async_block_till_done(wait_background_tasks=True)
assert config_entry.state is ConfigEntryState.SETUP_RETRY
async def test_bad_config_entry_fixing(hass: HomeAssistant) -> None:
"""Test fixing/deleting config entries with bad data."""
# Newly-added ZBT-1
new_entry = MockConfigEntry(
domain=DOMAIN,
unique_id="some_unique_id-9e2adbd75b8beb119fe564a0f320645d",
data={
"device": "/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_9e2adbd75b8beb119fe564a0f320645d-if00-port0",
"vid": "10C4",
"pid": "EA60",
"serial_number": "9e2adbd75b8beb119fe564a0f320645d",
"manufacturer": "Nabu Casa",
"product": "SkyConnect v1.0",
"firmware": "ezsp",
"firmware_version": "7.4.4.0 (build 123)",
},
version=1,
minor_version=3,
)
new_entry.add_to_hass(hass)
# Old config entry, without firmware info
old_entry = MockConfigEntry(
domain=DOMAIN,
unique_id="some_unique_id-3c0ed67c628beb11b1cd64a0f320645d",
data={
"device": "/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_3c0ed67c628beb11b1cd64a0f320645d-if00-port0",
"vid": "10C4",
"pid": "EA60",
"serial_number": "3c0ed67c628beb11b1cd64a0f320645d",
"manufacturer": "Nabu Casa",
"description": "SkyConnect v1.0",
},
version=1,
minor_version=1,
)
old_entry.add_to_hass(hass)
# Bad config entry, missing most keys
bad_entry = MockConfigEntry(
domain=DOMAIN,
unique_id="some_unique_id-9f6c4bba657cc9a4f0cea48bc5948562",
data={
"device": "/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_9f6c4bba657cc9a4f0cea48bc5948562-if00-port0",
},
version=1,
minor_version=2,
)
bad_entry.add_to_hass(hass)
# Bad config entry, missing most keys, but fixable since the device is present
fixable_entry = MockConfigEntry(
domain=DOMAIN,
unique_id="some_unique_id-4f5f3b26d59f8714a78b599690741999",
data={
"device": "/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_4f5f3b26d59f8714a78b599690741999-if00-port0",
},
version=1,
minor_version=2,
)
fixable_entry.add_to_hass(hass)
with patch(
"homeassistant.components.homeassistant_sky_connect.scan_serial_ports",
return_value=[
USBDevice(
device="/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_4f5f3b26d59f8714a78b599690741999-if00-port0",
vid="10C4",
pid="EA60",
serial_number="4f5f3b26d59f8714a78b599690741999",
manufacturer="Nabu Casa",
description="SkyConnect v1.0",
)
],
):
await async_setup_component(hass, "homeassistant_sky_connect", {})
assert hass.config_entries.async_get_entry(new_entry.entry_id) is not None
assert hass.config_entries.async_get_entry(old_entry.entry_id) is not None
assert hass.config_entries.async_get_entry(fixable_entry.entry_id) is not None
updated_entry = hass.config_entries.async_get_entry(fixable_entry.entry_id)
assert updated_entry is not None
assert updated_entry.data[VID] == "10C4"
assert updated_entry.data[PID] == "EA60"
assert updated_entry.data[SERIAL_NUMBER] == "4f5f3b26d59f8714a78b599690741999"
assert updated_entry.data[MANUFACTURER] == "Nabu Casa"
assert updated_entry.data[PRODUCT] == "SkyConnect v1.0"
assert updated_entry.data[DESCRIPTION] == "SkyConnect v1.0"
untouched_bad_entry = hass.config_entries.async_get_entry(bad_entry.entry_id)
assert untouched_bad_entry.minor_version == 3