mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
Add backup endpoints to the onboarding integration (#136051)
* Add backup endpoints to the onboarding integration * Add backup as after dependency of onboarding * Add test snapshots * Fix stale docstrings * Add utility function for getting the backup manager instance * Return backup_id when uploading backup * Change /api/onboarding/backup/restore to accept a JSON body * Fix with_backup_manager
This commit is contained in:
parent
706a01837c
commit
7249c02655
@ -53,6 +53,7 @@ __all__ = [
|
||||
"NewBackup",
|
||||
"RestoreBackupEvent",
|
||||
"WrittenBackup",
|
||||
"async_get_manager",
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
|
||||
|
@ -144,13 +144,17 @@ class DownloadBackupView(HomeAssistantView):
|
||||
|
||||
|
||||
class UploadBackupView(HomeAssistantView):
|
||||
"""Generate backup view."""
|
||||
"""Upload backup view."""
|
||||
|
||||
url = "/api/backup/upload"
|
||||
name = "api:backup:upload"
|
||||
|
||||
@require_admin
|
||||
async def post(self, request: Request) -> Response:
|
||||
"""Upload a backup file."""
|
||||
return await self._post(request)
|
||||
|
||||
async def _post(self, request: Request) -> Response:
|
||||
"""Upload a backup file."""
|
||||
try:
|
||||
agent_ids = request.query.getall("agent_id")
|
||||
@ -161,7 +165,9 @@ class UploadBackupView(HomeAssistantView):
|
||||
contents = cast(BodyPartReader, await reader.next())
|
||||
|
||||
try:
|
||||
await manager.async_receive_backup(contents=contents, agent_ids=agent_ids)
|
||||
backup_id = await manager.async_receive_backup(
|
||||
contents=contents, agent_ids=agent_ids
|
||||
)
|
||||
except OSError as err:
|
||||
return Response(
|
||||
body=f"Can't write backup file: {err}",
|
||||
@ -175,4 +181,4 @@ class UploadBackupView(HomeAssistantView):
|
||||
except asyncio.CancelledError:
|
||||
return Response(status=HTTPStatus.INTERNAL_SERVER_ERROR)
|
||||
|
||||
return Response(status=HTTPStatus.CREATED)
|
||||
return self.json({"backup_id": backup_id}, status_code=HTTPStatus.CREATED)
|
||||
|
@ -298,6 +298,7 @@ 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]] = []
|
||||
|
||||
async def async_setup(self) -> None:
|
||||
@ -620,7 +621,7 @@ class BackupManager:
|
||||
*,
|
||||
agent_ids: list[str],
|
||||
contents: aiohttp.BodyPartReader,
|
||||
) -> None:
|
||||
) -> str:
|
||||
"""Receive and store a backup file from upload."""
|
||||
if self.state is not BackupManagerState.IDLE:
|
||||
raise BackupManagerError(f"Backup manager busy: {self.state}")
|
||||
@ -632,7 +633,9 @@ class BackupManager:
|
||||
)
|
||||
)
|
||||
try:
|
||||
await self._async_receive_backup(agent_ids=agent_ids, contents=contents)
|
||||
backup_id = await self._async_receive_backup(
|
||||
agent_ids=agent_ids, contents=contents
|
||||
)
|
||||
except Exception:
|
||||
self.async_on_backup_event(
|
||||
ReceiveBackupEvent(
|
||||
@ -650,6 +653,7 @@ class BackupManager:
|
||||
state=ReceiveBackupState.COMPLETED,
|
||||
)
|
||||
)
|
||||
return backup_id
|
||||
finally:
|
||||
self.async_on_backup_event(IdleEvent())
|
||||
|
||||
@ -658,7 +662,7 @@ class BackupManager:
|
||||
*,
|
||||
agent_ids: list[str],
|
||||
contents: aiohttp.BodyPartReader,
|
||||
) -> None:
|
||||
) -> str:
|
||||
"""Receive and store a backup file from upload."""
|
||||
contents.chunk_size = BUF_SIZE
|
||||
self.async_on_backup_event(
|
||||
@ -687,6 +691,7 @@ class BackupManager:
|
||||
)
|
||||
await written_backup.release_stream()
|
||||
self.known_backups.add(written_backup.backup, agent_errors)
|
||||
return written_backup.backup.backup_id
|
||||
|
||||
async def async_create_backup(
|
||||
self,
|
||||
@ -1041,6 +1046,8 @@ class BackupManager:
|
||||
if (current_state := self.state) != (new_state := event.manager_state):
|
||||
LOGGER.debug("Backup state: %s -> %s", current_state, new_state)
|
||||
self.last_event = event
|
||||
if not isinstance(event, IdleEvent):
|
||||
self.last_non_idle_event = event
|
||||
for subscription in self._backup_event_subscriptions:
|
||||
subscription(event)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "onboarding",
|
||||
"name": "Home Assistant Onboarding",
|
||||
"after_dependencies": ["hassio"],
|
||||
"after_dependencies": ["backup", "hassio"],
|
||||
"codeowners": ["@home-assistant/core"],
|
||||
"dependencies": ["auth", "http", "person"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/onboarding",
|
||||
|
@ -3,9 +3,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Coroutine
|
||||
from collections.abc import Callable, Coroutine
|
||||
from functools import wraps
|
||||
from http import HTTPStatus
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
from typing import TYPE_CHECKING, Any, Concatenate, cast
|
||||
|
||||
from aiohttp import web
|
||||
from aiohttp.web_exceptions import HTTPUnauthorized
|
||||
@ -15,10 +16,18 @@ from homeassistant.auth.const import GROUP_ID_ADMIN
|
||||
from homeassistant.auth.providers.homeassistant import HassAuthProvider
|
||||
from homeassistant.components import person
|
||||
from homeassistant.components.auth import indieauth
|
||||
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
|
||||
from homeassistant.components.http.data_validator import RequestDataValidator
|
||||
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.hassio import is_hassio
|
||||
from homeassistant.helpers.system_info import async_get_system_info
|
||||
@ -50,6 +59,9 @@ async def async_setup(
|
||||
hass.http.register_view(CoreConfigOnboardingView(data, store))
|
||||
hass.http.register_view(IntegrationOnboardingView(data, store))
|
||||
hass.http.register_view(AnalyticsOnboardingView(data, store))
|
||||
hass.http.register_view(BackupInfoView(data))
|
||||
hass.http.register_view(RestoreBackupView(data))
|
||||
hass.http.register_view(UploadBackupView(data))
|
||||
|
||||
|
||||
class OnboardingView(HomeAssistantView):
|
||||
@ -312,6 +324,119 @@ class AnalyticsOnboardingView(_BaseOnboardingView):
|
||||
return self.json({})
|
||||
|
||||
|
||||
class BackupOnboardingView(HomeAssistantView):
|
||||
"""Backup onboarding view."""
|
||||
|
||||
requires_auth = False
|
||||
|
||||
def __init__(self, data: OnboardingStoreData) -> None:
|
||||
"""Initialize the view."""
|
||||
self._data = data
|
||||
|
||||
|
||||
def with_backup_manager[_ViewT: BackupOnboardingView, **_P](
|
||||
func: Callable[
|
||||
Concatenate[_ViewT, BackupManager, web.Request, _P],
|
||||
Coroutine[Any, Any, web.Response],
|
||||
],
|
||||
) -> Callable[Concatenate[_ViewT, web.Request, _P], Coroutine[Any, Any, web.Response]]:
|
||||
"""Home Assistant API decorator to check onboarding and inject manager."""
|
||||
|
||||
@wraps(func)
|
||||
async def with_backup(
|
||||
self: _ViewT,
|
||||
request: web.Request,
|
||||
*args: _P.args,
|
||||
**kwargs: _P.kwargs,
|
||||
) -> web.Response:
|
||||
"""Check admin and call function."""
|
||||
if self._data["done"]:
|
||||
raise HTTPUnauthorized
|
||||
|
||||
try:
|
||||
manager = async_get_backup_manager(request.app[KEY_HASS])
|
||||
except HomeAssistantError:
|
||||
return self.json(
|
||||
{"error": "backup_disabled"},
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
return await func(self, manager, request, *args, **kwargs)
|
||||
|
||||
return with_backup
|
||||
|
||||
|
||||
class BackupInfoView(BackupOnboardingView):
|
||||
"""Get backup info view."""
|
||||
|
||||
url = "/api/onboarding/backup/info"
|
||||
name = "api:onboarding:backup:info"
|
||||
|
||||
@with_backup_manager
|
||||
async def get(self, manager: BackupManager, request: web.Request) -> web.Response:
|
||||
"""Return backup info."""
|
||||
backups, _ = await manager.async_get_backups()
|
||||
return self.json(
|
||||
{
|
||||
"backups": [backup.as_frontend_json() for backup in backups.values()],
|
||||
"state": manager.state,
|
||||
"last_non_idle_event": manager.last_non_idle_event,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class RestoreBackupView(BackupOnboardingView):
|
||||
"""Restore backup view."""
|
||||
|
||||
url = "/api/onboarding/backup/restore"
|
||||
name = "api:onboarding:backup:restore"
|
||||
|
||||
@RequestDataValidator(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required("backup_id"): str,
|
||||
vol.Required("agent_id"): str,
|
||||
vol.Optional("password"): str,
|
||||
vol.Optional("restore_addons"): [str],
|
||||
vol.Optional("restore_database", default=True): bool,
|
||||
vol.Optional("restore_folders"): [vol.Coerce(Folder)],
|
||||
}
|
||||
)
|
||||
)
|
||||
@with_backup_manager
|
||||
async def post(
|
||||
self, manager: BackupManager, request: web.Request, data: dict[str, Any]
|
||||
) -> web.Response:
|
||||
"""Restore a backup."""
|
||||
try:
|
||||
await manager.async_restore_backup(
|
||||
data["backup_id"],
|
||||
agent_id=data["agent_id"],
|
||||
password=data.get("password"),
|
||||
restore_addons=data.get("restore_addons"),
|
||||
restore_database=data["restore_database"],
|
||||
restore_folders=data.get("restore_folders"),
|
||||
restore_homeassistant=True,
|
||||
)
|
||||
except IncorrectPasswordError:
|
||||
return self.json(
|
||||
{"message": "incorrect_password"}, status_code=HTTPStatus.BAD_REQUEST
|
||||
)
|
||||
return web.Response(status=HTTPStatus.OK)
|
||||
|
||||
|
||||
class UploadBackupView(BackupOnboardingView, backup_http.UploadBackupView):
|
||||
"""Upload backup view."""
|
||||
|
||||
url = "/api/onboarding/backup/upload"
|
||||
name = "api:onboarding:backup:upload"
|
||||
|
||||
@with_backup_manager
|
||||
async def post(self, manager: BackupManager, request: web.Request) -> web.Response:
|
||||
"""Upload a backup file."""
|
||||
return await self._post(request)
|
||||
|
||||
|
||||
@callback
|
||||
def _async_get_hass_provider(hass: HomeAssistant) -> HassAuthProvider:
|
||||
"""Get the Home Assistant auth provider."""
|
||||
|
@ -233,12 +233,14 @@ async def test_uploading_a_backup_file(
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.backup.manager.BackupManager.async_receive_backup",
|
||||
return_value=TEST_BACKUP_ABC123.backup_id,
|
||||
) as async_receive_backup_mock:
|
||||
resp = await client.post(
|
||||
"/api/backup/upload?agent_id=backup.local",
|
||||
data={"file": StringIO("test")},
|
||||
)
|
||||
assert resp.status == 201
|
||||
assert await resp.json() == {"backup_id": TEST_BACKUP_ABC123.backup_id}
|
||||
assert async_receive_backup_mock.called
|
||||
|
||||
|
||||
|
58
tests/components/onboarding/snapshots/test_views.ambr
Normal file
58
tests/components/onboarding/snapshots/test_views.ambr
Normal file
@ -0,0 +1,58 @@
|
||||
# serializer version: 1
|
||||
# name: test_onboarding_backup_info
|
||||
dict({
|
||||
'backups': list([
|
||||
dict({
|
||||
'addons': list([
|
||||
dict({
|
||||
'name': 'Test',
|
||||
'slug': 'test',
|
||||
'version': '1.0.0',
|
||||
}),
|
||||
]),
|
||||
'agent_ids': list([
|
||||
'backup.local',
|
||||
]),
|
||||
'backup_id': 'abc123',
|
||||
'database_included': True,
|
||||
'date': '1970-01-01T00:00:00.000Z',
|
||||
'failed_agent_ids': list([
|
||||
]),
|
||||
'folders': list([
|
||||
'media',
|
||||
'share',
|
||||
]),
|
||||
'homeassistant_included': True,
|
||||
'homeassistant_version': '2024.12.0',
|
||||
'name': 'Test',
|
||||
'protected': False,
|
||||
'size': 0,
|
||||
'with_automatic_settings': True,
|
||||
}),
|
||||
dict({
|
||||
'addons': list([
|
||||
]),
|
||||
'agent_ids': list([
|
||||
'test.remote',
|
||||
]),
|
||||
'backup_id': 'def456',
|
||||
'database_included': False,
|
||||
'date': '1980-01-01T00:00:00.000Z',
|
||||
'failed_agent_ids': list([
|
||||
]),
|
||||
'folders': list([
|
||||
'media',
|
||||
'share',
|
||||
]),
|
||||
'homeassistant_included': True,
|
||||
'homeassistant_version': '2024.12.0',
|
||||
'name': 'Test 2',
|
||||
'protected': False,
|
||||
'size': 1,
|
||||
'with_automatic_settings': None,
|
||||
}),
|
||||
]),
|
||||
'last_non_idle_event': None,
|
||||
'state': 'idle',
|
||||
})
|
||||
# ---
|
@ -3,13 +3,15 @@
|
||||
import asyncio
|
||||
from collections.abc import AsyncGenerator
|
||||
from http import HTTPStatus
|
||||
from io import StringIO
|
||||
import os
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
from unittest.mock import ANY, AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components import onboarding
|
||||
from homeassistant.components import backup, onboarding
|
||||
from homeassistant.components.onboarding import const, views
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import area_registry as ar
|
||||
@ -649,12 +651,28 @@ async def test_onboarding_installation_type(
|
||||
assert resp_content["installation_type"] == "Home Assistant Core"
|
||||
|
||||
|
||||
async def test_onboarding_installation_type_after_done(
|
||||
@pytest.mark.parametrize(
|
||||
("method", "view", "kwargs"),
|
||||
[
|
||||
("get", "installation_type", {}),
|
||||
("get", "backup/info", {}),
|
||||
(
|
||||
"post",
|
||||
"backup/restore",
|
||||
{"json": {"backup_id": "abc123", "agent_id": "test"}},
|
||||
),
|
||||
("post", "backup/upload", {}),
|
||||
],
|
||||
)
|
||||
async def test_onboarding_view_after_done(
|
||||
hass: HomeAssistant,
|
||||
hass_storage: dict[str, Any],
|
||||
hass_client: ClientSessionGenerator,
|
||||
method: str,
|
||||
view: str,
|
||||
kwargs: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test raising for installation type after onboarding."""
|
||||
"""Test raising after onboarding."""
|
||||
mock_storage(hass_storage, {"done": [const.STEP_USER]})
|
||||
|
||||
assert await async_setup_component(hass, "onboarding", {})
|
||||
@ -662,7 +680,7 @@ async def test_onboarding_installation_type_after_done(
|
||||
|
||||
client = await hass_client()
|
||||
|
||||
resp = await client.get("/api/onboarding/installation_type")
|
||||
resp = await client.request(method, f"/api/onboarding/{view}", **kwargs)
|
||||
|
||||
assert resp.status == 401
|
||||
|
||||
@ -726,3 +744,286 @@ async def test_complete_onboarding(
|
||||
listener_3 = Mock()
|
||||
onboarding.async_add_listener(hass, listener_3)
|
||||
listener_3.assert_called_once_with()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("method", "view", "kwargs"),
|
||||
[
|
||||
("get", "backup/info", {}),
|
||||
(
|
||||
"post",
|
||||
"backup/restore",
|
||||
{"json": {"backup_id": "abc123", "agent_id": "test"}},
|
||||
),
|
||||
("post", "backup/upload", {}),
|
||||
],
|
||||
)
|
||||
async def test_onboarding_backup_view_without_backup(
|
||||
hass: HomeAssistant,
|
||||
hass_storage: dict[str, Any],
|
||||
hass_client: ClientSessionGenerator,
|
||||
method: str,
|
||||
view: str,
|
||||
kwargs: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test interacting with backup wievs when backup integration is missing."""
|
||||
mock_storage(hass_storage, {"done": []})
|
||||
|
||||
assert await async_setup_component(hass, "onboarding", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
client = await hass_client()
|
||||
|
||||
resp = await client.request(method, f"/api/onboarding/{view}", **kwargs)
|
||||
|
||||
assert resp.status == 500
|
||||
assert await resp.json() == {"error": "backup_disabled"}
|
||||
|
||||
|
||||
async def test_onboarding_backup_info(
|
||||
hass: HomeAssistant,
|
||||
hass_storage: dict[str, Any],
|
||||
hass_client: ClientSessionGenerator,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test returning installation type during onboarding."""
|
||||
mock_storage(hass_storage, {"done": []})
|
||||
|
||||
assert await async_setup_component(hass, "onboarding", {})
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
client = await hass_client()
|
||||
|
||||
backups = {
|
||||
"abc123": backup.ManagerBackup(
|
||||
addons=[backup.AddonInfo(name="Test", slug="test", version="1.0.0")],
|
||||
backup_id="abc123",
|
||||
date="1970-01-01T00:00:00.000Z",
|
||||
database_included=True,
|
||||
extra_metadata={"instance_id": "abc123", "with_automatic_settings": True},
|
||||
folders=[backup.Folder.MEDIA, backup.Folder.SHARE],
|
||||
homeassistant_included=True,
|
||||
homeassistant_version="2024.12.0",
|
||||
name="Test",
|
||||
protected=False,
|
||||
size=0,
|
||||
agent_ids=["backup.local"],
|
||||
failed_agent_ids=[],
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"def456": backup.ManagerBackup(
|
||||
addons=[],
|
||||
backup_id="def456",
|
||||
date="1980-01-01T00:00:00.000Z",
|
||||
database_included=False,
|
||||
extra_metadata={
|
||||
"instance_id": "unknown_uuid",
|
||||
"with_automatic_settings": True,
|
||||
},
|
||||
folders=[backup.Folder.MEDIA, backup.Folder.SHARE],
|
||||
homeassistant_included=True,
|
||||
homeassistant_version="2024.12.0",
|
||||
name="Test 2",
|
||||
protected=False,
|
||||
size=1,
|
||||
agent_ids=["test.remote"],
|
||||
failed_agent_ids=[],
|
||||
with_automatic_settings=None,
|
||||
),
|
||||
}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.backup.manager.BackupManager.async_get_backups",
|
||||
return_value=(backups, {}),
|
||||
):
|
||||
resp = await client.get("/api/onboarding/backup/info")
|
||||
|
||||
assert resp.status == 200
|
||||
assert await resp.json() == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("params", "expected_kwargs"),
|
||||
[
|
||||
(
|
||||
{"backup_id": "abc123", "agent_id": "backup.local"},
|
||||
{
|
||||
"agent_id": "backup.local",
|
||||
"password": None,
|
||||
"restore_addons": None,
|
||||
"restore_database": True,
|
||||
"restore_folders": None,
|
||||
"restore_homeassistant": True,
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
"backup_id": "abc123",
|
||||
"agent_id": "backup.local",
|
||||
"password": "hunter2",
|
||||
"restore_addons": ["addon_1"],
|
||||
"restore_database": True,
|
||||
"restore_folders": ["media"],
|
||||
},
|
||||
{
|
||||
"agent_id": "backup.local",
|
||||
"password": "hunter2",
|
||||
"restore_addons": ["addon_1"],
|
||||
"restore_database": True,
|
||||
"restore_folders": [backup.Folder.MEDIA],
|
||||
"restore_homeassistant": True,
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
"backup_id": "abc123",
|
||||
"agent_id": "backup.local",
|
||||
"password": "hunter2",
|
||||
"restore_addons": ["addon_1", "addon_2"],
|
||||
"restore_database": False,
|
||||
"restore_folders": ["media", "share"],
|
||||
},
|
||||
{
|
||||
"agent_id": "backup.local",
|
||||
"password": "hunter2",
|
||||
"restore_addons": ["addon_1", "addon_2"],
|
||||
"restore_database": False,
|
||||
"restore_folders": [backup.Folder.MEDIA, backup.Folder.SHARE],
|
||||
"restore_homeassistant": True,
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_onboarding_backup_restore(
|
||||
hass: HomeAssistant,
|
||||
hass_storage: dict[str, Any],
|
||||
hass_client: ClientSessionGenerator,
|
||||
params: dict[str, Any],
|
||||
expected_kwargs: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test returning installation type during onboarding."""
|
||||
mock_storage(hass_storage, {"done": []})
|
||||
|
||||
assert await async_setup_component(hass, "onboarding", {})
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
client = await hass_client()
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.backup.manager.BackupManager.async_restore_backup",
|
||||
) as mock_restore:
|
||||
resp = await client.post("/api/onboarding/backup/restore", json=params)
|
||||
assert resp.status == 200
|
||||
mock_restore.assert_called_once_with("abc123", **expected_kwargs)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("params", "restore_error", "expected_status", "expected_message", "restore_calls"),
|
||||
[
|
||||
# Missing agent_id
|
||||
(
|
||||
{"backup_id": "abc123"},
|
||||
None,
|
||||
400,
|
||||
"Message format incorrect: required key not provided @ data['agent_id']",
|
||||
0,
|
||||
),
|
||||
# Missing backup_id
|
||||
(
|
||||
{"agent_id": "backup.local"},
|
||||
None,
|
||||
400,
|
||||
"Message format incorrect: required key not provided @ data['backup_id']",
|
||||
0,
|
||||
),
|
||||
# Invalid restore_database
|
||||
(
|
||||
{
|
||||
"backup_id": "abc123",
|
||||
"agent_id": "backup.local",
|
||||
"restore_database": "yes_please",
|
||||
},
|
||||
None,
|
||||
400,
|
||||
"Message format incorrect: expected bool for dictionary value @ data['restore_database']",
|
||||
0,
|
||||
),
|
||||
# Invalid folder
|
||||
(
|
||||
{
|
||||
"backup_id": "abc123",
|
||||
"agent_id": "backup.local",
|
||||
"restore_folders": ["invalid"],
|
||||
},
|
||||
None,
|
||||
400,
|
||||
"Message format incorrect: expected Folder or one of 'share', 'addons/local', 'ssl', 'media' @ data['restore_folders'][0]",
|
||||
0,
|
||||
),
|
||||
# Wrong password
|
||||
(
|
||||
{"backup_id": "abc123", "agent_id": "backup.local"},
|
||||
backup.IncorrectPasswordError,
|
||||
400,
|
||||
"incorrect_password",
|
||||
1,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_onboarding_backup_restore_error(
|
||||
hass: HomeAssistant,
|
||||
hass_storage: dict[str, Any],
|
||||
hass_client: ClientSessionGenerator,
|
||||
params: dict[str, Any],
|
||||
restore_error: Exception | None,
|
||||
expected_status: int,
|
||||
expected_message: str,
|
||||
restore_calls: int,
|
||||
) -> None:
|
||||
"""Test returning installation type during onboarding."""
|
||||
mock_storage(hass_storage, {"done": []})
|
||||
|
||||
assert await async_setup_component(hass, "onboarding", {})
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
client = await hass_client()
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.backup.manager.BackupManager.async_restore_backup",
|
||||
side_effect=restore_error,
|
||||
) as mock_restore:
|
||||
resp = await client.post("/api/onboarding/backup/restore", json=params)
|
||||
|
||||
assert resp.status == expected_status
|
||||
assert await resp.json() == {"message": expected_message}
|
||||
assert len(mock_restore.mock_calls) == restore_calls
|
||||
|
||||
|
||||
async def test_onboarding_backup_upload(
|
||||
hass: HomeAssistant,
|
||||
hass_storage: dict[str, Any],
|
||||
hass_client: ClientSessionGenerator,
|
||||
) -> None:
|
||||
"""Test returning installation type during onboarding."""
|
||||
mock_storage(hass_storage, {"done": []})
|
||||
|
||||
assert await async_setup_component(hass, "onboarding", {})
|
||||
assert await async_setup_component(hass, "backup", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
client = await hass_client()
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.backup.manager.BackupManager.async_receive_backup",
|
||||
return_value="abc123",
|
||||
) as mock_receive:
|
||||
resp = await client.post(
|
||||
"/api/onboarding/backup/upload?agent_id=backup.local",
|
||||
data={"file": StringIO("test")},
|
||||
)
|
||||
assert resp.status == 201
|
||||
assert await resp.json() == {"backup_id": "abc123"}
|
||||
mock_receive.assert_called_once_with(agent_ids=["backup.local"], contents=ANY)
|
||||
|
Loading…
x
Reference in New Issue
Block a user