mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 01:08:12 +00:00
Add backup helper (#139199)
* Add backup helper * Add hassio to stage 1 * Apply same changes to newly merged `webdav` and `azure_storage` to fix inflight conflict * Address comments, add tests --------- Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
c386abd49d
commit
bf190a8a73
@ -74,6 +74,7 @@ from .core_config import async_process_ha_core_config
|
||||
from .exceptions import HomeAssistantError
|
||||
from .helpers import (
|
||||
area_registry,
|
||||
backup,
|
||||
category_registry,
|
||||
config_validation as cv,
|
||||
device_registry,
|
||||
@ -163,16 +164,6 @@ FRONTEND_INTEGRATIONS = {
|
||||
# integrations can be removed and database migration status is
|
||||
# visible in frontend
|
||||
"frontend",
|
||||
# Hassio is an after dependency of backup, after dependencies
|
||||
# are not promoted from stage 2 to earlier stages, so we need to
|
||||
# add it here. Hassio needs to be setup before backup, otherwise
|
||||
# the backup integration will think we are a container/core install
|
||||
# when using HAOS or Supervised install.
|
||||
"hassio",
|
||||
# Backup is an after dependency of frontend, after dependencies
|
||||
# are not promoted from stage 2 to earlier stages, so we need to
|
||||
# add it here.
|
||||
"backup",
|
||||
}
|
||||
# Stage 0 is divided into substages. Each substage has a name, a set of integrations and a timeout.
|
||||
# The substage containing recorder should have no timeout, as it could cancel a database migration.
|
||||
@ -206,6 +197,8 @@ STAGE_1_INTEGRATIONS = {
|
||||
"mqtt_eventstream",
|
||||
# To provide account link implementations
|
||||
"cloud",
|
||||
# Ensure supervisor is available
|
||||
"hassio",
|
||||
}
|
||||
|
||||
DEFAULT_INTEGRATIONS = {
|
||||
@ -905,6 +898,10 @@ async def _async_set_up_integrations(
|
||||
if "recorder" in domains_to_setup:
|
||||
recorder.async_initialize_recorder(hass)
|
||||
|
||||
# Initialize backup
|
||||
if "backup" in domains_to_setup:
|
||||
backup.async_initialize_backup(hass)
|
||||
|
||||
stage_0_and_1_domains: list[tuple[str, set[str], int | None]] = [
|
||||
*(
|
||||
(name, domain_group & domains_to_setup, timeout)
|
||||
|
@ -1,8 +1,8 @@
|
||||
"""The Backup integration."""
|
||||
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.backup import DATA_BACKUP
|
||||
from homeassistant.helpers.hassio import is_hassio
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
@ -32,6 +32,7 @@ from .manager import (
|
||||
IdleEvent,
|
||||
IncorrectPasswordError,
|
||||
ManagerBackup,
|
||||
ManagerStateEvent,
|
||||
NewBackup,
|
||||
RestoreBackupEvent,
|
||||
RestoreBackupStage,
|
||||
@ -63,12 +64,12 @@ __all__ = [
|
||||
"IncorrectPasswordError",
|
||||
"LocalBackupAgent",
|
||||
"ManagerBackup",
|
||||
"ManagerStateEvent",
|
||||
"NewBackup",
|
||||
"RestoreBackupEvent",
|
||||
"RestoreBackupStage",
|
||||
"RestoreBackupState",
|
||||
"WrittenBackup",
|
||||
"async_get_manager",
|
||||
"suggested_filename",
|
||||
"suggested_filename_from_name_date",
|
||||
]
|
||||
@ -91,7 +92,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
||||
backup_manager = BackupManager(hass, reader_writer)
|
||||
hass.data[DATA_MANAGER] = backup_manager
|
||||
await backup_manager.async_setup()
|
||||
try:
|
||||
await backup_manager.async_setup()
|
||||
except Exception as err:
|
||||
hass.data[DATA_BACKUP].manager_ready.set_exception(err)
|
||||
raise
|
||||
else:
|
||||
hass.data[DATA_BACKUP].manager_ready.set_result(None)
|
||||
|
||||
async_register_websocket_handlers(hass, with_hassio)
|
||||
|
||||
@ -122,15 +129,3 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
async_register_http_views(hass)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@callback
|
||||
def async_get_manager(hass: HomeAssistant) -> BackupManager:
|
||||
"""Get the backup manager instance.
|
||||
|
||||
Raises HomeAssistantError if the backup integration is not available.
|
||||
"""
|
||||
if DATA_MANAGER not in hass.data:
|
||||
raise HomeAssistantError("Backup integration is not available")
|
||||
|
||||
return hass.data[DATA_MANAGER]
|
||||
|
38
homeassistant/components/backup/basic_websocket.py
Normal file
38
homeassistant/components/backup/basic_websocket.py
Normal file
@ -0,0 +1,38 @@
|
||||
"""Websocket commands for the Backup integration."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.backup import async_subscribe_events
|
||||
|
||||
from .const import DATA_MANAGER
|
||||
from .manager import ManagerStateEvent
|
||||
|
||||
|
||||
@callback
|
||||
def async_register_websocket_handlers(hass: HomeAssistant) -> None:
|
||||
"""Register websocket commands."""
|
||||
websocket_api.async_register_command(hass, handle_subscribe_events)
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.websocket_command({vol.Required("type"): "backup/subscribe_events"})
|
||||
@websocket_api.async_response
|
||||
async def handle_subscribe_events(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Subscribe to backup events."""
|
||||
|
||||
def on_event(event: ManagerStateEvent) -> None:
|
||||
connection.send_message(websocket_api.event_message(msg["id"], event))
|
||||
|
||||
if DATA_MANAGER in hass.data:
|
||||
manager = hass.data[DATA_MANAGER]
|
||||
on_event(manager.last_event)
|
||||
connection.subscriptions[msg["id"]] = async_subscribe_events(hass, on_event)
|
||||
connection.send_result(msg["id"])
|
@ -33,6 +33,7 @@ from homeassistant.helpers import (
|
||||
integration_platform,
|
||||
issue_registry as ir,
|
||||
)
|
||||
from homeassistant.helpers.backup import DATA_BACKUP
|
||||
from homeassistant.helpers.json import json_bytes
|
||||
from homeassistant.util import dt as dt_util, json as json_util
|
||||
|
||||
@ -332,7 +333,9 @@ class BackupManager:
|
||||
# Latest backup event and backup event subscribers
|
||||
self.last_event: ManagerStateEvent = IdleEvent()
|
||||
self.last_non_idle_event: ManagerStateEvent | None = None
|
||||
self._backup_event_subscriptions: list[Callable[[ManagerStateEvent], None]] = []
|
||||
self._backup_event_subscriptions = hass.data[
|
||||
DATA_BACKUP
|
||||
].backup_event_subscriptions
|
||||
|
||||
async def async_setup(self) -> None:
|
||||
"""Set up the backup manager."""
|
||||
@ -1279,19 +1282,6 @@ class BackupManager:
|
||||
for subscription in self._backup_event_subscriptions:
|
||||
subscription(event)
|
||||
|
||||
@callback
|
||||
def async_subscribe_events(
|
||||
self,
|
||||
on_event: Callable[[ManagerStateEvent], None],
|
||||
) -> Callable[[], None]:
|
||||
"""Subscribe events."""
|
||||
|
||||
def remove_subscription() -> None:
|
||||
self._backup_event_subscriptions.remove(on_event)
|
||||
|
||||
self._backup_event_subscriptions.append(on_event)
|
||||
return remove_subscription
|
||||
|
||||
def _update_issue_backup_failed(self) -> None:
|
||||
"""Update issue registry when a backup fails."""
|
||||
ir.async_create_issue(
|
||||
|
@ -10,11 +10,7 @@ from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from .config import Day, ScheduleRecurrence
|
||||
from .const import DATA_MANAGER, LOGGER
|
||||
from .manager import (
|
||||
DecryptOnDowloadNotSupported,
|
||||
IncorrectPasswordError,
|
||||
ManagerStateEvent,
|
||||
)
|
||||
from .manager import DecryptOnDowloadNotSupported, IncorrectPasswordError
|
||||
from .models import BackupNotFound, Folder
|
||||
|
||||
|
||||
@ -34,7 +30,6 @@ def async_register_websocket_handlers(hass: HomeAssistant, with_hassio: bool) ->
|
||||
websocket_api.async_register_command(hass, handle_create_with_automatic_settings)
|
||||
websocket_api.async_register_command(hass, handle_delete)
|
||||
websocket_api.async_register_command(hass, handle_restore)
|
||||
websocket_api.async_register_command(hass, handle_subscribe_events)
|
||||
|
||||
websocket_api.async_register_command(hass, handle_config_info)
|
||||
websocket_api.async_register_command(hass, handle_config_update)
|
||||
@ -401,22 +396,3 @@ def handle_config_update(
|
||||
changes.pop("type")
|
||||
manager.config.update(**changes)
|
||||
connection.send_result(msg["id"])
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.websocket_command({vol.Required("type"): "backup/subscribe_events"})
|
||||
@websocket_api.async_response
|
||||
async def handle_subscribe_events(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Subscribe to backup events."""
|
||||
|
||||
def on_event(event: ManagerStateEvent) -> None:
|
||||
connection.send_message(websocket_api.event_message(msg["id"], event))
|
||||
|
||||
manager = hass.data[DATA_MANAGER]
|
||||
on_event(manager.last_event)
|
||||
connection.subscriptions[msg["id"]] = manager.async_subscribe_events(on_event)
|
||||
connection.send_result(msg["id"])
|
||||
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"domain": "frontend",
|
||||
"name": "Home Assistant Frontend",
|
||||
"after_dependencies": ["backup"],
|
||||
"codeowners": ["@home-assistant/frontend"],
|
||||
"dependencies": [
|
||||
"api",
|
||||
|
@ -45,13 +45,13 @@ from homeassistant.components.backup import (
|
||||
RestoreBackupStage,
|
||||
RestoreBackupState,
|
||||
WrittenBackup,
|
||||
async_get_manager as async_get_backup_manager,
|
||||
suggested_filename as suggested_backup_filename,
|
||||
suggested_filename_from_name_date,
|
||||
)
|
||||
from homeassistant.const import __version__ as HAVERSION
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.backup import async_get_manager as async_get_backup_manager
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.enum import try_parse_enum
|
||||
@ -751,7 +751,7 @@ async def backup_addon_before_update(
|
||||
|
||||
async def backup_core_before_update(hass: HomeAssistant) -> None:
|
||||
"""Prepare for updating core."""
|
||||
backup_manager = async_get_backup_manager(hass)
|
||||
backup_manager = await async_get_backup_manager(hass)
|
||||
client = get_supervisor_client(hass)
|
||||
|
||||
try:
|
||||
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"domain": "onboarding",
|
||||
"name": "Home Assistant Onboarding",
|
||||
"after_dependencies": ["backup"],
|
||||
"codeowners": ["@home-assistant/core"],
|
||||
"dependencies": ["auth", "http", "person"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/onboarding",
|
||||
|
@ -20,7 +20,6 @@ from homeassistant.components.backup import (
|
||||
BackupManager,
|
||||
Folder,
|
||||
IncorrectPasswordError,
|
||||
async_get_manager as async_get_backup_manager,
|
||||
http as backup_http,
|
||||
)
|
||||
from homeassistant.components.http import KEY_HASS, KEY_HASS_REFRESH_TOKEN_ID
|
||||
@ -29,6 +28,7 @@ from homeassistant.components.http.view import HomeAssistantView
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import area_registry as ar
|
||||
from homeassistant.helpers.backup import async_get_manager as async_get_backup_manager
|
||||
from homeassistant.helpers.system_info import async_get_system_info
|
||||
from homeassistant.helpers.translation import async_get_translations
|
||||
from homeassistant.setup import async_setup_component
|
||||
@ -341,7 +341,7 @@ def with_backup_manager[_ViewT: BackupOnboardingView, **_P](
|
||||
raise HTTPUnauthorized
|
||||
|
||||
try:
|
||||
manager = async_get_backup_manager(request.app[KEY_HASS])
|
||||
manager = await async_get_backup_manager(request.app[KEY_HASS])
|
||||
except HomeAssistantError:
|
||||
return self.json(
|
||||
{"code": "backup_disabled"},
|
||||
|
70
homeassistant/helpers/backup.py
Normal file
70
homeassistant/helpers/backup.py
Normal file
@ -0,0 +1,70 @@
|
||||
"""Helpers for the backup integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.components.backup import BackupManager, ManagerStateEvent
|
||||
|
||||
DATA_BACKUP: HassKey[BackupData] = HassKey("backup_data")
|
||||
DATA_MANAGER: HassKey[BackupManager] = HassKey("backup")
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class BackupData:
|
||||
"""Backup data stored in hass.data."""
|
||||
|
||||
backup_event_subscriptions: list[Callable[[ManagerStateEvent], None]] = field(
|
||||
default_factory=list
|
||||
)
|
||||
manager_ready: asyncio.Future[None] = field(default_factory=asyncio.Future)
|
||||
|
||||
|
||||
@callback
|
||||
def async_initialize_backup(hass: HomeAssistant) -> None:
|
||||
"""Initialize backup data.
|
||||
|
||||
This creates the BackupData instance stored in hass.data[DATA_BACKUP] and
|
||||
registers the basic backup websocket API which is used by frontend to subscribe
|
||||
to backup events.
|
||||
"""
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from homeassistant.components.backup import basic_websocket
|
||||
|
||||
hass.data[DATA_BACKUP] = BackupData()
|
||||
basic_websocket.async_register_websocket_handlers(hass)
|
||||
|
||||
|
||||
async def async_get_manager(hass: HomeAssistant) -> BackupManager:
|
||||
"""Get the backup manager instance.
|
||||
|
||||
Raises HomeAssistantError if the backup integration is not available.
|
||||
"""
|
||||
if DATA_BACKUP not in hass.data:
|
||||
raise HomeAssistantError("Backup integration is not available")
|
||||
|
||||
await hass.data[DATA_BACKUP].manager_ready
|
||||
return hass.data[DATA_MANAGER]
|
||||
|
||||
|
||||
@callback
|
||||
def async_subscribe_events(
|
||||
hass: HomeAssistant,
|
||||
on_event: Callable[[ManagerStateEvent], None],
|
||||
) -> Callable[[], None]:
|
||||
"""Subscribe to backup events."""
|
||||
backup_event_subscriptions = hass.data[DATA_BACKUP].backup_event_subscriptions
|
||||
|
||||
def remove_subscription() -> None:
|
||||
backup_event_subscriptions.remove(on_event)
|
||||
|
||||
backup_event_subscriptions.append(on_event)
|
||||
return remove_subscription
|
@ -175,6 +175,10 @@ IGNORE_VIOLATIONS = {
|
||||
"logbook",
|
||||
# Temporary needed for migration until 2024.10
|
||||
("conversation", "assist_pipeline"),
|
||||
# The onboarding integration provides a limited backup API used during
|
||||
# onboarding. The onboarding integration waits for the backup manager
|
||||
# to be ready before calling any backup functionality.
|
||||
("onboarding", "backup"),
|
||||
}
|
||||
|
||||
|
||||
|
@ -19,6 +19,7 @@ from homeassistant.components.azure_storage.const import (
|
||||
)
|
||||
from homeassistant.components.backup import DOMAIN as BACKUP_DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.backup import async_initialize_backup
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import setup_integration
|
||||
@ -38,6 +39,7 @@ async def setup_backup_integration(
|
||||
patch("homeassistant.components.backup.is_hassio", return_value=False),
|
||||
patch("homeassistant.components.backup.store.STORE_DELAY_SAVE", 0),
|
||||
):
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, BACKUP_DOMAIN, {})
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
|
@ -18,6 +18,7 @@ from homeassistant.components.backup import (
|
||||
)
|
||||
from homeassistant.components.backup.const import DATA_MANAGER
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.backup import async_initialize_backup
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import mock_platform
|
||||
@ -125,6 +126,7 @@ async def setup_backup_integration(
|
||||
) -> dict[str, Mock]:
|
||||
"""Set up the Backup integration."""
|
||||
backups = backups or {}
|
||||
async_initialize_backup(hass)
|
||||
with (
|
||||
patch("homeassistant.components.backup.is_hassio", return_value=with_hassio),
|
||||
patch(
|
||||
|
@ -5768,3 +5768,20 @@
|
||||
'type': 'event',
|
||||
})
|
||||
# ---
|
||||
# name: test_subscribe_event_early
|
||||
dict({
|
||||
'event': dict({
|
||||
'manager_state': 'idle',
|
||||
}),
|
||||
'id': 1,
|
||||
'type': 'event',
|
||||
})
|
||||
# ---
|
||||
# name: test_subscribe_event_early.1
|
||||
dict({
|
||||
'id': 1,
|
||||
'result': None,
|
||||
'success': True,
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
|
@ -14,6 +14,7 @@ from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.backup import DOMAIN, AgentBackup
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.backup import async_initialize_backup
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .common import (
|
||||
@ -63,6 +64,7 @@ async def test_load_backups(
|
||||
side_effect: Exception | None,
|
||||
) -> None:
|
||||
"""Test load backups."""
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
client = await hass_ws_client(hass)
|
||||
@ -82,6 +84,7 @@ async def test_upload(
|
||||
hass_client: ClientSessionGenerator,
|
||||
) -> None:
|
||||
"""Test upload backup."""
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
client = await hass_client()
|
||||
@ -137,6 +140,7 @@ async def test_delete_backup(
|
||||
unlink_path: Path | None,
|
||||
) -> None:
|
||||
"""Test delete backup."""
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
client = await hass_ws_client(hass)
|
||||
|
@ -27,6 +27,8 @@ from homeassistant.components.backup.manager import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.backup import async_initialize_backup
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .common import (
|
||||
LOCAL_AGENT_ID,
|
||||
@ -3264,6 +3266,29 @@ async def test_subscribe_event(
|
||||
assert await client.receive_json() == snapshot
|
||||
|
||||
|
||||
async def test_subscribe_event_early(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test subscribe event before backup integration has started."""
|
||||
async_initialize_backup(hass)
|
||||
await setup_backup_integration(hass, with_hassio=False)
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
await client.send_json_auto_id({"type": "backup/subscribe_events"})
|
||||
assert await client.receive_json() == snapshot
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
manager = hass.data[DATA_MANAGER]
|
||||
|
||||
manager.async_on_backup_event(
|
||||
CreateBackupEvent(stage=None, state=CreateBackupState.IN_PROGRESS, reason=None)
|
||||
)
|
||||
assert await client.receive_json() == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("agent_id", "backup_id", "password"),
|
||||
[
|
||||
|
@ -21,6 +21,7 @@ from homeassistant.components.cloud import DOMAIN
|
||||
from homeassistant.components.cloud.backup import async_register_backup_agents_listener
|
||||
from homeassistant.components.cloud.const import EVENT_CLOUD_EVENT
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.backup import async_initialize_backup
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.aiohttp import MockStreamReader
|
||||
@ -44,7 +45,8 @@ async def setup_integration(
|
||||
cloud: MagicMock,
|
||||
cloud_logged_in: None,
|
||||
) -> AsyncGenerator[None]:
|
||||
"""Set up cloud integration."""
|
||||
"""Set up cloud and backup integrations."""
|
||||
async_initialize_backup(hass)
|
||||
with (
|
||||
patch("homeassistant.components.backup.is_hassio", return_value=False),
|
||||
patch("homeassistant.components.backup.store.STORE_DELAY_SAVE", 0),
|
||||
|
@ -17,6 +17,7 @@ from homeassistant.components.backup import (
|
||||
)
|
||||
from homeassistant.components.google_drive import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.backup import async_initialize_backup
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .conftest import CONFIG_ENTRY_TITLE, TEST_AGENT_ID
|
||||
@ -63,7 +64,8 @@ async def setup_integration(
|
||||
config_entry: MockConfigEntry,
|
||||
mock_api: MagicMock,
|
||||
) -> None:
|
||||
"""Set up Google Drive integration."""
|
||||
"""Set up Google Drive and backup integrations."""
|
||||
async_initialize_backup(hass)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await async_setup_component(hass, BACKUP_DOMAIN, {BACKUP_DOMAIN: {}})
|
||||
mock_api.list_files = AsyncMock(
|
||||
|
@ -44,6 +44,7 @@ from homeassistant.components.backup import (
|
||||
from homeassistant.components.hassio import DOMAIN
|
||||
from homeassistant.components.hassio.backup import RESTORE_JOB_ID_ENV
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.backup import async_initialize_backup
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .test_init import MOCK_ENVIRON
|
||||
@ -320,6 +321,7 @@ async def setup_backup_integration(
|
||||
hass: HomeAssistant, hassio_enabled: None, supervisor_client: AsyncMock
|
||||
) -> None:
|
||||
"""Set up Backup integration."""
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, BACKUP_DOMAIN, {BACKUP_DOMAIN: {}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -432,6 +434,7 @@ async def test_agent_info(
|
||||
client = await hass_ws_client(hass)
|
||||
supervisor_client.mounts.info.return_value = mounts
|
||||
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, BACKUP_DOMAIN, {BACKUP_DOMAIN: {}})
|
||||
|
||||
await client.send_json_auto_id({"type": "backup/agents/info"})
|
||||
@ -1287,6 +1290,7 @@ async def test_reader_writer_create_per_agent_encryption(
|
||||
)
|
||||
supervisor_client.jobs.get_job.return_value = TEST_JOB_NOT_DONE
|
||||
supervisor_client.mounts.info.return_value = mounts
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, BACKUP_DOMAIN, {BACKUP_DOMAIN: {}})
|
||||
|
||||
for command in commands:
|
||||
@ -2352,6 +2356,7 @@ async def test_restore_progress_after_restart(
|
||||
|
||||
supervisor_client.jobs.get_job.return_value = get_job_result
|
||||
|
||||
async_initialize_backup(hass)
|
||||
with patch.dict(os.environ, MOCK_ENVIRON | {RESTORE_JOB_ID_ENV: TEST_JOB_ID}):
|
||||
assert await async_setup_component(hass, BACKUP_DOMAIN, {BACKUP_DOMAIN: {}})
|
||||
|
||||
@ -2375,6 +2380,7 @@ async def test_restore_progress_after_restart_report_progress(
|
||||
|
||||
supervisor_client.jobs.get_job.return_value = TEST_JOB_NOT_DONE
|
||||
|
||||
async_initialize_backup(hass)
|
||||
with patch.dict(os.environ, MOCK_ENVIRON | {RESTORE_JOB_ID_ENV: TEST_JOB_ID}):
|
||||
assert await async_setup_component(hass, BACKUP_DOMAIN, {BACKUP_DOMAIN: {}})
|
||||
|
||||
@ -2457,6 +2463,7 @@ async def test_restore_progress_after_restart_unknown_job(
|
||||
|
||||
supervisor_client.jobs.get_job.side_effect = SupervisorError
|
||||
|
||||
async_initialize_backup(hass)
|
||||
with patch.dict(os.environ, MOCK_ENVIRON | {RESTORE_JOB_ID_ENV: TEST_JOB_ID}):
|
||||
assert await async_setup_component(hass, BACKUP_DOMAIN, {BACKUP_DOMAIN: {}})
|
||||
|
||||
@ -2556,6 +2563,7 @@ async def test_config_load_config_info(
|
||||
|
||||
hass_storage.update(storage_data)
|
||||
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, BACKUP_DOMAIN, {BACKUP_DOMAIN: {}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
@ -18,6 +18,7 @@ from homeassistant.components.hassio.const import REQUEST_REFRESH_DELAY
|
||||
from homeassistant.const import __version__ as HAVERSION
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.backup import async_initialize_backup
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
@ -235,6 +236,13 @@ async def test_update_addon(hass: HomeAssistant, update_addon: AsyncMock) -> Non
|
||||
update_addon.assert_called_once_with("test", StoreAddonUpdate(backup=False))
|
||||
|
||||
|
||||
async def setup_backup_integration(hass: HomeAssistant) -> None:
|
||||
"""Set up the backup integration."""
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("commands", "default_mount", "expected_kwargs"),
|
||||
[
|
||||
@ -318,8 +326,7 @@ async def test_update_addon_with_backup(
|
||||
{"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
|
||||
)
|
||||
assert result
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
await setup_backup_integration(hass)
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
for command in commands:
|
||||
@ -413,8 +420,7 @@ async def test_update_addon_with_backup_removes_old_backups(
|
||||
{"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
|
||||
)
|
||||
assert result
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
await setup_backup_integration(hass)
|
||||
|
||||
supervisor_client.mounts.info.return_value.default_backup_mount = None
|
||||
with (
|
||||
@ -588,8 +594,7 @@ async def test_update_core_with_backup(
|
||||
{"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
|
||||
)
|
||||
assert result
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
await setup_backup_integration(hass)
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
for command in commands:
|
||||
@ -691,8 +696,7 @@ async def test_update_addon_with_backup_and_error(
|
||||
{"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
|
||||
)
|
||||
assert result
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
await setup_backup_integration(hass)
|
||||
|
||||
supervisor_client.homeassistant.update.return_value = None
|
||||
supervisor_client.mounts.info.return_value.default_backup_mount = None
|
||||
@ -811,8 +815,7 @@ async def test_update_core_with_backup_and_error(
|
||||
{"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
|
||||
)
|
||||
assert result
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
await setup_backup_integration(hass)
|
||||
|
||||
supervisor_client.homeassistant.update.return_value = None
|
||||
supervisor_client.mounts.info.return_value.default_backup_mount = None
|
||||
|
@ -26,6 +26,7 @@ from homeassistant.components.hassio.const import (
|
||||
)
|
||||
from homeassistant.const import __version__ as HAVERSION
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.backup import async_initialize_backup
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
@ -355,6 +356,13 @@ async def test_update_addon(
|
||||
update_addon.assert_called_once_with("test", StoreAddonUpdate(backup=False))
|
||||
|
||||
|
||||
async def setup_backup_integration(hass: HomeAssistant) -> None:
|
||||
"""Set up the backup integration."""
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("commands", "default_mount", "expected_kwargs"),
|
||||
[
|
||||
@ -438,8 +446,7 @@ async def test_update_addon_with_backup(
|
||||
{"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
|
||||
)
|
||||
assert result
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
await setup_backup_integration(hass)
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
for command in commands:
|
||||
@ -533,8 +540,7 @@ async def test_update_addon_with_backup_removes_old_backups(
|
||||
{"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
|
||||
)
|
||||
assert result
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
await setup_backup_integration(hass)
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
supervisor_client.mounts.info.return_value.default_backup_mount = None
|
||||
@ -686,8 +692,7 @@ async def test_update_core_with_backup(
|
||||
{"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
|
||||
)
|
||||
assert result
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
await setup_backup_integration(hass)
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
for command in commands:
|
||||
@ -766,8 +771,7 @@ async def test_update_addon_with_backup_and_error(
|
||||
{"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
|
||||
)
|
||||
assert result
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
await setup_backup_integration(hass)
|
||||
|
||||
supervisor_client.homeassistant.update.return_value = None
|
||||
supervisor_client.mounts.info.return_value.default_backup_mount = None
|
||||
@ -834,8 +838,7 @@ async def test_update_core_with_backup_and_error(
|
||||
{"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
|
||||
)
|
||||
assert result
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
await setup_backup_integration(hass)
|
||||
|
||||
supervisor_client.homeassistant.update.return_value = None
|
||||
supervisor_client.mounts.info.return_value.default_backup_mount = None
|
||||
|
@ -15,6 +15,7 @@ from homeassistant.components.backup import (
|
||||
from homeassistant.components.kitchen_sink import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import instance_id
|
||||
from homeassistant.helpers.backup import async_initialize_backup
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
||||
@ -35,7 +36,8 @@ async def backup_only() -> AsyncGenerator[None]:
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def setup_integration(hass: HomeAssistant) -> AsyncGenerator[None]:
|
||||
"""Set up Kitchen Sink integration."""
|
||||
"""Set up Kitchen Sink and backup integrations."""
|
||||
async_initialize_backup(hass)
|
||||
with patch("homeassistant.components.backup.is_hassio", return_value=False):
|
||||
assert await async_setup_component(hass, BACKUP_DOMAIN, {BACKUP_DOMAIN: {}})
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
|
||||
|
@ -16,6 +16,7 @@ from homeassistant.components.onboarding import const, views
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import area_registry as ar
|
||||
from homeassistant.helpers.backup import async_initialize_backup
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import mock_storage
|
||||
@ -765,6 +766,7 @@ async def test_onboarding_backup_info(
|
||||
mock_storage(hass_storage, {"done": []})
|
||||
|
||||
assert await async_setup_component(hass, "onboarding", {})
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -881,6 +883,7 @@ async def test_onboarding_backup_restore(
|
||||
mock_storage(hass_storage, {"done": []})
|
||||
|
||||
assert await async_setup_component(hass, "onboarding", {})
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -977,6 +980,7 @@ async def test_onboarding_backup_restore_error(
|
||||
mock_storage(hass_storage, {"done": []})
|
||||
|
||||
assert await async_setup_component(hass, "onboarding", {})
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -1020,6 +1024,7 @@ async def test_onboarding_backup_restore_unexpected_error(
|
||||
mock_storage(hass_storage, {"done": []})
|
||||
|
||||
assert await async_setup_component(hass, "onboarding", {})
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -1045,6 +1050,7 @@ async def test_onboarding_backup_upload(
|
||||
mock_storage(hass_storage, {"done": []})
|
||||
|
||||
assert await async_setup_component(hass, "onboarding", {})
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
@ -21,6 +21,7 @@ from homeassistant.components.onedrive.backup import (
|
||||
from homeassistant.components.onedrive.const import DATA_BACKUP_AGENT_LISTENERS, DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_REAUTH
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.backup import async_initialize_backup
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import setup_integration
|
||||
@ -35,7 +36,8 @@ async def setup_backup_integration(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> AsyncGenerator[None]:
|
||||
"""Set up onedrive integration."""
|
||||
"""Set up onedrive and backup integrations."""
|
||||
async_initialize_backup(hass)
|
||||
with (
|
||||
patch("homeassistant.components.backup.is_hassio", return_value=False),
|
||||
patch("homeassistant.components.backup.store.STORE_DELAY_SAVE", 0),
|
||||
|
@ -28,6 +28,7 @@ from homeassistant.const import (
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.backup import async_initialize_backup
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.aiohttp import MockStreamReader
|
||||
|
||||
@ -164,7 +165,8 @@ async def setup_dsm_with_filestation(
|
||||
hass: HomeAssistant,
|
||||
mock_dsm_with_filestation: MagicMock,
|
||||
):
|
||||
"""Mock setup of synology dsm config entry."""
|
||||
"""Mock setup of synology dsm config entry and backup integration."""
|
||||
async_initialize_backup(hass)
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.synology_dsm.common.SynologyDSM",
|
||||
@ -222,6 +224,7 @@ async def test_agents_not_loaded(
|
||||
) -> None:
|
||||
"""Test backup agent with no loaded config entry."""
|
||||
with patch("homeassistant.components.backup.is_hassio", return_value=False):
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, BACKUP_DOMAIN, {BACKUP_DOMAIN: {}})
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
|
||||
await hass.async_block_till_done()
|
||||
|
@ -13,6 +13,7 @@ from homeassistant.components.backup import DOMAIN as BACKUP_DOMAIN, AgentBackup
|
||||
from homeassistant.components.webdav.backup import async_register_backup_agents_listener
|
||||
from homeassistant.components.webdav.const import DATA_BACKUP_AGENT_LISTENERS, DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.backup import async_initialize_backup
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .const import BACKUP_METADATA, MOCK_LIST_WITH_INFOS
|
||||
@ -30,6 +31,7 @@ async def setup_backup_integration(
|
||||
patch("homeassistant.components.backup.is_hassio", return_value=False),
|
||||
patch("homeassistant.components.backup.store.STORE_DELAY_SAVE", 0),
|
||||
):
|
||||
async_initialize_backup(hass)
|
||||
assert await async_setup_component(hass, BACKUP_DOMAIN, {})
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
|
42
tests/helpers/test_backup.py
Normal file
42
tests/helpers/test_backup.py
Normal file
@ -0,0 +1,42 @@
|
||||
"""The tests for the backup helpers."""
|
||||
|
||||
import asyncio
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.backup import DOMAIN as BACKUP_DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import backup as backup_helper
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
|
||||
async def test_async_get_manager(hass: HomeAssistant) -> None:
|
||||
"""Test async_get_manager."""
|
||||
backup_helper.async_initialize_backup(hass)
|
||||
task = asyncio.create_task(backup_helper.async_get_manager(hass))
|
||||
assert await async_setup_component(hass, BACKUP_DOMAIN, {})
|
||||
manager = await task
|
||||
assert manager is hass.data[backup_helper.DATA_MANAGER]
|
||||
|
||||
|
||||
async def test_async_get_manager_no_backup(hass: HomeAssistant) -> None:
|
||||
"""Test async_get_manager when the backup integration is not enabled."""
|
||||
with pytest.raises(HomeAssistantError, match="Backup integration is not available"):
|
||||
await backup_helper.async_get_manager(hass)
|
||||
|
||||
|
||||
async def test_async_get_manager_backup_failed_setup(hass: HomeAssistant) -> None:
|
||||
"""Test test_async_get_manager when the backup integration can't be set up."""
|
||||
backup_helper.async_initialize_backup(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.backup.manager.BackupManager.async_setup",
|
||||
side_effect=Exception("Boom!"),
|
||||
):
|
||||
assert not await async_setup_component(hass, BACKUP_DOMAIN, {})
|
||||
with (
|
||||
pytest.raises(Exception, match="Boom!"),
|
||||
):
|
||||
await backup_helper.async_get_manager(hass)
|
Loading…
x
Reference in New Issue
Block a user