mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Persist backup restore status after core restart (#136838)
* Persist backup restore status after core restart * Don't blow up if restore result file can't be removed * Update tests
This commit is contained in:
parent
8ab6bec746
commit
b2ec72d75f
@ -18,6 +18,7 @@ import securetar
|
|||||||
from .const import __version__ as HA_VERSION
|
from .const import __version__ as HA_VERSION
|
||||||
|
|
||||||
RESTORE_BACKUP_FILE = ".HA_RESTORE"
|
RESTORE_BACKUP_FILE = ".HA_RESTORE"
|
||||||
|
RESTORE_BACKUP_RESULT_FILE = ".HA_RESTORE_RESULT"
|
||||||
KEEP_BACKUPS = ("backups",)
|
KEEP_BACKUPS = ("backups",)
|
||||||
KEEP_DATABASE = (
|
KEEP_DATABASE = (
|
||||||
"home-assistant_v2.db",
|
"home-assistant_v2.db",
|
||||||
@ -62,7 +63,10 @@ def restore_backup_file_content(config_dir: Path) -> RestoreBackupFileContent |
|
|||||||
restore_database=instruction_content["restore_database"],
|
restore_database=instruction_content["restore_database"],
|
||||||
restore_homeassistant=instruction_content["restore_homeassistant"],
|
restore_homeassistant=instruction_content["restore_homeassistant"],
|
||||||
)
|
)
|
||||||
except (FileNotFoundError, KeyError, json.JSONDecodeError):
|
except FileNotFoundError:
|
||||||
|
return None
|
||||||
|
except (KeyError, json.JSONDecodeError) as err:
|
||||||
|
_write_restore_result_file(config_dir, False, err)
|
||||||
return None
|
return None
|
||||||
finally:
|
finally:
|
||||||
# Always remove the backup instruction file to prevent a boot loop
|
# Always remove the backup instruction file to prevent a boot loop
|
||||||
@ -159,6 +163,23 @@ def _extract_backup(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _write_restore_result_file(
|
||||||
|
config_dir: Path, success: bool, error: Exception | None
|
||||||
|
) -> None:
|
||||||
|
"""Write the restore result file."""
|
||||||
|
result_path = config_dir.joinpath(RESTORE_BACKUP_RESULT_FILE)
|
||||||
|
result_path.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"success": success,
|
||||||
|
"error": str(error) if error else None,
|
||||||
|
"error_type": str(type(error).__name__) if error else None,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def restore_backup(config_dir_path: str) -> bool:
|
def restore_backup(config_dir_path: str) -> bool:
|
||||||
"""Restore the backup file if any.
|
"""Restore the backup file if any.
|
||||||
|
|
||||||
@ -177,7 +198,14 @@ def restore_backup(config_dir_path: str) -> bool:
|
|||||||
restore_content=restore_content,
|
restore_content=restore_content,
|
||||||
)
|
)
|
||||||
except FileNotFoundError as err:
|
except FileNotFoundError as err:
|
||||||
raise ValueError(f"Backup file {backup_file_path} does not exist") from err
|
file_not_found = ValueError(f"Backup file {backup_file_path} does not exist")
|
||||||
|
_write_restore_result_file(config_dir, False, file_not_found)
|
||||||
|
raise file_not_found from err
|
||||||
|
except Exception as err:
|
||||||
|
_write_restore_result_file(config_dir, False, err)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
_write_restore_result_file(config_dir, True, None)
|
||||||
if restore_content.remove_after_restore:
|
if restore_content.remove_after_restore:
|
||||||
backup_file_path.unlink(missing_ok=True)
|
backup_file_path.unlink(missing_ok=True)
|
||||||
_LOGGER.info("Restore complete, restarting")
|
_LOGGER.info("Restore complete, restarting")
|
||||||
|
@ -26,6 +26,7 @@ from .manager import (
|
|||||||
BackupReaderWriterError,
|
BackupReaderWriterError,
|
||||||
CoreBackupReaderWriter,
|
CoreBackupReaderWriter,
|
||||||
CreateBackupEvent,
|
CreateBackupEvent,
|
||||||
|
IdleEvent,
|
||||||
IncorrectPasswordError,
|
IncorrectPasswordError,
|
||||||
ManagerBackup,
|
ManagerBackup,
|
||||||
NewBackup,
|
NewBackup,
|
||||||
@ -47,6 +48,7 @@ __all__ = [
|
|||||||
"BackupReaderWriterError",
|
"BackupReaderWriterError",
|
||||||
"CreateBackupEvent",
|
"CreateBackupEvent",
|
||||||
"Folder",
|
"Folder",
|
||||||
|
"IdleEvent",
|
||||||
"IncorrectPasswordError",
|
"IncorrectPasswordError",
|
||||||
"LocalBackupAgent",
|
"LocalBackupAgent",
|
||||||
"ManagerBackup",
|
"ManagerBackup",
|
||||||
|
@ -19,7 +19,11 @@ from typing import IO, TYPE_CHECKING, Any, Protocol, TypedDict, cast
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
from securetar import SecureTarFile, atomic_contents_add
|
from securetar import SecureTarFile, atomic_contents_add
|
||||||
|
|
||||||
from homeassistant.backup_restore import RESTORE_BACKUP_FILE, password_to_key
|
from homeassistant.backup_restore import (
|
||||||
|
RESTORE_BACKUP_FILE,
|
||||||
|
RESTORE_BACKUP_RESULT_FILE,
|
||||||
|
password_to_key,
|
||||||
|
)
|
||||||
from homeassistant.const import __version__ as HAVERSION
|
from homeassistant.const import __version__ as HAVERSION
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
@ -28,7 +32,7 @@ from homeassistant.helpers import (
|
|||||||
issue_registry as ir,
|
issue_registry as ir,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.json import json_bytes
|
from homeassistant.helpers.json import json_bytes
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util, json as json_util
|
||||||
|
|
||||||
from . import util as backup_util
|
from . import util as backup_util
|
||||||
from .agent import (
|
from .agent import (
|
||||||
@ -261,6 +265,14 @@ class BackupReaderWriter(abc.ABC):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Restore a backup."""
|
"""Restore a backup."""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
async def async_resume_restore_progress_after_restart(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
on_progress: Callable[[RestoreBackupEvent | IdleEvent], None],
|
||||||
|
) -> None:
|
||||||
|
"""Get restore events after core restart."""
|
||||||
|
|
||||||
|
|
||||||
class BackupReaderWriterError(BackupError):
|
class BackupReaderWriterError(BackupError):
|
||||||
"""Backup reader/writer error."""
|
"""Backup reader/writer error."""
|
||||||
@ -318,6 +330,10 @@ class BackupManager:
|
|||||||
self.config.load(stored["config"])
|
self.config.load(stored["config"])
|
||||||
self.known_backups.load(stored["backups"])
|
self.known_backups.load(stored["backups"])
|
||||||
|
|
||||||
|
await self._reader_writer.async_resume_restore_progress_after_restart(
|
||||||
|
on_progress=self.async_on_backup_event
|
||||||
|
)
|
||||||
|
|
||||||
await self.load_platforms()
|
await self.load_platforms()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -1605,6 +1621,54 @@ class CoreBackupReaderWriter(BackupReaderWriter):
|
|||||||
)
|
)
|
||||||
await self._hass.services.async_call("homeassistant", "restart", blocking=True)
|
await self._hass.services.async_call("homeassistant", "restart", blocking=True)
|
||||||
|
|
||||||
|
async def async_resume_restore_progress_after_restart(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
on_progress: Callable[[RestoreBackupEvent | IdleEvent], None],
|
||||||
|
) -> None:
|
||||||
|
"""Check restore status after core restart."""
|
||||||
|
|
||||||
|
def _read_restore_file() -> json_util.JsonObjectType | None:
|
||||||
|
"""Read the restore file."""
|
||||||
|
result_path = Path(self._hass.config.path(RESTORE_BACKUP_RESULT_FILE))
|
||||||
|
|
||||||
|
try:
|
||||||
|
restore_result = json_util.json_loads_object(result_path.read_bytes())
|
||||||
|
except FileNotFoundError:
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
result_path.unlink(missing_ok=True)
|
||||||
|
except OSError as err:
|
||||||
|
LOGGER.warning(
|
||||||
|
"Unexpected error deleting backup restore result file: %s %s",
|
||||||
|
type(err),
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
return restore_result
|
||||||
|
|
||||||
|
restore_result = await self._hass.async_add_executor_job(_read_restore_file)
|
||||||
|
if not restore_result:
|
||||||
|
return
|
||||||
|
|
||||||
|
success = restore_result["success"]
|
||||||
|
if not success:
|
||||||
|
LOGGER.warning(
|
||||||
|
"Backup restore failed with %s: %s",
|
||||||
|
restore_result["error_type"],
|
||||||
|
restore_result["error"],
|
||||||
|
)
|
||||||
|
state = RestoreBackupState.COMPLETED if success else RestoreBackupState.FAILED
|
||||||
|
on_progress(
|
||||||
|
RestoreBackupEvent(
|
||||||
|
reason=cast(str, restore_result["error"]),
|
||||||
|
stage=None,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
on_progress(IdleEvent())
|
||||||
|
|
||||||
|
|
||||||
def _generate_backup_id(date: str, name: str) -> str:
|
def _generate_backup_id(date: str, name: str) -> str:
|
||||||
"""Generate a backup ID."""
|
"""Generate a backup ID."""
|
||||||
|
@ -60,8 +60,10 @@ async def handle_info(
|
|||||||
"backups": [backup.as_frontend_json() for backup in backups.values()],
|
"backups": [backup.as_frontend_json() for backup in backups.values()],
|
||||||
"last_attempted_automatic_backup": manager.config.data.last_attempted_automatic_backup,
|
"last_attempted_automatic_backup": manager.config.data.last_attempted_automatic_backup,
|
||||||
"last_completed_automatic_backup": manager.config.data.last_completed_automatic_backup,
|
"last_completed_automatic_backup": manager.config.data.last_completed_automatic_backup,
|
||||||
|
"last_non_idle_event": manager.last_non_idle_event,
|
||||||
"next_automatic_backup": manager.config.data.schedule.next_automatic_backup,
|
"next_automatic_backup": manager.config.data.schedule.next_automatic_backup,
|
||||||
"next_automatic_backup_additional": manager.config.data.schedule.next_automatic_backup_additional,
|
"next_automatic_backup_additional": manager.config.data.schedule.next_automatic_backup_additional,
|
||||||
|
"state": manager.state,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ from homeassistant.components.backup import (
|
|||||||
BackupReaderWriterError,
|
BackupReaderWriterError,
|
||||||
CreateBackupEvent,
|
CreateBackupEvent,
|
||||||
Folder,
|
Folder,
|
||||||
|
IdleEvent,
|
||||||
IncorrectPasswordError,
|
IncorrectPasswordError,
|
||||||
NewBackup,
|
NewBackup,
|
||||||
RestoreBackupEvent,
|
RestoreBackupEvent,
|
||||||
@ -456,6 +457,13 @@ class SupervisorBackupReaderWriter(BackupReaderWriter):
|
|||||||
finally:
|
finally:
|
||||||
unsub()
|
unsub()
|
||||||
|
|
||||||
|
async def async_resume_restore_progress_after_restart(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
on_progress: Callable[[RestoreBackupEvent | IdleEvent], None],
|
||||||
|
) -> None:
|
||||||
|
"""Check restore status after core restart."""
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_listen_job_events(
|
def _async_listen_job_events(
|
||||||
self, job_id: str, on_event: Callable[[Mapping[str, Any]], None]
|
self, job_id: str, on_event: Callable[[Mapping[str, Any]], None]
|
||||||
|
@ -85,8 +85,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -117,8 +119,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -149,8 +153,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -181,8 +187,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -213,8 +221,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
|
@ -2977,8 +2977,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -3005,8 +3007,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -3050,8 +3054,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -3078,8 +3084,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -3123,8 +3131,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -3179,8 +3189,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -3219,8 +3231,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -3270,8 +3284,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -3319,8 +3335,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -3375,8 +3393,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -3432,8 +3452,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -3490,8 +3512,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -3546,8 +3570,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -3602,8 +3628,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -3658,8 +3686,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -3715,8 +3745,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -4181,8 +4213,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -4226,8 +4260,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -4275,8 +4311,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -4343,8 +4381,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
@ -4389,8 +4429,10 @@
|
|||||||
]),
|
]),
|
||||||
'last_attempted_automatic_backup': None,
|
'last_attempted_automatic_backup': None,
|
||||||
'last_completed_automatic_backup': None,
|
'last_completed_automatic_backup': None,
|
||||||
|
'last_non_idle_event': None,
|
||||||
'next_automatic_backup': None,
|
'next_automatic_backup': None,
|
||||||
'next_automatic_backup_additional': False,
|
'next_automatic_backup_additional': False,
|
||||||
|
'state': 'idle',
|
||||||
}),
|
}),
|
||||||
'success': True,
|
'success': True,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
|
@ -397,8 +397,10 @@ async def test_initiate_backup(
|
|||||||
"agent_errors": {},
|
"agent_errors": {},
|
||||||
"last_attempted_automatic_backup": None,
|
"last_attempted_automatic_backup": None,
|
||||||
"last_completed_automatic_backup": None,
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": None,
|
||||||
"next_automatic_backup": None,
|
"next_automatic_backup": None,
|
||||||
"next_automatic_backup_additional": False,
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
||||||
@ -626,8 +628,10 @@ async def test_initiate_backup_with_agent_error(
|
|||||||
"agent_errors": {},
|
"agent_errors": {},
|
||||||
"last_attempted_automatic_backup": None,
|
"last_attempted_automatic_backup": None,
|
||||||
"last_completed_automatic_backup": None,
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": None,
|
||||||
"next_automatic_backup": None,
|
"next_automatic_backup": None,
|
||||||
"next_automatic_backup_additional": False,
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
await ws_client.send_json_auto_id(
|
await ws_client.send_json_auto_id(
|
||||||
@ -724,8 +728,15 @@ async def test_initiate_backup_with_agent_error(
|
|||||||
"agent_errors": {},
|
"agent_errors": {},
|
||||||
"last_attempted_automatic_backup": None,
|
"last_attempted_automatic_backup": None,
|
||||||
"last_completed_automatic_backup": None,
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": {
|
||||||
|
"manager_state": "create_backup",
|
||||||
|
"reason": "upload_failed",
|
||||||
|
"stage": None,
|
||||||
|
"state": "failed",
|
||||||
|
},
|
||||||
"next_automatic_backup": None,
|
"next_automatic_backup": None,
|
||||||
"next_automatic_backup_additional": False,
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -993,8 +1004,10 @@ async def test_initiate_backup_non_agent_upload_error(
|
|||||||
"agent_errors": {},
|
"agent_errors": {},
|
||||||
"last_attempted_automatic_backup": None,
|
"last_attempted_automatic_backup": None,
|
||||||
"last_completed_automatic_backup": None,
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": None,
|
||||||
"next_automatic_backup": None,
|
"next_automatic_backup": None,
|
||||||
"next_automatic_backup_additional": False,
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
||||||
@ -1109,8 +1122,10 @@ async def test_initiate_backup_with_task_error(
|
|||||||
"agent_errors": {},
|
"agent_errors": {},
|
||||||
"last_attempted_automatic_backup": None,
|
"last_attempted_automatic_backup": None,
|
||||||
"last_completed_automatic_backup": None,
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": None,
|
||||||
"next_automatic_backup": None,
|
"next_automatic_backup": None,
|
||||||
"next_automatic_backup_additional": False,
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
||||||
@ -1217,8 +1232,10 @@ async def test_initiate_backup_file_error(
|
|||||||
"agent_errors": {},
|
"agent_errors": {},
|
||||||
"last_attempted_automatic_backup": None,
|
"last_attempted_automatic_backup": None,
|
||||||
"last_completed_automatic_backup": None,
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": None,
|
||||||
"next_automatic_backup": None,
|
"next_automatic_backup": None,
|
||||||
"next_automatic_backup_additional": False,
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
||||||
@ -1703,8 +1720,10 @@ async def test_receive_backup_agent_error(
|
|||||||
"agent_errors": {},
|
"agent_errors": {},
|
||||||
"last_attempted_automatic_backup": None,
|
"last_attempted_automatic_backup": None,
|
||||||
"last_completed_automatic_backup": None,
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": None,
|
||||||
"next_automatic_backup": None,
|
"next_automatic_backup": None,
|
||||||
"next_automatic_backup_additional": False,
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
await ws_client.send_json_auto_id(
|
await ws_client.send_json_auto_id(
|
||||||
@ -1786,8 +1805,15 @@ async def test_receive_backup_agent_error(
|
|||||||
"agent_errors": {},
|
"agent_errors": {},
|
||||||
"last_attempted_automatic_backup": None,
|
"last_attempted_automatic_backup": None,
|
||||||
"last_completed_automatic_backup": None,
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": {
|
||||||
|
"manager_state": "receive_backup",
|
||||||
|
"reason": None,
|
||||||
|
"stage": None,
|
||||||
|
"state": "completed",
|
||||||
|
},
|
||||||
"next_automatic_backup": None,
|
"next_automatic_backup": None,
|
||||||
"next_automatic_backup_additional": False,
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -1848,8 +1874,10 @@ async def test_receive_backup_non_agent_upload_error(
|
|||||||
"agent_errors": {},
|
"agent_errors": {},
|
||||||
"last_attempted_automatic_backup": None,
|
"last_attempted_automatic_backup": None,
|
||||||
"last_completed_automatic_backup": None,
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": None,
|
||||||
"next_automatic_backup": None,
|
"next_automatic_backup": None,
|
||||||
"next_automatic_backup_additional": False,
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
||||||
@ -1973,8 +2001,10 @@ async def test_receive_backup_file_write_error(
|
|||||||
"agent_errors": {},
|
"agent_errors": {},
|
||||||
"last_attempted_automatic_backup": None,
|
"last_attempted_automatic_backup": None,
|
||||||
"last_completed_automatic_backup": None,
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": None,
|
||||||
"next_automatic_backup": None,
|
"next_automatic_backup": None,
|
||||||
"next_automatic_backup_additional": False,
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
||||||
@ -2086,8 +2116,10 @@ async def test_receive_backup_read_tar_error(
|
|||||||
"agent_errors": {},
|
"agent_errors": {},
|
||||||
"last_attempted_automatic_backup": None,
|
"last_attempted_automatic_backup": None,
|
||||||
"last_completed_automatic_backup": None,
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": None,
|
||||||
"next_automatic_backup": None,
|
"next_automatic_backup": None,
|
||||||
"next_automatic_backup_additional": False,
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
||||||
@ -2264,8 +2296,10 @@ async def test_receive_backup_file_read_error(
|
|||||||
"agent_errors": {},
|
"agent_errors": {},
|
||||||
"last_attempted_automatic_backup": None,
|
"last_attempted_automatic_backup": None,
|
||||||
"last_completed_automatic_backup": None,
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": None,
|
||||||
"next_automatic_backup": None,
|
"next_automatic_backup": None,
|
||||||
"next_automatic_backup_additional": False,
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
await ws_client.send_json_auto_id({"type": "backup/subscribe_events"})
|
||||||
@ -3034,8 +3068,10 @@ async def test_initiate_backup_per_agent_encryption(
|
|||||||
"agent_errors": {},
|
"agent_errors": {},
|
||||||
"last_attempted_automatic_backup": None,
|
"last_attempted_automatic_backup": None,
|
||||||
"last_completed_automatic_backup": None,
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": None,
|
||||||
"next_automatic_backup": None,
|
"next_automatic_backup": None,
|
||||||
"next_automatic_backup_additional": False,
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
for command in commands:
|
for command in commands:
|
||||||
@ -3127,3 +3163,88 @@ async def test_initiate_backup_per_agent_encryption(
|
|||||||
"name": "test",
|
"name": "test",
|
||||||
"with_automatic_settings": False,
|
"with_automatic_settings": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("restore_result", "last_non_idle_event"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{"error": None, "error_type": None, "success": True},
|
||||||
|
{
|
||||||
|
"manager_state": "restore_backup",
|
||||||
|
"reason": None,
|
||||||
|
"stage": None,
|
||||||
|
"state": "completed",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"error": "Boom!", "error_type": "ValueError", "success": False},
|
||||||
|
{
|
||||||
|
"manager_state": "restore_backup",
|
||||||
|
"reason": "Boom!",
|
||||||
|
"stage": None,
|
||||||
|
"state": "failed",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_restore_progress_after_restart(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
|
restore_result: dict[str, Any],
|
||||||
|
last_non_idle_event: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
"""Test restore backup progress after restart."""
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"pathlib.Path.read_bytes", return_value=json.dumps(restore_result).encode()
|
||||||
|
):
|
||||||
|
await async_setup_component(hass, DOMAIN, {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
ws_client = await hass_ws_client(hass)
|
||||||
|
await ws_client.send_json_auto_id({"type": "backup/info"})
|
||||||
|
result = await ws_client.receive_json()
|
||||||
|
assert result["success"] is True
|
||||||
|
assert result["result"] == {
|
||||||
|
"agent_errors": {},
|
||||||
|
"backups": [],
|
||||||
|
"last_attempted_automatic_backup": None,
|
||||||
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": last_non_idle_event,
|
||||||
|
"next_automatic_backup": None,
|
||||||
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_restore_progress_after_restart_fail_to_remove(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test restore backup progress after restart when failing to remove result file."""
|
||||||
|
|
||||||
|
with patch("pathlib.Path.unlink", side_effect=OSError("Boom!")):
|
||||||
|
await async_setup_component(hass, DOMAIN, {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
ws_client = await hass_ws_client(hass)
|
||||||
|
await ws_client.send_json_auto_id({"type": "backup/info"})
|
||||||
|
result = await ws_client.receive_json()
|
||||||
|
assert result["success"] is True
|
||||||
|
assert result["result"] == {
|
||||||
|
"agent_errors": {},
|
||||||
|
"backups": [],
|
||||||
|
"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",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"Unexpected error deleting backup restore result file: <class 'OSError'> Boom!"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
@ -205,8 +205,10 @@ async def test_agents_list_backups_fail_cloud(
|
|||||||
"backups": [],
|
"backups": [],
|
||||||
"last_attempted_automatic_backup": None,
|
"last_attempted_automatic_backup": None,
|
||||||
"last_completed_automatic_backup": None,
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": None,
|
||||||
"next_automatic_backup": None,
|
"next_automatic_backup": None,
|
||||||
"next_automatic_backup_additional": False,
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,9 +10,12 @@
|
|||||||
'version': '1.0.0',
|
'version': '1.0.0',
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
'agent_ids': list([
|
'agents': dict({
|
||||||
'backup.local',
|
'backup.local': dict({
|
||||||
]),
|
'protected': True,
|
||||||
|
'size': 0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
'backup_id': 'abc123',
|
'backup_id': 'abc123',
|
||||||
'database_included': True,
|
'database_included': True,
|
||||||
'date': '1970-01-01T00:00:00.000Z',
|
'date': '1970-01-01T00:00:00.000Z',
|
||||||
@ -25,16 +28,17 @@
|
|||||||
'homeassistant_included': True,
|
'homeassistant_included': True,
|
||||||
'homeassistant_version': '2024.12.0',
|
'homeassistant_version': '2024.12.0',
|
||||||
'name': 'Test',
|
'name': 'Test',
|
||||||
'protected': False,
|
|
||||||
'size': 0,
|
|
||||||
'with_automatic_settings': True,
|
'with_automatic_settings': True,
|
||||||
}),
|
}),
|
||||||
dict({
|
dict({
|
||||||
'addons': list([
|
'addons': list([
|
||||||
]),
|
]),
|
||||||
'agent_ids': list([
|
'agents': dict({
|
||||||
'test.remote',
|
'test.remote': dict({
|
||||||
]),
|
'protected': True,
|
||||||
|
'size': 0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
'backup_id': 'def456',
|
'backup_id': 'def456',
|
||||||
'database_included': False,
|
'database_included': False,
|
||||||
'date': '1980-01-01T00:00:00.000Z',
|
'date': '1980-01-01T00:00:00.000Z',
|
||||||
@ -47,8 +51,6 @@
|
|||||||
'homeassistant_included': True,
|
'homeassistant_included': True,
|
||||||
'homeassistant_version': '2024.12.0',
|
'homeassistant_version': '2024.12.0',
|
||||||
'name': 'Test 2',
|
'name': 'Test 2',
|
||||||
'protected': False,
|
|
||||||
'size': 1,
|
|
||||||
'with_automatic_settings': None,
|
'with_automatic_settings': None,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
|
@ -798,6 +798,9 @@ async def test_onboarding_backup_info(
|
|||||||
backups = {
|
backups = {
|
||||||
"abc123": backup.ManagerBackup(
|
"abc123": backup.ManagerBackup(
|
||||||
addons=[backup.AddonInfo(name="Test", slug="test", version="1.0.0")],
|
addons=[backup.AddonInfo(name="Test", slug="test", version="1.0.0")],
|
||||||
|
agents={
|
||||||
|
"backup.local": backup.manager.AgentBackupStatus(protected=True, size=0)
|
||||||
|
},
|
||||||
backup_id="abc123",
|
backup_id="abc123",
|
||||||
date="1970-01-01T00:00:00.000Z",
|
date="1970-01-01T00:00:00.000Z",
|
||||||
database_included=True,
|
database_included=True,
|
||||||
@ -806,14 +809,14 @@ async def test_onboarding_backup_info(
|
|||||||
homeassistant_included=True,
|
homeassistant_included=True,
|
||||||
homeassistant_version="2024.12.0",
|
homeassistant_version="2024.12.0",
|
||||||
name="Test",
|
name="Test",
|
||||||
protected=False,
|
|
||||||
size=0,
|
|
||||||
agent_ids=["backup.local"],
|
|
||||||
failed_agent_ids=[],
|
failed_agent_ids=[],
|
||||||
with_automatic_settings=True,
|
with_automatic_settings=True,
|
||||||
),
|
),
|
||||||
"def456": backup.ManagerBackup(
|
"def456": backup.ManagerBackup(
|
||||||
addons=[],
|
addons=[],
|
||||||
|
agents={
|
||||||
|
"test.remote": backup.manager.AgentBackupStatus(protected=True, size=0)
|
||||||
|
},
|
||||||
backup_id="def456",
|
backup_id="def456",
|
||||||
date="1980-01-01T00:00:00.000Z",
|
date="1980-01-01T00:00:00.000Z",
|
||||||
database_included=False,
|
database_included=False,
|
||||||
@ -825,9 +828,6 @@ async def test_onboarding_backup_info(
|
|||||||
homeassistant_included=True,
|
homeassistant_included=True,
|
||||||
homeassistant_version="2024.12.0",
|
homeassistant_version="2024.12.0",
|
||||||
name="Test 2",
|
name="Test 2",
|
||||||
protected=False,
|
|
||||||
size=1,
|
|
||||||
agent_ids=["test.remote"],
|
|
||||||
failed_agent_ids=[],
|
failed_agent_ids=[],
|
||||||
with_automatic_settings=None,
|
with_automatic_settings=None,
|
||||||
),
|
),
|
||||||
|
@ -332,8 +332,10 @@ async def test_agents_list_backups_error(
|
|||||||
"backups": [],
|
"backups": [],
|
||||||
"last_attempted_automatic_backup": None,
|
"last_attempted_automatic_backup": None,
|
||||||
"last_completed_automatic_backup": None,
|
"last_completed_automatic_backup": None,
|
||||||
|
"last_non_idle_event": None,
|
||||||
"next_automatic_backup": None,
|
"next_automatic_backup": None,
|
||||||
"next_automatic_backup_additional": False,
|
"next_automatic_backup_additional": False,
|
||||||
|
"state": "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
"""Test methods in backup_restore."""
|
"""Test methods in backup_restore."""
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import tarfile
|
import tarfile
|
||||||
|
from typing import Any
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -11,6 +14,23 @@ from homeassistant import backup_restore
|
|||||||
from .common import get_test_config_dir
|
from .common import get_test_config_dir
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def remove_restore_result_file() -> Generator[None, Any, Any]:
|
||||||
|
"""Remove the restore result file."""
|
||||||
|
yield
|
||||||
|
Path(get_test_config_dir(".HA_RESTORE_RESULT")).unlink(missing_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def restore_result_file_content() -> dict[str, Any] | None:
|
||||||
|
"""Return the content of the restore result file."""
|
||||||
|
try:
|
||||||
|
return json.loads(
|
||||||
|
Path(get_test_config_dir(".HA_RESTORE_RESULT")).read_text("utf-8")
|
||||||
|
)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("side_effect", "content", "expected"),
|
("side_effect", "content", "expected"),
|
||||||
[
|
[
|
||||||
@ -87,6 +107,11 @@ def test_restoring_backup_that_does_not_exist() -> None:
|
|||||||
),
|
),
|
||||||
):
|
):
|
||||||
assert backup_restore.restore_backup(Path(get_test_config_dir())) is False
|
assert backup_restore.restore_backup(Path(get_test_config_dir())) is False
|
||||||
|
assert restore_result_file_content() == {
|
||||||
|
"error": f"Backup file {backup_file_path} does not exist",
|
||||||
|
"error_type": "ValueError",
|
||||||
|
"success": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_restoring_backup_when_instructions_can_not_be_read() -> None:
|
def test_restoring_backup_when_instructions_can_not_be_read() -> None:
|
||||||
@ -98,6 +123,7 @@ def test_restoring_backup_when_instructions_can_not_be_read() -> None:
|
|||||||
),
|
),
|
||||||
):
|
):
|
||||||
assert backup_restore.restore_backup(Path(get_test_config_dir())) is False
|
assert backup_restore.restore_backup(Path(get_test_config_dir())) is False
|
||||||
|
assert restore_result_file_content() is None
|
||||||
|
|
||||||
|
|
||||||
def test_restoring_backup_that_is_not_a_file() -> None:
|
def test_restoring_backup_that_is_not_a_file() -> None:
|
||||||
@ -121,6 +147,11 @@ def test_restoring_backup_that_is_not_a_file() -> None:
|
|||||||
),
|
),
|
||||||
):
|
):
|
||||||
assert backup_restore.restore_backup(Path(get_test_config_dir())) is False
|
assert backup_restore.restore_backup(Path(get_test_config_dir())) is False
|
||||||
|
assert restore_result_file_content() == {
|
||||||
|
"error": f"Backup file {backup_file_path} does not exist",
|
||||||
|
"error_type": "ValueError",
|
||||||
|
"success": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_aborting_for_older_versions() -> None:
|
def test_aborting_for_older_versions() -> None:
|
||||||
@ -152,6 +183,13 @@ def test_aborting_for_older_versions() -> None:
|
|||||||
),
|
),
|
||||||
):
|
):
|
||||||
assert backup_restore.restore_backup(config_dir) is True
|
assert backup_restore.restore_backup(config_dir) is True
|
||||||
|
assert restore_result_file_content() == {
|
||||||
|
"error": (
|
||||||
|
"You need at least Home Assistant version 9999.99.99 to restore this backup"
|
||||||
|
),
|
||||||
|
"error_type": "ValueError",
|
||||||
|
"success": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -280,6 +318,11 @@ def test_removal_of_current_configuration_when_restoring(
|
|||||||
assert removed_directories == {
|
assert removed_directories == {
|
||||||
Path(config_dir, d) for d in expected_removed_directories
|
Path(config_dir, d) for d in expected_removed_directories
|
||||||
}
|
}
|
||||||
|
assert restore_result_file_content() == {
|
||||||
|
"error": None,
|
||||||
|
"error_type": None,
|
||||||
|
"success": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_extracting_the_contents_of_a_backup_file() -> None:
|
def test_extracting_the_contents_of_a_backup_file() -> None:
|
||||||
@ -332,6 +375,11 @@ def test_extracting_the_contents_of_a_backup_file() -> None:
|
|||||||
assert {
|
assert {
|
||||||
member.name for member in extractall_mock.mock_calls[-1].kwargs["members"]
|
member.name for member in extractall_mock.mock_calls[-1].kwargs["members"]
|
||||||
} == {"data", "data/.HA_VERSION", "data/.storage", "data/www"}
|
} == {"data", "data/.HA_VERSION", "data/.storage", "data/www"}
|
||||||
|
assert restore_result_file_content() == {
|
||||||
|
"error": None,
|
||||||
|
"error_type": None,
|
||||||
|
"success": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -362,6 +410,11 @@ def test_remove_backup_file_after_restore(
|
|||||||
assert mock_unlink.call_count == unlink_calls
|
assert mock_unlink.call_count == unlink_calls
|
||||||
for call in mock_unlink.mock_calls:
|
for call in mock_unlink.mock_calls:
|
||||||
assert call.args[0] == backup_file_path
|
assert call.args[0] == backup_file_path
|
||||||
|
assert restore_result_file_content() == {
|
||||||
|
"error": None,
|
||||||
|
"error_type": None,
|
||||||
|
"success": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user