diff --git a/homeassistant/components/backup/__init__.py b/homeassistant/components/backup/__init__.py index 0680e4b27e5..29dbe6f9e6a 100644 --- a/homeassistant/components/backup/__init__.py +++ b/homeassistant/components/backup/__init__.py @@ -11,7 +11,12 @@ from homeassistant.helpers.typing import ConfigType from .agent import BackupAgent, BackupAgentPlatformProtocol from .const import DOMAIN, LOGGER from .http import async_register_http_views -from .manager import Backup, BackupManager, BackupPlatformProtocol +from .manager import ( + Backup, + BackupManager, + BackupPlatformProtocol, + CoreBackupReaderWriter, +) from .models import BackupUploadMetadata, BaseBackup from .websocket import async_register_websocket_handlers @@ -31,7 +36,9 @@ SERVICE_CREATE_SCHEMA = vol.Schema({vol.Optional(CONF_PASSWORD): str}) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Backup integration.""" - hass.data[DOMAIN] = backup_manager = BackupManager(hass) + hass.data[DOMAIN] = backup_manager = BackupManager( + hass, CoreBackupReaderWriter(hass) + ) await backup_manager.async_setup() with_hassio = is_hassio(hass) diff --git a/homeassistant/components/backup/const.py b/homeassistant/components/backup/const.py index 932a29de6f1..31f89d4514a 100644 --- a/homeassistant/components/backup/const.py +++ b/homeassistant/components/backup/const.py @@ -8,12 +8,11 @@ from typing import TYPE_CHECKING from homeassistant.util.hass_dict import HassKey if TYPE_CHECKING: - from .manager import BaseBackupManager - from .models import BaseBackup + from .manager import BackupManager BUF_SIZE = 2**20 * 4 # 4MB DOMAIN = "backup" -DATA_MANAGER: HassKey[BaseBackupManager[BaseBackup]] = HassKey(DOMAIN) +DATA_MANAGER: HassKey[BackupManager] = HassKey(DOMAIN) LOGGER = getLogger(__package__) EXCLUDE_FROM_BACKUP = [ diff --git a/homeassistant/components/backup/http.py b/homeassistant/components/backup/http.py index 07d03e95dce..462ebf837f0 100644 --- a/homeassistant/components/backup/http.py +++ b/homeassistant/components/backup/http.py @@ -15,7 +15,6 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.util import slugify from .const import DATA_MANAGER -from .manager import BackupManager # pylint: disable=fixme # TODO: Don't forget to remove this when the implementation is complete @@ -47,7 +46,7 @@ class DownloadBackupView(HomeAssistantView): except KeyError: return Response(status=HTTPStatus.BAD_REQUEST) - manager = cast(BackupManager, request.app[KEY_HASS].data[DATA_MANAGER]) + manager = request.app[KEY_HASS].data[DATA_MANAGER] if agent_id not in manager.backup_agents: return Response(status=HTTPStatus.BAD_REQUEST) agent = manager.backup_agents[agent_id] diff --git a/homeassistant/components/backup/manager.py b/homeassistant/components/backup/manager.py index 251a7bcf976..72e625859fe 100644 --- a/homeassistant/components/backup/manager.py +++ b/homeassistant/components/backup/manager.py @@ -15,11 +15,10 @@ import shutil import tarfile from tempfile import TemporaryDirectory import time -from typing import Any, Generic, Protocol +from typing import TYPE_CHECKING, Any, Protocol import aiohttp from securetar import SecureTarFile, atomic_contents_add -from typing_extensions import TypeVar from homeassistant.backup_restore import RESTORE_BACKUP_FILE, password_to_key from homeassistant.const import __version__ as HAVERSION @@ -38,6 +37,7 @@ from .agent import ( from .config import BackupConfig from .const import ( BUF_SIZE, + DATA_MANAGER, DOMAIN, EXCLUDE_DATABASE_FROM_BACKUP, EXCLUDE_FROM_BACKUP, @@ -46,8 +46,6 @@ from .const import ( from .models import BackupUploadMetadata, BaseBackup from .util import read_backup -_BackupT = TypeVar("_BackupT", bound=BaseBackup, default=BaseBackup) - @dataclass(slots=True) class NewBackup: @@ -82,24 +80,62 @@ class BackupPlatformProtocol(Protocol): """Perform operations after a backup finishes.""" -class BaseBackupManager(abc.ABC, Generic[_BackupT]): +class BackupReaderWriter(abc.ABC): + """Abstract class for reading and writing backups.""" + + temp_backup_dir: Path + + @abc.abstractmethod + async def async_create_backup( + self, + *, + addons_included: list[str] | None, + agent_ids: list[str], + database_included: bool, + backup_name: str, + folders_included: list[str] | None, + on_progress: Callable[[BackupProgress], None] | None, + password: str | None, + ) -> tuple[NewBackup, asyncio.Task[tuple[BaseBackup, Path]]]: + """Create a backup.""" + + @abc.abstractmethod + async def async_restore_backup( + self, + backup_id: str, + *, + agent_id: str, + password: str | None, + **kwargs: Any, + ) -> None: + """Restore a backup.""" + + +class BackupManager: """Define the format that backup managers can have.""" - def __init__(self, hass: HomeAssistant) -> None: + def __init__(self, hass: HomeAssistant, reader_writer: BackupReaderWriter) -> None: """Initialize the backup manager.""" self.hass = hass - self.backup_task: asyncio.Task | None = None + self.backup_task: asyncio.Task[tuple[BaseBackup, Path]] | None = None + self.finish_backup_task: asyncio.Task[None] | None = None self.platforms: dict[str, BackupPlatformProtocol] = {} self.backup_agents: dict[str, BackupAgent] = {} self.local_backup_agents: dict[str, LocalBackupAgent] = {} self.config = BackupConfig(hass) self.syncing = False + self._reader_writer = reader_writer async def async_setup(self) -> None: """Set up the backup manager.""" await self.config.load() await self.load_platforms() + @property + def temp_backup_dir(self) -> Path: + """Return the temporary backup directory.""" + return self._reader_writer.temp_backup_dir + @callback def _add_platform_pre_post_handler( self, @@ -182,74 +218,6 @@ class BaseBackupManager(abc.ABC, Generic[_BackupT]): LOGGER.debug("Loaded %s platforms", len(self.platforms)) LOGGER.debug("Loaded %s agents", len(self.backup_agents)) - @abc.abstractmethod - async def async_restore_backup( - self, - backup_id: str, - *, - agent_id: str, - password: str | None, - **kwargs: Any, - ) -> None: - """Restore a backup.""" - - @abc.abstractmethod - async def async_create_backup( - self, - *, - addons_included: list[str] | None, - agent_ids: list[str], - database_included: bool, - folders_included: list[str] | None, - name: str | None, - on_progress: Callable[[BackupProgress], None] | None, - password: str | None, - **kwargs: Any, - ) -> NewBackup: - """Initiate generating a backup. - - :param on_progress: A callback that will be called with the progress of the - backup. - """ - - @abc.abstractmethod - async def async_get_backups( - self, **kwargs: Any - ) -> tuple[dict[str, Backup], dict[str, Exception]]: - """Get backups. - - Return a dictionary of Backup instances keyed by their ID. - """ - - @abc.abstractmethod - async def async_get_backup( - self, backup_id: str, **kwargs: Any - ) -> tuple[_BackupT | None, dict[str, Exception]]: - """Get a backup.""" - - @abc.abstractmethod - async def async_delete_backup(self, backup_id: str, **kwargs: Any) -> None: - """Delete a backup.""" - - @abc.abstractmethod - async def async_receive_backup( - self, - *, - agent_ids: list[str], - contents: aiohttp.BodyPartReader, - **kwargs: Any, - ) -> None: - """Receive and store a backup file from upload.""" - - -class BackupManager(BaseBackupManager[Backup]): - """Backup manager for the Backup integration.""" - - def __init__(self, hass: HomeAssistant) -> None: - """Initialize the backup manager.""" - super().__init__(hass=hass) - self.temp_backup_dir = Path(hass.config.path("tmp_backups")) - async def _async_upload_backup( self, *, @@ -288,7 +256,10 @@ class BackupManager(BaseBackupManager[Backup]): async def async_get_backups( self, **kwargs: Any ) -> tuple[dict[str, Backup], dict[str, Exception]]: - """Return backups.""" + """Get backups. + + Return a dictionary of Backup instances keyed by their ID. + """ backups: dict[str, Backup] = {} agent_errors: dict[str, Exception] = {} agent_ids = list(self.backup_agents) @@ -320,7 +291,7 @@ class BackupManager(BaseBackupManager[Backup]): async def async_get_backup( self, backup_id: str, **kwargs: Any ) -> tuple[Backup | None, dict[str, Exception]]: - """Return a backup.""" + """Get a backup.""" backup: Backup | None = None agent_errors: dict[str, Exception] = {} agent_ids = list(self.backup_agents.keys()) @@ -453,7 +424,11 @@ class BackupManager(BaseBackupManager[Backup]): password: str | None, **kwargs: Any, ) -> NewBackup: - """Initiate generating a backup.""" + """Initiate generating a backup. + + :param on_progress: A callback that will be called with the progress of the + backup. + """ if self.backup_task: raise HomeAssistantError("Backup already in progress") if not agent_ids: @@ -461,9 +436,118 @@ class BackupManager(BaseBackupManager[Backup]): if any(agent_id not in self.backup_agents for agent_id in agent_ids): raise HomeAssistantError("Invalid agent selected") backup_name = name or f"Core {HAVERSION}" + new_backup, self.backup_task = await self._reader_writer.async_create_backup( + addons_included=addons_included, + agent_ids=agent_ids, + backup_name=backup_name, + database_included=database_included, + folders_included=folders_included, + on_progress=on_progress, + password=password, + ) + self.finish_backup_task = self.hass.async_create_task( + self._async_finish_backup(agent_ids), + name="backup_manager_finish_backup", + ) + return new_backup + + async def _async_finish_backup(self, agent_ids: list[str]) -> None: + if TYPE_CHECKING: + assert self.backup_task is not None + try: + backup, tar_file_path = await self.backup_task + except Exception as err: # noqa: BLE001 + LOGGER.debug("Backup upload failed", exc_info=err) + else: + LOGGER.debug( + "Generated new backup with backup_id %s, uploading to agents %s", + backup.backup_id, + agent_ids, + ) + local_file_paths = [ + self.local_backup_agents[agent_id].get_backup_path(backup.backup_id) + for agent_id in agent_ids + if agent_id in self.local_backup_agents + ] + keep_path = False + for local_path in local_file_paths: + if local_path == tar_file_path: + keep_path = True + continue + await self.hass.async_add_executor_job( + shutil.copy, tar_file_path, local_path + ) + await self._async_upload_backup( + backup=backup, agent_ids=agent_ids, path=tar_file_path + ) + if not keep_path: + await self.hass.async_add_executor_job(tar_file_path.unlink, True) + finally: + self.backup_task = None + self.finish_backup_task = None + + async def async_restore_backup( + self, + backup_id: str, + *, + agent_id: str, + password: str | None, + **kwargs: Any, + ) -> None: + """Initiate restoring a backup. + + :param on_progress: A callback that will be called with the progress of the + restore. Home Assistant Core may need to be restarted during the backup + restore process, which means the restore process may not be able to report + when it's done. + """ + + if agent_id in self.local_backup_agents: + local_agent = self.local_backup_agents[agent_id] + if not await local_agent.async_get_backup(backup_id): + raise HomeAssistantError( + f"Backup {backup_id} not found in agent {agent_id}" + ) + else: + path = self.temp_backup_dir / f"{backup_id}.tar" + agent = self.backup_agents[agent_id] + if not await agent.async_get_backup(backup_id): + raise HomeAssistantError( + f"Backup {backup_id} not found in agent {agent_id}" + ) + await agent.async_download_backup(backup_id, path=path) + + await self._reader_writer.async_restore_backup( + backup_id=backup_id, + agent_id=agent_id, + password=password, + ) + + +class CoreBackupReaderWriter(BackupReaderWriter): + """Class for reading and writing backups in core and container installations.""" + + def __init__(self, hass: HomeAssistant) -> None: + """Initialize the backup reader/writer.""" + self._hass = hass + self.temp_backup_dir = Path(hass.config.path("tmp_backups")) + + async def async_create_backup( + self, + *, + addons_included: list[str] | None, + agent_ids: list[str], + database_included: bool, + backup_name: str, + folders_included: list[str] | None, + on_progress: Callable[[BackupProgress], None] | None, + password: str | None, + ) -> tuple[NewBackup, asyncio.Task[tuple[BaseBackup, Path]]]: + """Initiate generating a backup.""" date_str = dt_util.now().isoformat() backup_id = _generate_backup_id(date_str, backup_name) - self.backup_task = self.hass.async_create_task( + + backup_task = self._hass.async_create_task( self._async_create_backup( addons_included=addons_included, agent_ids=agent_ids, @@ -478,32 +562,35 @@ class BackupManager(BaseBackupManager[Backup]): name="backup_manager_create_backup", eager_start=False, # To ensure the task is not started before we return ) - return NewBackup(backup_id=backup_id) + + return (NewBackup(backup_id=backup_id), backup_task) async def _async_create_backup( self, *, addons_included: list[str] | None, agent_ids: list[str], + backup_id: str, database_included: bool, backup_name: str, date_str: str, folders_included: list[str] | None, on_progress: Callable[[BackupProgress], None] | None, password: str | None, - backup_id: str, - ) -> BaseBackup: + ) -> tuple[BaseBackup, Path]: """Generate a backup.""" + manager = self._hass.data[DATA_MANAGER] success = False - local_file_paths = [ - self.local_backup_agents[agent_id].get_backup_path(backup_id) - for agent_id in agent_ids - if agent_id in self.local_backup_agents - ] + suggested_tar_file_path = None + for agent_id in agent_ids: + if local_agent := manager.local_backup_agents.get(agent_id): + suggested_tar_file_path = local_agent.get_backup_path(backup_id) + break try: - await self.async_pre_backup_actions() + # Inform integrations a backup is about to be made + await manager.async_pre_backup_actions() backup_data = { "compressed": True, @@ -519,12 +606,12 @@ class BackupManager(BaseBackupManager[Backup]): "type": "partial", } - tar_file_path, size_in_bytes = await self.hass.async_add_executor_job( + tar_file_path, size_in_bytes = await self._hass.async_add_executor_job( self._mkdir_and_generate_backup_contents, - local_file_paths, backup_data, database_included, password, + suggested_tar_file_path, ) backup = BaseBackup( backup_id=backup_id, @@ -533,35 +620,23 @@ class BackupManager(BaseBackupManager[Backup]): protected=password is not None, size=round(size_in_bytes / 1_048_576, 2), ) - LOGGER.debug( - "Generated new backup with backup_id %s, uploading to agents %s", - backup_id, - agent_ids, - ) - await self._async_upload_backup( - backup=backup, agent_ids=agent_ids, path=tar_file_path - ) - if not local_file_paths: - await self.hass.async_add_executor_job(tar_file_path.unlink, True) success = True - return backup + return (backup, tar_file_path) finally: if on_progress: on_progress(BackupProgress(done=True, stage=None, success=success)) - self.backup_task = None - await self.async_post_backup_actions() + # Inform integrations the backup is done + await manager.async_post_backup_actions() def _mkdir_and_generate_backup_contents( self, - tar_file_paths: list[Path], backup_data: dict[str, Any], database_included: bool, password: str | None, + tar_file_path: Path | None, ) -> tuple[Path, int]: """Generate backup contents and return the size.""" - if tar_file_paths: - tar_file_path = tar_file_paths[0] - else: + if not tar_file_path: tar_file_path = self.temp_backup_dir / f"{backup_data['slug']}.tar" if not (backup_dir := tar_file_path.parent).exists(): LOGGER.debug("Creating backup directory %s", backup_dir) @@ -588,12 +663,10 @@ class BackupManager(BaseBackupManager[Backup]): ) as core_tar: atomic_contents_add( tar_file=core_tar, - origin_path=Path(self.hass.config.path()), + origin_path=Path(self._hass.config.path()), excludes=excludes, arcname="data", ) - for local_path in tar_file_paths[1:]: - shutil.copy(tar_file_path, local_path) return (tar_file_path, tar_file_path.stat().st_size) async def async_restore_backup( @@ -610,31 +683,22 @@ class BackupManager(BaseBackupManager[Backup]): will be handled during startup by the restore_backup module. """ - if agent_id in self.local_backup_agents: - local_agent = self.local_backup_agents[agent_id] - if not await local_agent.async_get_backup(backup_id): - raise HomeAssistantError( - f"Backup {backup_id} not found in agent {agent_id}" - ) + manager = self._hass.data[DATA_MANAGER] + if agent_id in manager.local_backup_agents: + local_agent = manager.local_backup_agents[agent_id] path = local_agent.get_backup_path(backup_id) else: path = self.temp_backup_dir / f"{backup_id}.tar" - agent = self.backup_agents[agent_id] - if not await agent.async_get_backup(backup_id): - raise HomeAssistantError( - f"Backup {backup_id} not found in agent {agent_id}" - ) - await agent.async_download_backup(backup_id, path=path) def _write_restore_file() -> None: """Write the restore file.""" - Path(self.hass.config.path(RESTORE_BACKUP_FILE)).write_text( + Path(self._hass.config.path(RESTORE_BACKUP_FILE)).write_text( json.dumps({"path": path.as_posix(), "password": password}), encoding="utf-8", ) - await self.hass.async_add_executor_job(_write_restore_file) - await self.hass.services.async_call("homeassistant", "restart", {}) + await self._hass.async_add_executor_job(_write_restore_file) + await self._hass.services.async_call("homeassistant", "restart", {}) def _generate_backup_id(date: str, name: str) -> str: diff --git a/homeassistant/components/backup/websocket.py b/homeassistant/components/backup/websocket.py index fb9ed7ebbd9..d4ca8e1e377 100644 --- a/homeassistant/components/backup/websocket.py +++ b/homeassistant/components/backup/websocket.py @@ -1,6 +1,6 @@ """Websocket commands for the Backup integration.""" -from typing import Any, cast +from typing import Any import voluptuous as vol @@ -8,7 +8,7 @@ from homeassistant.components import websocket_api from homeassistant.core import HomeAssistant, callback from .const import DATA_MANAGER, LOGGER -from .manager import BackupManager, BackupProgress +from .manager import BackupProgress @callback @@ -254,7 +254,7 @@ async def backup_agents_download( msg: dict[str, Any], ) -> None: """Download an uploaded backup.""" - manager = cast(BackupManager, hass.data[DATA_MANAGER]) + manager = hass.data[DATA_MANAGER] if not (agent := manager.backup_agents.get(msg["agent_id"])): connection.send_error( msg["id"], "unknown_agent", f"Agent {msg['agent_id']} not found" diff --git a/tests/components/backup/test_manager.py b/tests/components/backup/test_manager.py index 2ba9f3bba4b..ec6e91c8f05 100644 --- a/tests/components/backup/test_manager.py +++ b/tests/components/backup/test_manager.py @@ -18,7 +18,11 @@ from homeassistant.components.backup import ( BaseBackup, backup as local_backup_platform, ) -from homeassistant.components.backup.manager import BackupProgress +from homeassistant.components.backup.const import DATA_MANAGER +from homeassistant.components.backup.manager import ( + BackupProgress, + CoreBackupReaderWriter, +) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component @@ -71,7 +75,8 @@ async def _mock_backup_generation( assert manager.backup_task is not None assert progress == [] - backup = await manager.backup_task + backup, _ = await manager.backup_task + await manager.finish_backup_task assert progress == [BackupProgress(done=True, stage=None, success=True)] assert mocked_json_bytes.call_count == 1 @@ -132,13 +137,13 @@ async def _setup_backup_platform( async def test_constructor(hass: HomeAssistant) -> None: """Test BackupManager constructor.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) assert manager.temp_backup_dir.as_posix() == hass.config.path("tmp_backups") async def test_load_backups(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None: """Test loading backups.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform) await manager.load_platforms() @@ -170,7 +175,7 @@ async def test_load_backups_with_exception( caplog: pytest.LogCaptureFixture, ) -> None: """Test loading backups with exception.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform) await manager.load_platforms() @@ -194,7 +199,7 @@ async def test_deleting_backup( caplog: pytest.LogCaptureFixture, ) -> None: """Test deleting backup.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform) await manager.load_platforms() @@ -213,7 +218,7 @@ async def test_deleting_non_existing_backup( caplog: pytest.LogCaptureFixture, ) -> None: """Test deleting not existing backup.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform) await manager.load_platforms() @@ -227,7 +232,7 @@ async def test_getting_backup_that_does_not_exist( caplog: pytest.LogCaptureFixture, ) -> None: """Test getting backup that does not exist.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform) await manager.load_platforms() @@ -253,7 +258,7 @@ async def test_getting_backup_that_does_not_exist( async def test_async_create_backup_when_backing_up(hass: HomeAssistant) -> None: """Test generate backup.""" event = asyncio.Event() - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) manager.backup_task = hass.async_create_task(event.wait()) with pytest.raises(HomeAssistantError, match="Backup already in progress"): await manager.async_create_backup( @@ -279,7 +284,7 @@ async def test_async_create_backup_wrong_agent_id( hass: HomeAssistant, agent_ids: list[str], expected_error: str ) -> None: """Test generate backup.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) with pytest.raises(HomeAssistantError, match=expected_error): await manager.async_create_backup( addons_included=[], @@ -320,7 +325,8 @@ async def test_async_create_backup( backup_directory: str, ) -> None: """Test generate backup.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) + hass.data[DATA_MANAGER] = manager await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform) await _setup_backup_platform( @@ -358,7 +364,7 @@ async def test_loading_platforms( caplog: pytest.LogCaptureFixture, ) -> None: """Test loading backup platforms.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) assert not manager.platforms @@ -383,7 +389,7 @@ async def test_loading_agents( caplog: pytest.LogCaptureFixture, ) -> None: """Test loading backup agents.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) assert not manager.platforms @@ -407,7 +413,7 @@ async def test_not_loading_bad_platforms( caplog: pytest.LogCaptureFixture, ) -> None: """Test loading backup platforms.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) assert not manager.platforms @@ -424,7 +430,7 @@ async def test_exception_plaform_pre( hass: HomeAssistant, mocked_json_bytes: Mock, mocked_tarfile: Mock ) -> None: """Test exception in pre step.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) manager.loaded_backups = True async def _mock_step(hass: HomeAssistant) -> None: @@ -447,7 +453,7 @@ async def test_exception_plaform_post( hass: HomeAssistant, mocked_json_bytes: Mock, mocked_tarfile: Mock ) -> None: """Test exception in post step.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) manager.loaded_backups = True async def _mock_step(hass: HomeAssistant) -> None: @@ -471,7 +477,7 @@ async def test_async_receive_backup( caplog: pytest.LogCaptureFixture, ) -> None: """Test receiving a backup file.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform) await manager.load_platforms() @@ -516,7 +522,8 @@ async def test_async_trigger_restore( caplog: pytest.LogCaptureFixture, ) -> None: """Test trigger restore.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) + hass.data[DATA_MANAGER] = manager await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform) await manager.load_platforms() @@ -545,7 +552,8 @@ async def test_async_trigger_restore_with_password( caplog: pytest.LogCaptureFixture, ) -> None: """Test trigger restore.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) + hass.data[DATA_MANAGER] = manager await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform) await manager.load_platforms() @@ -573,7 +581,7 @@ async def test_async_trigger_restore_with_password( async def test_async_trigger_restore_missing_backup(hass: HomeAssistant) -> None: """Test trigger restore.""" - manager = BackupManager(hass) + manager = BackupManager(hass, CoreBackupReaderWriter(hass)) await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform) await manager.load_platforms()