Move prusalink migration to async_migrate_entry and use a minor version bump (#106109)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>pull/105552/head
parent
ea28b74fe9
commit
803e77bebd
|
@ -20,6 +20,7 @@ from homeassistant.const import (
|
|||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryError
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
|
@ -29,73 +30,24 @@ from homeassistant.helpers.update_coordinator import (
|
|||
UpdateFailed,
|
||||
)
|
||||
|
||||
from .config_flow import ConfigFlow
|
||||
from .const import DOMAIN
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.BUTTON, Platform.CAMERA, Platform.SENSOR]
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def _migrate_to_version_2(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
) -> PrusaLink | None:
|
||||
"""Migrate to Version 2."""
|
||||
_LOGGER.debug("Migrating entry to version 2")
|
||||
|
||||
data = dict(entry.data)
|
||||
# "maker" is currently hardcoded in the firmware
|
||||
# https://github.com/prusa3d/Prusa-Firmware-Buddy/blob/bfb0ffc745ee6546e7efdba618d0e7c0f4c909cd/lib/WUI/wui_api.h#L19
|
||||
data = {
|
||||
**entry.data,
|
||||
CONF_USERNAME: "maker",
|
||||
CONF_PASSWORD: entry.data[CONF_API_KEY],
|
||||
}
|
||||
data.pop(CONF_API_KEY)
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up PrusaLink from a config entry."""
|
||||
if entry.version == 1 and entry.minor_version < 2:
|
||||
raise ConfigEntryError("Please upgrade your printer's firmware.")
|
||||
|
||||
api = PrusaLink(
|
||||
async_get_clientsession(hass),
|
||||
data[CONF_HOST],
|
||||
data[CONF_USERNAME],
|
||||
data[CONF_PASSWORD],
|
||||
entry.data[CONF_HOST],
|
||||
entry.data[CONF_USERNAME],
|
||||
entry.data[CONF_PASSWORD],
|
||||
)
|
||||
try:
|
||||
await api.get_info()
|
||||
except InvalidAuth:
|
||||
# We are unable to reach the new API which usually means
|
||||
# that the user is running an outdated firmware version
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"firmware_5_1_required",
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.ERROR,
|
||||
translation_key="firmware_5_1_required",
|
||||
translation_placeholders={
|
||||
"entry_title": entry.title,
|
||||
"prusa_mini_firmware_update": "https://help.prusa3d.com/article/firmware-updating-mini-mini_124784",
|
||||
"prusa_mk4_xl_firmware_update": "https://help.prusa3d.com/article/how-to-update-firmware-mk4-xl_453086",
|
||||
},
|
||||
)
|
||||
return None
|
||||
|
||||
entry.version = 2
|
||||
hass.config_entries.async_update_entry(entry, data=data)
|
||||
_LOGGER.info("Migrated config entry to version %d", entry.version)
|
||||
return api
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up PrusaLink from a config entry."""
|
||||
if entry.version == 1:
|
||||
if (api := await _migrate_to_version_2(hass, entry)) is None:
|
||||
return False
|
||||
ir.async_delete_issue(hass, DOMAIN, "firmware_5_1_required")
|
||||
else:
|
||||
api = PrusaLink(
|
||||
async_get_clientsession(hass),
|
||||
entry.data[CONF_HOST],
|
||||
entry.data[CONF_USERNAME],
|
||||
entry.data[CONF_PASSWORD],
|
||||
)
|
||||
|
||||
coordinators = {
|
||||
"legacy_status": LegacyStatusCoordinator(hass, api),
|
||||
|
@ -112,9 +64,59 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Migrate old entry."""
|
||||
# Version 1->2 migration are handled in async_setup_entry.
|
||||
if config_entry.version > ConfigFlow.VERSION:
|
||||
# This means the user has downgraded from a future version
|
||||
return False
|
||||
|
||||
new_data = dict(config_entry.data)
|
||||
if config_entry.version == 1:
|
||||
if config_entry.minor_version < 2:
|
||||
# Add username and password
|
||||
# "maker" is currently hardcoded in the firmware
|
||||
# https://github.com/prusa3d/Prusa-Firmware-Buddy/blob/bfb0ffc745ee6546e7efdba618d0e7c0f4c909cd/lib/WUI/wui_api.h#L19
|
||||
username = "maker"
|
||||
password = config_entry.data[CONF_API_KEY]
|
||||
|
||||
api = PrusaLink(
|
||||
async_get_clientsession(hass),
|
||||
config_entry.data[CONF_HOST],
|
||||
username,
|
||||
password,
|
||||
)
|
||||
try:
|
||||
await api.get_info()
|
||||
except InvalidAuth:
|
||||
# We are unable to reach the new API which usually means
|
||||
# that the user is running an outdated firmware version
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"firmware_5_1_required",
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.ERROR,
|
||||
translation_key="firmware_5_1_required",
|
||||
translation_placeholders={
|
||||
"entry_title": config_entry.title,
|
||||
"prusa_mini_firmware_update": "https://help.prusa3d.com/article/firmware-updating-mini-mini_124784",
|
||||
"prusa_mk4_xl_firmware_update": "https://help.prusa3d.com/article/how-to-update-firmware-mk4-xl_453086",
|
||||
},
|
||||
)
|
||||
# There is a check in the async_setup_entry to prevent the setup if minor_version < 2
|
||||
# Currently we can't reload the config entry
|
||||
# if the migration returns False.
|
||||
# Return True here to workaround that.
|
||||
return True
|
||||
|
||||
new_data[CONF_USERNAME] = username
|
||||
new_data[CONF_PASSWORD] = password
|
||||
|
||||
ir.async_delete_issue(hass, DOMAIN, "firmware_5_1_required")
|
||||
config_entry.minor_version = 2
|
||||
|
||||
hass.config_entries.async_update_entry(config_entry, data=new_data)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
@ -66,7 +66,8 @@ async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str,
|
|||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for PrusaLink."""
|
||||
|
||||
VERSION = 2
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 2
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
|
|
|
@ -14,7 +14,8 @@ def mock_config_entry(hass):
|
|||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={"host": "http://example.com", "username": "dummy", "password": "dummypw"},
|
||||
version=2,
|
||||
version=1,
|
||||
minor_version=2,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
return entry
|
||||
|
|
|
@ -6,6 +6,7 @@ from pyprusalink.types import InvalidAuth, PrusaLinkError
|
|||
import pytest
|
||||
|
||||
from homeassistant.components.prusalink import DOMAIN
|
||||
from homeassistant.components.prusalink.config_flow import ConfigFlow
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
@ -14,11 +15,12 @@ from homeassistant.util.dt import utcnow
|
|||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("mock_api")
|
||||
|
||||
|
||||
async def test_unloading(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: ConfigEntry,
|
||||
mock_api,
|
||||
) -> None:
|
||||
"""Test unloading prusalink."""
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
|
@ -35,7 +37,7 @@ async def test_unloading(
|
|||
|
||||
@pytest.mark.parametrize("exception", [InvalidAuth, PrusaLinkError])
|
||||
async def test_failed_update(
|
||||
hass: HomeAssistant, mock_config_entry: ConfigEntry, mock_api, exception
|
||||
hass: HomeAssistant, mock_config_entry: ConfigEntry, exception
|
||||
) -> None:
|
||||
"""Test failed update marks prusalink unavailable."""
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
|
@ -61,16 +63,17 @@ async def test_failed_update(
|
|||
assert state.state == "unavailable"
|
||||
|
||||
|
||||
async def test_migration_1_2(
|
||||
hass: HomeAssistant, issue_registry: ir.IssueRegistry, mock_api
|
||||
async def test_migration_from_1_1_to_1_2(
|
||||
hass: HomeAssistant, issue_registry: ir.IssueRegistry
|
||||
) -> None:
|
||||
"""Test migrating from version 1 to 2."""
|
||||
data = {
|
||||
CONF_HOST: "http://prusaxl.local",
|
||||
CONF_API_KEY: "api-key",
|
||||
}
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_HOST: "http://prusaxl.local",
|
||||
CONF_API_KEY: "api-key",
|
||||
},
|
||||
data=data,
|
||||
version=1,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
@ -83,7 +86,7 @@ async def test_migration_1_2(
|
|||
# Ensure that we have username, password after migration
|
||||
assert len(config_entries) == 1
|
||||
assert config_entries[0].data == {
|
||||
CONF_HOST: "http://prusaxl.local",
|
||||
**data,
|
||||
CONF_USERNAME: "maker",
|
||||
CONF_PASSWORD: "api-key",
|
||||
}
|
||||
|
@ -91,10 +94,10 @@ async def test_migration_1_2(
|
|||
assert len(issue_registry.issues) == 0
|
||||
|
||||
|
||||
async def test_outdated_firmware_migration_1_2(
|
||||
hass: HomeAssistant, issue_registry: ir.IssueRegistry, mock_api
|
||||
async def test_migration_from_1_1_to_1_2_outdated_firmware(
|
||||
hass: HomeAssistant, issue_registry: ir.IssueRegistry
|
||||
) -> None:
|
||||
"""Test migrating from version 1 to 2."""
|
||||
"""Test migrating from version 1.1 to 1.2."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
|
@ -107,14 +110,14 @@ async def test_outdated_firmware_migration_1_2(
|
|||
|
||||
with patch(
|
||||
"pyprusalink.PrusaLink.get_info",
|
||||
side_effect=InvalidAuth,
|
||||
side_effect=InvalidAuth, # Simulate firmware update required
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.state == ConfigEntryState.SETUP_ERROR
|
||||
# Make sure that we don't have thrown the issues
|
||||
assert len(issue_registry.issues) == 1
|
||||
assert entry.minor_version == 1
|
||||
assert (DOMAIN, "firmware_5_1_required") in issue_registry.issues
|
||||
|
||||
# Reloading the integration with a working API (e.g. User updated firmware)
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
@ -122,4 +125,22 @@ async def test_outdated_firmware_migration_1_2(
|
|||
|
||||
# Integration should be running now, the issue should be gone
|
||||
assert entry.state == ConfigEntryState.LOADED
|
||||
assert len(issue_registry.issues) == 0
|
||||
assert entry.minor_version == 2
|
||||
assert (DOMAIN, "firmware_5_1_required") not in issue_registry.issues
|
||||
|
||||
|
||||
async def test_migration_fails_on_future_version(
|
||||
hass: HomeAssistant, issue_registry: ir.IssueRegistry
|
||||
) -> None:
|
||||
"""Test migrating fails on a version higher than the current one."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={},
|
||||
version=ConfigFlow.VERSION + 1,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.state == ConfigEntryState.MIGRATION_ERROR
|
||||
|
|
Loading…
Reference in New Issue