mirror of
https://github.com/home-assistant/core.git
synced 2026-10-07 14:58:11 -04:00
Raise a LIFX repair issue for an entry that cannot be migrated (#184191)
Signed-off-by: Avi Miller <me@dje.li> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
71a74f6d8a
commit
6f8a8cefc3
@@ -7,7 +7,7 @@ from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import config_validation as cv, issue_registry as ir
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import CONF_SERIAL, DATA_LIFX_MANAGER, DOMAIN, LOGGER
|
||||
@@ -55,6 +55,11 @@ PLATFORMS = [
|
||||
]
|
||||
|
||||
|
||||
def invalid_serial_issue_id(entry_id: str) -> str:
|
||||
"""Return the repair issue ID for an entry whose serial cannot be migrated."""
|
||||
return f"invalid_serial_{entry_id}"
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the LIFX component."""
|
||||
async_setup_services(hass)
|
||||
@@ -80,6 +85,19 @@ async def async_migrate_entry(hass: HomeAssistant, entry: LIFXConfigEntry) -> bo
|
||||
except ValueError as err:
|
||||
# Nothing can make the entry usable, so it is left for the user to
|
||||
# remove rather than retried
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
invalid_serial_issue_id(entry.entry_id),
|
||||
data={"entry_id": entry.entry_id},
|
||||
is_fixable=True,
|
||||
severity=ir.IssueSeverity.ERROR,
|
||||
translation_key="invalid_serial",
|
||||
translation_placeholders={
|
||||
"title": entry.title,
|
||||
"unique_id": entry.unique_id,
|
||||
},
|
||||
)
|
||||
raise ConfigEntryError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_serial",
|
||||
@@ -137,3 +155,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: LIFXConfigEntry) -> boo
|
||||
# is the usual reason an entry is being unloaded in the first place
|
||||
LOGGER.debug("Could not stop the effects running on %s: %s", entry.title, err)
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_remove_entry(hass: HomeAssistant, entry: LIFXConfigEntry) -> None:
|
||||
"""Remove the repair issue raised for an entry that could not be migrated."""
|
||||
ir.async_delete_issue(hass, DOMAIN, invalid_serial_issue_id(entry.entry_id))
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Repairs for LIFX."""
|
||||
|
||||
from typing import override
|
||||
|
||||
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlowResult
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
class InvalidSerialRepairFlow(ConfirmRepairFlow):
|
||||
"""Remove an entry whose serial cannot be migrated."""
|
||||
|
||||
def __init__(self, entry_id: str) -> None:
|
||||
"""Initialize the flow."""
|
||||
self.entry_id = entry_id
|
||||
|
||||
@override
|
||||
async def async_step_confirm(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> RepairsFlowResult:
|
||||
"""Remove the entry once the user confirms."""
|
||||
# The entry may have been deleted by hand while this flow was open
|
||||
if (
|
||||
user_input is not None
|
||||
and self.hass.config_entries.async_get_entry(self.entry_id) is not None
|
||||
):
|
||||
await self.hass.config_entries.async_remove(self.entry_id)
|
||||
return await super().async_step_confirm(user_input)
|
||||
|
||||
|
||||
async def async_create_fix_flow(
|
||||
hass: HomeAssistant,
|
||||
issue_id: str,
|
||||
data: dict[str, str | int | float | None] | None,
|
||||
) -> InvalidSerialRepairFlow:
|
||||
"""Create a fix flow for an invalid serial issue."""
|
||||
assert data is not None
|
||||
return InvalidSerialRepairFlow(str(data["entry_id"]))
|
||||
@@ -104,6 +104,19 @@
|
||||
"message": "Error fetching the state of the LIFX device: {error}"
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"invalid_serial": {
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Home Assistant cannot migrate the LIFX entry \"{title}\" because its stored identifier \"{unique_id}\" is not a valid LIFX serial number.\n\nClick **Submit** to delete the entry then either wait for it to be rediscovered automatically or add it manually.",
|
||||
"title": "[%key:component::lifx::issues::invalid_serial::title%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "LIFX entry {title} cannot be migrated"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"effect_colorloop": {
|
||||
"description": "Runs an effect with looping colors.",
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
"""Tests for LIFX repairs."""
|
||||
|
||||
from homeassistant.components.lifx import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import IP_ADDRESS
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.components.repairs import process_repair_fix_flow, start_repair_fix_flow
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
MALFORMED_UNIQUE_ID = "gg:bb:cc:dd:ee:cc"
|
||||
|
||||
|
||||
async def _setup_malformed_entry(hass: HomeAssistant) -> MockConfigEntry:
|
||||
"""Set up an entry whose stored serial cannot be migrated."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="Kitchen",
|
||||
version=1,
|
||||
unique_id=MALFORMED_UNIQUE_ID,
|
||||
data={CONF_HOST: IP_ADDRESS},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
assert not await hass.config_entries.async_setup(entry.entry_id)
|
||||
assert entry.state is ConfigEntryState.MIGRATION_ERROR
|
||||
return entry
|
||||
|
||||
|
||||
async def test_invalid_serial_fix_flow_removes_entry(
|
||||
hass: HomeAssistant,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
hass_client: ClientSessionGenerator,
|
||||
) -> None:
|
||||
"""Test the invalid serial repair removes the entry it was raised for."""
|
||||
assert await async_setup_component(hass, "repairs", {})
|
||||
entry = await _setup_malformed_entry(hass)
|
||||
issue_id = f"invalid_serial_{entry.entry_id}"
|
||||
|
||||
issue = issue_registry.async_get_issue(DOMAIN, issue_id)
|
||||
assert issue is not None
|
||||
assert issue.is_fixable
|
||||
assert issue.severity is ir.IssueSeverity.ERROR
|
||||
assert issue.translation_placeholders == {
|
||||
"title": "Kitchen",
|
||||
"unique_id": MALFORMED_UNIQUE_ID,
|
||||
}
|
||||
|
||||
client = await hass_client()
|
||||
data = await start_repair_fix_flow(client, DOMAIN, issue_id)
|
||||
assert data["step_id"] == "confirm"
|
||||
|
||||
data = await process_repair_fix_flow(client, data["flow_id"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert data["type"] == "create_entry"
|
||||
assert hass.config_entries.async_get_entry(entry.entry_id) is None
|
||||
assert issue_registry.async_get_issue(DOMAIN, issue_id) is None
|
||||
|
||||
|
||||
async def test_removing_entry_deletes_invalid_serial_issue(
|
||||
hass: HomeAssistant, issue_registry: ir.IssueRegistry
|
||||
) -> None:
|
||||
"""Test deleting the entry by hand also clears its repair issue."""
|
||||
entry = await _setup_malformed_entry(hass)
|
||||
issue_id = f"invalid_serial_{entry.entry_id}"
|
||||
assert issue_registry.async_get_issue(DOMAIN, issue_id) is not None
|
||||
|
||||
await hass.config_entries.async_remove(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert issue_registry.async_get_issue(DOMAIN, issue_id) is None
|
||||
|
||||
|
||||
async def test_invalid_serial_fix_flow_entry_already_removed(
|
||||
hass: HomeAssistant, hass_client: ClientSessionGenerator
|
||||
) -> None:
|
||||
"""Test confirming the repair after the entry was deleted by hand."""
|
||||
assert await async_setup_component(hass, "repairs", {})
|
||||
entry = await _setup_malformed_entry(hass)
|
||||
issue_id = f"invalid_serial_{entry.entry_id}"
|
||||
|
||||
client = await hass_client()
|
||||
data = await start_repair_fix_flow(client, DOMAIN, issue_id)
|
||||
assert data["step_id"] == "confirm"
|
||||
|
||||
await hass.config_entries.async_remove(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
data = await process_repair_fix_flow(client, data["flow_id"])
|
||||
|
||||
assert data["type"] == "create_entry"
|
||||
Reference in New Issue
Block a user