diff --git a/homeassistant/components/backup/agent.py b/homeassistant/components/backup/agent.py index fe9eb9ea699..33656b6edcc 100644 --- a/homeassistant/components/backup/agent.py +++ b/homeassistant/components/backup/agent.py @@ -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." diff --git a/homeassistant/components/backup/manager.py b/homeassistant/components/backup/manager.py index 99740428863..4a871cdf73e 100644 --- a/homeassistant/components/backup/manager.py +++ b/homeassistant/components/backup/manager.py @@ -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) diff --git a/homeassistant/components/backup/models.py b/homeassistant/components/backup/models.py index 81c00d699c6..f2a83f50c17 100644 --- a/homeassistant/components/backup/models.py +++ b/homeassistant/components/backup/models.py @@ -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" diff --git a/tests/components/backup/snapshots/test_websocket.ambr b/tests/components/backup/snapshots/test_websocket.ambr index 2a6bc14fb74..634404b09cd 100644 --- a/tests/components/backup/snapshots/test_websocket.ambr +++ b/tests/components/backup/snapshots/test_websocket.ambr @@ -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', }), diff --git a/tests/components/backup/test_manager.py b/tests/components/backup/test_manager.py index c6eeff79d45..f2c2e5c5b05 100644 --- a/tests/components/backup/test_manager.py +++ b/tests/components/backup/test_manager.py @@ -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, } diff --git a/tests/components/backup/test_websocket.py b/tests/components/backup/test_websocket.py index 52c04474162..0fd0ba308b3 100644 --- a/tests/components/backup/test_websocket.py +++ b/tests/components/backup/test_websocket.py @@ -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 diff --git a/tests/components/hassio/test_backup.py b/tests/components/hassio/test_backup.py index 9483b513718..8cf8d11af04 100644 --- a/tests/components/hassio/test_backup.py +++ b/tests/components/hassio/test_backup.py @@ -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", }