Improve Supervisor backup error handling (#134346)

* Raise Home Assistant error in case backup restore fails

This change raises a Home Assistant error in case the backup restore
fails. The Supervisor is checking some common issues before starting
the actual restore in background. This early checks raise an exception
(represented by a HTTP 400 error). This change catches such errors and
raises a Home Assistant error with the message from the Supervisor
exception.

* Add test coverage
pull/134436/head
Stefan Agner 2025-01-02 11:37:25 +01:00 committed by GitHub
parent 3845acd0ce
commit fb3105bdc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 92 additions and 11 deletions

View File

@ -28,6 +28,9 @@ from homeassistant.components.backup import (
NewBackup,
WrittenBackup,
)
# pylint: disable-next=hass-component-root-import
from homeassistant.components.backup.manager import IncorrectPasswordError
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@ -385,17 +388,24 @@ class SupervisorBackupReaderWriter(BackupReaderWriter):
agent = cast(SupervisorBackupAgent, manager.backup_agents[agent_id])
restore_location = agent.location
job = await self._client.backups.partial_restore(
backup_id,
supervisor_backups.PartialRestoreOptions(
addons=restore_addons_set,
folders=restore_folders_set,
homeassistant=restore_homeassistant,
password=password,
background=True,
location=restore_location,
),
)
try:
job = await self._client.backups.partial_restore(
backup_id,
supervisor_backups.PartialRestoreOptions(
addons=restore_addons_set,
folders=restore_folders_set,
homeassistant=restore_homeassistant,
password=password,
background=True,
location=restore_location,
),
)
except SupervisorBadRequestError as err:
# Supervisor currently does not transmit machine parsable error types
message = err.args[0]
if message.startswith("Invalid password for backup"):
raise IncorrectPasswordError(message) from err
raise HomeAssistantError(message) from err
restore_complete = asyncio.Event()

View File

@ -997,6 +997,77 @@ async def test_reader_writer_restore(
assert response["result"] is None
@pytest.mark.parametrize(
("supervisor_error_string", "expected_error_code"),
[
(
"Invalid password for backup",
"password_incorrect",
),
(
"Backup was made on supervisor version 2025.12.0, can't restore on 2024.12.0. Must update supervisor first.",
"home_assistant_error",
),
],
)
@pytest.mark.usefixtures("hassio_client", "setup_integration")
async def test_reader_writer_restore_error(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
supervisor_client: AsyncMock,
supervisor_error_string: str,
expected_error_code: str,
) -> None:
"""Test restoring a backup."""
client = await hass_ws_client(hass)
supervisor_client.backups.partial_restore.side_effect = SupervisorBadRequestError(
supervisor_error_string
)
supervisor_client.backups.list.return_value = [TEST_BACKUP]
supervisor_client.backups.backup_info.return_value = TEST_BACKUP_DETAILS
await client.send_json_auto_id({"type": "backup/subscribe_events"})
response = await client.receive_json()
assert response["event"] == {"manager_state": "idle"}
response = await client.receive_json()
assert response["success"]
await client.send_json_auto_id(
{"type": "backup/restore", "agent_id": "hassio.local", "backup_id": "abc123"}
)
response = await client.receive_json()
assert response["event"] == {
"manager_state": "restore_backup",
"stage": None,
"state": "in_progress",
}
supervisor_client.backups.partial_restore.assert_called_once_with(
"abc123",
supervisor_backups.PartialRestoreOptions(
addons=None,
background=True,
folders=None,
homeassistant=True,
location=None,
password=None,
),
)
response = await client.receive_json()
assert response["event"] == {
"manager_state": "restore_backup",
"stage": None,
"state": "failed",
}
response = await client.receive_json()
assert response["event"] == {"manager_state": "idle"}
response = await client.receive_json()
assert response["error"]["code"] == expected_error_code
@pytest.mark.parametrize(
("parameters", "expected_error"),
[