Include error reason in backup events (#136697)

* Include error reason in backup events

* Update hassio backup tests

* Sort code

* Remove catching BackupError in async_receive_backup
This commit is contained in:
Erik Montnemery 2025-01-28 14:44:09 +01:00 committed by GitHub
parent 9a4b73a834
commit abb58ec785
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 219 additions and 36 deletions

View File

@ -10,18 +10,20 @@ from typing import Any, Protocol
from propcache.api import cached_property
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from .models import AgentBackup
from .models import AgentBackup, BackupError
class BackupAgentError(HomeAssistantError):
class BackupAgentError(BackupError):
"""Base class for backup agent errors."""
error_code = "backup_agent_error"
class BackupAgentUnreachableError(BackupAgentError):
"""Raised when the agent can't reach its API."""
error_code = "backup_agent_unreachable"
_message = "The backup agent is unreachable."

View File

@ -22,7 +22,6 @@ from securetar import SecureTarFile, atomic_contents_add
from homeassistant.backup_restore import RESTORE_BACKUP_FILE, password_to_key
from homeassistant.const import __version__ as HAVERSION
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
instance_id,
integration_platform,
@ -47,7 +46,7 @@ from .const import (
EXCLUDE_FROM_BACKUP,
LOGGER,
)
from .models import AgentBackup, BackupManagerError, Folder
from .models import AgentBackup, BackupError, BackupManagerError, Folder
from .store import BackupStore
from .util import (
AsyncIteratorReader,
@ -171,6 +170,7 @@ class CreateBackupEvent(ManagerStateEvent):
"""Backup in progress."""
manager_state: BackupManagerState = BackupManagerState.CREATE_BACKUP
reason: str | None
stage: CreateBackupStage | None
state: CreateBackupState
@ -180,6 +180,7 @@ class ReceiveBackupEvent(ManagerStateEvent):
"""Backup receive."""
manager_state: BackupManagerState = BackupManagerState.RECEIVE_BACKUP
reason: str | None
stage: ReceiveBackupStage | None
state: ReceiveBackupState
@ -189,6 +190,7 @@ class RestoreBackupEvent(ManagerStateEvent):
"""Backup restore."""
manager_state: BackupManagerState = BackupManagerState.RESTORE_BACKUP
reason: str | None
stage: RestoreBackupStage | None
state: RestoreBackupState
@ -250,19 +252,23 @@ class BackupReaderWriter(abc.ABC):
"""Restore a backup."""
class BackupReaderWriterError(HomeAssistantError):
class BackupReaderWriterError(BackupError):
"""Backup reader/writer error."""
error_code = "backup_reader_writer_error"
class IncorrectPasswordError(BackupReaderWriterError):
"""Raised when the password is incorrect."""
error_code = "password_incorrect"
_message = "The password provided is incorrect."
class DecryptOnDowloadNotSupported(BackupManagerError):
"""Raised when on-the-fly decryption is not supported."""
error_code = "decrypt_on_download_not_supported"
_message = "On-the-fly decryption is not supported for this backup."
@ -619,18 +625,30 @@ class BackupManager:
if self.state is not BackupManagerState.IDLE:
raise BackupManagerError(f"Backup manager busy: {self.state}")
self.async_on_backup_event(
ReceiveBackupEvent(stage=None, state=ReceiveBackupState.IN_PROGRESS)
ReceiveBackupEvent(
reason=None,
stage=None,
state=ReceiveBackupState.IN_PROGRESS,
)
)
try:
await self._async_receive_backup(agent_ids=agent_ids, contents=contents)
except Exception:
self.async_on_backup_event(
ReceiveBackupEvent(stage=None, state=ReceiveBackupState.FAILED)
ReceiveBackupEvent(
reason="unknown_error",
stage=None,
state=ReceiveBackupState.FAILED,
)
)
raise
else:
self.async_on_backup_event(
ReceiveBackupEvent(stage=None, state=ReceiveBackupState.COMPLETED)
ReceiveBackupEvent(
reason=None,
stage=None,
state=ReceiveBackupState.COMPLETED,
)
)
finally:
self.async_on_backup_event(IdleEvent())
@ -645,6 +663,7 @@ class BackupManager:
contents.chunk_size = BUF_SIZE
self.async_on_backup_event(
ReceiveBackupEvent(
reason=None,
stage=ReceiveBackupStage.RECEIVE_FILE,
state=ReceiveBackupState.IN_PROGRESS,
)
@ -656,6 +675,7 @@ class BackupManager:
)
self.async_on_backup_event(
ReceiveBackupEvent(
reason=None,
stage=ReceiveBackupStage.UPLOAD_TO_AGENTS,
state=ReceiveBackupState.IN_PROGRESS,
)
@ -739,7 +759,11 @@ class BackupManager:
self.store.save()
self.async_on_backup_event(
CreateBackupEvent(stage=None, state=CreateBackupState.IN_PROGRESS)
CreateBackupEvent(
reason=None,
stage=None,
state=CreateBackupState.IN_PROGRESS,
)
)
try:
return await self._async_create_backup(
@ -755,9 +779,14 @@ class BackupManager:
raise_task_error=raise_task_error,
with_automatic_settings=with_automatic_settings,
)
except Exception:
except Exception as err:
reason = err.error_code if isinstance(err, BackupError) else "unknown_error"
self.async_on_backup_event(
CreateBackupEvent(stage=None, state=CreateBackupState.FAILED)
CreateBackupEvent(
reason=reason,
stage=None,
state=CreateBackupState.FAILED,
)
)
self.async_on_backup_event(IdleEvent())
if with_automatic_settings:
@ -861,6 +890,7 @@ class BackupManager:
)
self.async_on_backup_event(
CreateBackupEvent(
reason=None,
stage=CreateBackupStage.UPLOAD_TO_AGENTS,
state=CreateBackupState.IN_PROGRESS,
)
@ -891,14 +921,22 @@ class BackupManager:
finally:
self._backup_task = None
self._backup_finish_task = None
self.async_on_backup_event(
CreateBackupEvent(
stage=None,
state=CreateBackupState.COMPLETED
if backup_success
else CreateBackupState.FAILED,
if backup_success:
self.async_on_backup_event(
CreateBackupEvent(
reason=None,
stage=None,
state=CreateBackupState.COMPLETED,
)
)
else:
self.async_on_backup_event(
CreateBackupEvent(
reason="upload_failed",
stage=None,
state=CreateBackupState.FAILED,
)
)
)
self.async_on_backup_event(IdleEvent())
async def async_restore_backup(
@ -917,7 +955,11 @@ class BackupManager:
raise BackupManagerError(f"Backup manager busy: {self.state}")
self.async_on_backup_event(
RestoreBackupEvent(stage=None, state=RestoreBackupState.IN_PROGRESS)
RestoreBackupEvent(
reason=None,
stage=None,
state=RestoreBackupState.IN_PROGRESS,
)
)
try:
await self._async_restore_backup(
@ -930,11 +972,28 @@ class BackupManager:
restore_homeassistant=restore_homeassistant,
)
self.async_on_backup_event(
RestoreBackupEvent(stage=None, state=RestoreBackupState.COMPLETED)
RestoreBackupEvent(
reason=None,
stage=None,
state=RestoreBackupState.COMPLETED,
)
)
except BackupError as err:
self.async_on_backup_event(
RestoreBackupEvent(
reason=err.error_code,
stage=None,
state=RestoreBackupState.FAILED,
)
)
raise
except Exception:
self.async_on_backup_event(
RestoreBackupEvent(stage=None, state=RestoreBackupState.FAILED)
RestoreBackupEvent(
reason="unknown_error",
stage=None,
state=RestoreBackupState.FAILED,
)
)
raise
finally:
@ -1210,6 +1269,7 @@ class CoreBackupReaderWriter(BackupReaderWriter):
on_progress(
CreateBackupEvent(
reason=None,
stage=CreateBackupStage.HOME_ASSISTANT,
state=CreateBackupState.IN_PROGRESS,
)
@ -1469,7 +1529,11 @@ class CoreBackupReaderWriter(BackupReaderWriter):
await self._hass.async_add_executor_job(_write_restore_file)
on_progress(
RestoreBackupEvent(stage=None, state=RestoreBackupState.CORE_RESTART)
RestoreBackupEvent(
reason=None,
stage=None,
state=RestoreBackupState.CORE_RESTART,
)
)
await self._hass.services.async_call("homeassistant", "restart", blocking=True)

View File

@ -71,5 +71,13 @@ class AgentBackup:
)
class BackupManagerError(HomeAssistantError):
class BackupError(HomeAssistantError):
"""Base class for backup errors."""
error_code = "unknown"
class BackupManagerError(BackupError):
"""Backup manager error."""
error_code = "backup_manager_error"

View File

@ -3383,6 +3383,7 @@
dict({
'event': dict({
'manager_state': 'create_backup',
'reason': None,
'stage': None,
'state': 'in_progress',
}),
@ -3404,6 +3405,7 @@
dict({
'event': dict({
'manager_state': 'create_backup',
'reason': None,
'stage': 'home_assistant',
'state': 'in_progress',
}),
@ -3415,6 +3417,7 @@
dict({
'event': dict({
'manager_state': 'create_backup',
'reason': None,
'stage': 'upload_to_agents',
'state': 'in_progress',
}),
@ -3426,6 +3429,7 @@
dict({
'event': dict({
'manager_state': 'create_backup',
'reason': None,
'stage': None,
'state': 'completed',
}),
@ -3454,6 +3458,7 @@
dict({
'event': dict({
'manager_state': 'create_backup',
'reason': None,
'stage': None,
'state': 'in_progress',
}),
@ -3475,6 +3480,7 @@
dict({
'event': dict({
'manager_state': 'create_backup',
'reason': None,
'stage': 'home_assistant',
'state': 'in_progress',
}),
@ -3486,6 +3492,7 @@
dict({
'event': dict({
'manager_state': 'create_backup',
'reason': None,
'stage': 'upload_to_agents',
'state': 'in_progress',
}),
@ -3497,6 +3504,7 @@
dict({
'event': dict({
'manager_state': 'create_backup',
'reason': None,
'stage': None,
'state': 'completed',
}),
@ -3525,6 +3533,7 @@
dict({
'event': dict({
'manager_state': 'create_backup',
'reason': None,
'stage': None,
'state': 'in_progress',
}),
@ -3546,6 +3555,7 @@
dict({
'event': dict({
'manager_state': 'create_backup',
'reason': None,
'stage': 'home_assistant',
'state': 'in_progress',
}),
@ -3557,6 +3567,7 @@
dict({
'event': dict({
'manager_state': 'create_backup',
'reason': None,
'stage': 'upload_to_agents',
'state': 'in_progress',
}),
@ -3568,6 +3579,7 @@
dict({
'event': dict({
'manager_state': 'create_backup',
'reason': None,
'stage': None,
'state': 'completed',
}),
@ -3912,6 +3924,7 @@
dict({
'event': dict({
'manager_state': 'create_backup',
'reason': None,
'stage': None,
'state': 'in_progress',
}),

View File

@ -419,6 +419,7 @@ async def test_initiate_backup(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": None,
"stage": None,
"state": CreateBackupState.IN_PROGRESS,
}
@ -433,6 +434,7 @@ async def test_initiate_backup(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": None,
"stage": CreateBackupStage.HOME_ASSISTANT,
"state": CreateBackupState.IN_PROGRESS,
}
@ -440,6 +442,7 @@ async def test_initiate_backup(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": None,
"stage": CreateBackupStage.UPLOAD_TO_AGENTS,
"state": CreateBackupState.IN_PROGRESS,
}
@ -447,6 +450,7 @@ async def test_initiate_backup(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": None,
"stage": None,
"state": CreateBackupState.COMPLETED,
}
@ -670,6 +674,7 @@ async def test_initiate_backup_with_agent_error(
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"stage": None,
"reason": None,
"state": CreateBackupState.IN_PROGRESS,
}
result = await ws_client.receive_json()
@ -683,6 +688,7 @@ async def test_initiate_backup_with_agent_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": None,
"stage": CreateBackupStage.HOME_ASSISTANT,
"state": CreateBackupState.IN_PROGRESS,
}
@ -690,6 +696,7 @@ async def test_initiate_backup_with_agent_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": None,
"stage": CreateBackupStage.UPLOAD_TO_AGENTS,
"state": CreateBackupState.IN_PROGRESS,
}
@ -697,6 +704,7 @@ async def test_initiate_backup_with_agent_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": "upload_failed",
"stage": None,
"state": CreateBackupState.FAILED,
}
@ -1025,6 +1033,7 @@ async def test_initiate_backup_non_agent_upload_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": None,
"stage": None,
"state": CreateBackupState.IN_PROGRESS,
}
@ -1039,6 +1048,7 @@ async def test_initiate_backup_non_agent_upload_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": None,
"stage": CreateBackupStage.HOME_ASSISTANT,
"state": CreateBackupState.IN_PROGRESS,
}
@ -1046,6 +1056,7 @@ async def test_initiate_backup_non_agent_upload_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": None,
"stage": CreateBackupStage.UPLOAD_TO_AGENTS,
"state": CreateBackupState.IN_PROGRESS,
}
@ -1053,6 +1064,7 @@ async def test_initiate_backup_non_agent_upload_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": "upload_failed",
"stage": None,
"state": CreateBackupState.FAILED,
}
@ -1131,6 +1143,7 @@ async def test_initiate_backup_with_task_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": None,
"stage": None,
"state": CreateBackupState.IN_PROGRESS,
}
@ -1138,6 +1151,7 @@ async def test_initiate_backup_with_task_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": "upload_failed",
"stage": None,
"state": CreateBackupState.FAILED,
}
@ -1245,6 +1259,7 @@ async def test_initiate_backup_file_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": None,
"stage": None,
"state": CreateBackupState.IN_PROGRESS,
}
@ -1259,6 +1274,7 @@ async def test_initiate_backup_file_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": None,
"stage": CreateBackupStage.HOME_ASSISTANT,
"state": CreateBackupState.IN_PROGRESS,
}
@ -1266,6 +1282,7 @@ async def test_initiate_backup_file_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": None,
"stage": CreateBackupStage.UPLOAD_TO_AGENTS,
"state": CreateBackupState.IN_PROGRESS,
}
@ -1273,6 +1290,7 @@ async def test_initiate_backup_file_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": "upload_failed",
"stage": None,
"state": CreateBackupState.FAILED,
}
@ -1559,6 +1577,7 @@ async def test_receive_backup_busy_manager(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": "create_backup",
"reason": None,
"stage": None,
"state": "in_progress",
}
@ -1752,6 +1771,7 @@ async def test_receive_backup_agent_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": None,
"stage": None,
"state": ReceiveBackupState.IN_PROGRESS,
}
@ -1759,6 +1779,7 @@ async def test_receive_backup_agent_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": None,
"stage": ReceiveBackupStage.RECEIVE_FILE,
"state": ReceiveBackupState.IN_PROGRESS,
}
@ -1766,6 +1787,7 @@ async def test_receive_backup_agent_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": None,
"stage": ReceiveBackupStage.UPLOAD_TO_AGENTS,
"state": ReceiveBackupState.IN_PROGRESS,
}
@ -1773,6 +1795,7 @@ async def test_receive_backup_agent_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": None,
"stage": None,
"state": ReceiveBackupState.COMPLETED,
}
@ -1885,6 +1908,7 @@ async def test_receive_backup_non_agent_upload_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": None,
"stage": None,
"state": ReceiveBackupState.IN_PROGRESS,
}
@ -1892,6 +1916,7 @@ async def test_receive_backup_non_agent_upload_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": None,
"stage": ReceiveBackupStage.RECEIVE_FILE,
"state": ReceiveBackupState.IN_PROGRESS,
}
@ -1899,6 +1924,7 @@ async def test_receive_backup_non_agent_upload_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": None,
"stage": ReceiveBackupStage.UPLOAD_TO_AGENTS,
"state": ReceiveBackupState.IN_PROGRESS,
}
@ -2007,6 +2033,7 @@ async def test_receive_backup_file_write_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": None,
"stage": None,
"state": ReceiveBackupState.IN_PROGRESS,
}
@ -2014,6 +2041,7 @@ async def test_receive_backup_file_write_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": None,
"stage": ReceiveBackupStage.RECEIVE_FILE,
"state": ReceiveBackupState.IN_PROGRESS,
}
@ -2021,6 +2049,7 @@ async def test_receive_backup_file_write_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": "unknown_error",
"stage": None,
"state": ReceiveBackupState.FAILED,
}
@ -2114,6 +2143,7 @@ async def test_receive_backup_read_tar_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": None,
"stage": None,
"state": ReceiveBackupState.IN_PROGRESS,
}
@ -2121,6 +2151,7 @@ async def test_receive_backup_read_tar_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": None,
"stage": ReceiveBackupStage.RECEIVE_FILE,
"state": ReceiveBackupState.IN_PROGRESS,
}
@ -2128,6 +2159,7 @@ async def test_receive_backup_read_tar_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": "unknown_error",
"stage": None,
"state": ReceiveBackupState.FAILED,
}
@ -2151,6 +2183,7 @@ async def test_receive_backup_read_tar_error(
"unlink_call_count",
"unlink_exception",
"final_state",
"final_state_reason",
"response_status",
),
[
@ -2164,6 +2197,7 @@ async def test_receive_backup_read_tar_error(
1,
None,
ReceiveBackupState.COMPLETED,
None,
201,
),
(
@ -2176,6 +2210,7 @@ async def test_receive_backup_read_tar_error(
1,
None,
ReceiveBackupState.COMPLETED,
None,
201,
),
(
@ -2188,6 +2223,7 @@ async def test_receive_backup_read_tar_error(
1,
None,
ReceiveBackupState.COMPLETED,
None,
201,
),
(
@ -2200,6 +2236,7 @@ async def test_receive_backup_read_tar_error(
1,
OSError("Boom!"),
ReceiveBackupState.FAILED,
"unknown_error",
500,
),
],
@ -2218,6 +2255,7 @@ async def test_receive_backup_file_read_error(
unlink_call_count: int,
unlink_exception: Exception | None,
final_state: ReceiveBackupState,
final_state_reason: str | None,
response_status: int,
) -> None:
"""Test file read error during backup receive."""
@ -2288,6 +2326,7 @@ async def test_receive_backup_file_read_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": None,
"stage": None,
"state": ReceiveBackupState.IN_PROGRESS,
}
@ -2295,6 +2334,7 @@ async def test_receive_backup_file_read_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": None,
"stage": ReceiveBackupStage.RECEIVE_FILE,
"state": ReceiveBackupState.IN_PROGRESS,
}
@ -2302,6 +2342,7 @@ async def test_receive_backup_file_read_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": None,
"stage": ReceiveBackupStage.UPLOAD_TO_AGENTS,
"state": ReceiveBackupState.IN_PROGRESS,
}
@ -2309,6 +2350,7 @@ async def test_receive_backup_file_read_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RECEIVE_BACKUP,
"reason": final_state_reason,
"stage": None,
"state": final_state,
}
@ -2394,6 +2436,7 @@ async def test_restore_backup(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RESTORE_BACKUP,
"reason": None,
"stage": None,
"state": RestoreBackupState.IN_PROGRESS,
}
@ -2401,6 +2444,7 @@ async def test_restore_backup(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RESTORE_BACKUP,
"reason": None,
"stage": None,
"state": RestoreBackupState.CORE_RESTART,
}
@ -2410,6 +2454,7 @@ async def test_restore_backup(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RESTORE_BACKUP,
"reason": None,
"stage": None,
"state": RestoreBackupState.COMPLETED,
}
@ -2497,6 +2542,7 @@ async def test_restore_backup_wrong_password(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RESTORE_BACKUP,
"reason": None,
"stage": None,
"state": RestoreBackupState.IN_PROGRESS,
}
@ -2504,6 +2550,7 @@ async def test_restore_backup_wrong_password(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RESTORE_BACKUP,
"reason": "password_incorrect",
"stage": None,
"state": RestoreBackupState.FAILED,
}
@ -2523,23 +2570,27 @@ async def test_restore_backup_wrong_password(
@pytest.mark.usefixtures("path_glob")
@pytest.mark.parametrize(
("parameters", "expected_error"),
("parameters", "expected_error", "expected_reason"),
[
(
{"backup_id": TEST_BACKUP_DEF456.backup_id},
f"Backup def456 not found in agent {LOCAL_AGENT_ID}",
"backup_manager_error",
),
(
{"restore_addons": ["blah"]},
"Addons and folders are not supported in core restore",
"backup_reader_writer_error",
),
(
{"restore_folders": [Folder.ADDONS]},
"Addons and folders are not supported in core restore",
"backup_reader_writer_error",
),
(
{"restore_database": False, "restore_homeassistant": False},
"Home Assistant or database must be included in restore",
"backup_reader_writer_error",
),
],
)
@ -2548,6 +2599,7 @@ async def test_restore_backup_wrong_parameters(
hass_ws_client: WebSocketGenerator,
parameters: dict[str, Any],
expected_error: str,
expected_reason: str,
) -> None:
"""Test restore backup wrong parameters."""
await async_setup_component(hass, DOMAIN, {})
@ -2584,6 +2636,7 @@ async def test_restore_backup_wrong_parameters(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RESTORE_BACKUP,
"reason": None,
"stage": None,
"state": RestoreBackupState.IN_PROGRESS,
}
@ -2591,6 +2644,7 @@ async def test_restore_backup_wrong_parameters(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RESTORE_BACKUP,
"reason": expected_reason,
"stage": None,
"state": RestoreBackupState.FAILED,
}
@ -2640,10 +2694,20 @@ async def test_restore_backup_when_busy(
@pytest.mark.usefixtures("mock_backup_generation")
@pytest.mark.parametrize(
("exception", "error_code", "error_message"),
("exception", "error_code", "error_message", "expected_reason"),
[
(BackupAgentError("Boom!"), "home_assistant_error", "Boom!"),
(Exception("Boom!"), "unknown_error", "Unknown error"),
(
BackupAgentError("Boom!"),
"home_assistant_error",
"Boom!",
"backup_agent_error",
),
(
Exception("Boom!"),
"unknown_error",
"Unknown error",
"unknown_error",
),
],
)
async def test_restore_backup_agent_error(
@ -2652,6 +2716,7 @@ async def test_restore_backup_agent_error(
exception: Exception,
error_code: str,
error_message: str,
expected_reason: str,
) -> None:
"""Test restore backup with agent error."""
remote_agent = BackupAgentTest("remote", backups=[TEST_BACKUP_ABC123])
@ -2694,6 +2759,7 @@ async def test_restore_backup_agent_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RESTORE_BACKUP,
"reason": None,
"stage": None,
"state": RestoreBackupState.IN_PROGRESS,
}
@ -2701,6 +2767,7 @@ async def test_restore_backup_agent_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RESTORE_BACKUP,
"reason": expected_reason,
"stage": None,
"state": RestoreBackupState.FAILED,
}
@ -2841,6 +2908,7 @@ async def test_restore_backup_file_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RESTORE_BACKUP,
"reason": None,
"stage": None,
"state": RestoreBackupState.IN_PROGRESS,
}
@ -2848,6 +2916,7 @@ async def test_restore_backup_file_error(
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.RESTORE_BACKUP,
"reason": "unknown_error",
"stage": None,
"state": RestoreBackupState.FAILED,
}

View File

@ -2816,7 +2816,7 @@ async def test_subscribe_event(
assert await client.receive_json() == snapshot
manager.async_on_backup_event(
CreateBackupEvent(stage=None, state=CreateBackupState.IN_PROGRESS)
CreateBackupEvent(stage=None, state=CreateBackupState.IN_PROGRESS, reason=None)
)
assert await client.receive_json() == snapshot

View File

@ -753,6 +753,7 @@ async def test_reader_writer_create(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": None,
"stage": None,
"state": "in_progress",
}
@ -780,6 +781,7 @@ async def test_reader_writer_create(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": None,
"stage": "upload_to_agents",
"state": "in_progress",
}
@ -787,6 +789,7 @@ async def test_reader_writer_create(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": None,
"stage": None,
"state": "completed",
}
@ -800,14 +803,20 @@ async def test_reader_writer_create(
@pytest.mark.usefixtures("hassio_client", "setup_integration")
@pytest.mark.parametrize(
("side_effect", "error_code", "error_message"),
("side_effect", "error_code", "error_message", "expected_reason"),
[
(
SupervisorError("Boom!"),
"home_assistant_error",
"Error creating backup: Boom!",
"backup_manager_error",
),
(
Exception("Boom!"),
"unknown_error",
"Unknown error",
"unknown_error",
),
(Exception("Boom!"), "unknown_error", "Unknown error"),
],
)
async def test_reader_writer_create_partial_backup_error(
@ -817,6 +826,7 @@ async def test_reader_writer_create_partial_backup_error(
side_effect: Exception,
error_code: str,
error_message: str,
expected_reason: str,
) -> None:
"""Test client partial backup error when generating a backup."""
client = await hass_ws_client(hass)
@ -834,6 +844,7 @@ async def test_reader_writer_create_partial_backup_error(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": None,
"stage": None,
"state": "in_progress",
}
@ -841,6 +852,7 @@ async def test_reader_writer_create_partial_backup_error(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": expected_reason,
"stage": None,
"state": "failed",
}
@ -878,6 +890,7 @@ async def test_reader_writer_create_missing_reference_error(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": None,
"stage": None,
"state": "in_progress",
}
@ -903,6 +916,7 @@ async def test_reader_writer_create_missing_reference_error(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": "upload_failed",
"stage": None,
"state": "failed",
}
@ -961,6 +975,7 @@ async def test_reader_writer_create_download_remove_error(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": None,
"stage": None,
"state": "in_progress",
}
@ -986,6 +1001,7 @@ async def test_reader_writer_create_download_remove_error(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": None,
"stage": "upload_to_agents",
"state": "in_progress",
}
@ -993,6 +1009,7 @@ async def test_reader_writer_create_download_remove_error(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": "upload_failed",
"stage": None,
"state": "failed",
}
@ -1042,6 +1059,7 @@ async def test_reader_writer_create_info_error(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": None,
"stage": None,
"state": "in_progress",
}
@ -1067,6 +1085,7 @@ async def test_reader_writer_create_info_error(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": "upload_failed",
"stage": None,
"state": "failed",
}
@ -1114,6 +1133,7 @@ async def test_reader_writer_create_remote_backup(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": None,
"stage": None,
"state": "in_progress",
}
@ -1141,6 +1161,7 @@ async def test_reader_writer_create_remote_backup(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": None,
"stage": "upload_to_agents",
"state": "in_progress",
}
@ -1148,6 +1169,7 @@ async def test_reader_writer_create_remote_backup(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": None,
"stage": None,
"state": "completed",
}
@ -1204,6 +1226,7 @@ async def test_reader_writer_create_wrong_parameters(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": None,
"stage": None,
"state": "in_progress",
}
@ -1211,6 +1234,7 @@ async def test_reader_writer_create_wrong_parameters(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "create_backup",
"reason": "unknown_error",
"stage": None,
"state": "failed",
}
@ -1316,6 +1340,7 @@ async def test_reader_writer_restore(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "restore_backup",
"reason": None,
"stage": None,
"state": "in_progress",
}
@ -1347,6 +1372,7 @@ async def test_reader_writer_restore(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "restore_backup",
"reason": None,
"stage": None,
"state": "completed",
}
@ -1360,15 +1386,13 @@ async def test_reader_writer_restore(
@pytest.mark.parametrize(
("supervisor_error_string", "expected_error_code"),
("supervisor_error_string", "expected_error_code", "expected_reason"),
[
(
"Invalid password for backup",
"password_incorrect",
),
("Invalid password for backup", "password_incorrect", "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",
"unknown_error",
),
],
)
@ -1379,6 +1403,7 @@ async def test_reader_writer_restore_error(
supervisor_client: AsyncMock,
supervisor_error_string: str,
expected_error_code: str,
expected_reason: str,
) -> None:
"""Test restoring a backup."""
client = await hass_ws_client(hass)
@ -1400,6 +1425,7 @@ async def test_reader_writer_restore_error(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "restore_backup",
"reason": None,
"stage": None,
"state": "in_progress",
}
@ -1419,6 +1445,7 @@ async def test_reader_writer_restore_error(
response = await client.receive_json()
assert response["event"] == {
"manager_state": "restore_backup",
"reason": expected_reason,
"stage": None,
"state": "failed",
}