Improve error message when failing to create backups (#139262)

* Improve error message when failing to create backups

* Check for expected error message in tests
pull/139340/head
Erik Montnemery 2025-02-26 13:44:09 +01:00 committed by GitHub
parent 0f827fbf22
commit ee01aa73b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 133 additions and 4 deletions

View File

@ -1620,7 +1620,13 @@ class CoreBackupReaderWriter(BackupReaderWriter):
"""Generate backup contents and return the size."""
if not tar_file_path:
tar_file_path = self.temp_backup_dir / f"{backup_data['slug']}.tar"
make_backup_dir(tar_file_path.parent)
try:
make_backup_dir(tar_file_path.parent)
except OSError as err:
raise BackupReaderWriterError(
f"Failed to create dir {tar_file_path.parent}: "
f"{err} ({err.__class__.__name__})"
) from err
excludes = EXCLUDE_FROM_BACKUP
if not database_included:
@ -1658,7 +1664,14 @@ class CoreBackupReaderWriter(BackupReaderWriter):
file_filter=is_excluded_by_filter,
arcname="data",
)
return (tar_file_path, tar_file_path.stat().st_size)
try:
stat_result = tar_file_path.stat()
except OSError as err:
raise BackupReaderWriterError(
f"Error getting size of {tar_file_path}: "
f"{err} ({err.__class__.__name__})"
) from err
return (tar_file_path, stat_result.st_size)
async def async_receive_backup(
self,

View File

@ -1311,7 +1311,7 @@ async def test_initiate_backup_with_task_error(
(1, None, 1, None, 1, None, 1, OSError("Boom!")),
],
)
async def test_initiate_backup_file_error(
async def test_initiate_backup_file_error_upload_to_agents(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
generate_backup_id: MagicMock,
@ -1325,7 +1325,7 @@ async def test_initiate_backup_file_error(
unlink_call_count: int,
unlink_exception: Exception | None,
) -> None:
"""Test file error during generate backup."""
"""Test file error during generate backup, while uploading to agents."""
agent_ids = ["test.remote"]
await setup_backup_integration(hass, remote_agents=["test.remote"])
@ -1418,6 +1418,122 @@ async def test_initiate_backup_file_error(
assert unlink_mock.call_count == unlink_call_count
@pytest.mark.usefixtures("mock_backup_generation")
@pytest.mark.parametrize(
(
"mkdir_call_count",
"mkdir_exception",
"atomic_contents_add_call_count",
"atomic_contents_add_exception",
"stat_call_count",
"stat_exception",
"error_message",
),
[
(1, OSError("Boom!"), 0, None, 0, None, "Failed to create dir"),
(1, None, 1, OSError("Boom!"), 0, None, "Boom!"),
(1, None, 1, None, 1, OSError("Boom!"), "Error getting size"),
],
)
async def test_initiate_backup_file_error_create_backup(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
generate_backup_id: MagicMock,
path_glob: MagicMock,
caplog: pytest.LogCaptureFixture,
mkdir_call_count: int,
mkdir_exception: Exception | None,
atomic_contents_add_call_count: int,
atomic_contents_add_exception: Exception | None,
stat_call_count: int,
stat_exception: Exception | None,
error_message: str,
) -> None:
"""Test file error during generate backup, while creating backup."""
agent_ids = ["test.remote"]
await setup_backup_integration(hass, remote_agents=["test.remote"])
ws_client = await hass_ws_client(hass)
path_glob.return_value = []
await ws_client.send_json_auto_id({"type": "backup/info"})
result = await ws_client.receive_json()
assert result["success"] is True
assert result["result"] == {
"backups": [],
"agent_errors": {},
"last_attempted_automatic_backup": None,
"last_completed_automatic_backup": None,
"last_non_idle_event": None,
"next_automatic_backup": None,
"next_automatic_backup_additional": False,
"state": "idle",
}
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
result = await ws_client.receive_json()
assert result["event"] == {"manager_state": BackupManagerState.IDLE}
result = await ws_client.receive_json()
assert result["success"] is True
with (
patch(
"homeassistant.components.backup.manager.atomic_contents_add",
side_effect=atomic_contents_add_exception,
) as atomic_contents_add_mock,
patch("pathlib.Path.mkdir", side_effect=mkdir_exception) as mkdir_mock,
patch("pathlib.Path.stat", side_effect=stat_exception) as stat_mock,
):
await ws_client.send_json_auto_id(
{"type": "backup/generate", "agent_ids": agent_ids}
)
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": None,
"stage": None,
"state": CreateBackupState.IN_PROGRESS,
}
result = await ws_client.receive_json()
assert result["success"] is True
backup_id = result["result"]["backup_job_id"]
assert backup_id == generate_backup_id.return_value
await hass.async_block_till_done()
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": None,
"stage": CreateBackupStage.HOME_ASSISTANT,
"state": CreateBackupState.IN_PROGRESS,
}
result = await ws_client.receive_json()
assert result["event"] == {
"manager_state": BackupManagerState.CREATE_BACKUP,
"reason": "upload_failed",
"stage": None,
"state": CreateBackupState.FAILED,
}
result = await ws_client.receive_json()
assert result["event"] == {"manager_state": BackupManagerState.IDLE}
assert atomic_contents_add_mock.call_count == atomic_contents_add_call_count
assert mkdir_mock.call_count == mkdir_call_count
assert stat_mock.call_count == stat_call_count
assert error_message in caplog.text
def _mock_local_backup_agent(name: str) -> Mock:
local_agent = mock_backup_agent(name)
# This makes the local_agent pass isinstance checks for LocalBackupAgent