core/tests/components/webdav/test_backup.py
2025-03-14 10:21:16 +01:00

325 lines
9.8 KiB
Python

"""Test the backups for WebDAV."""
from __future__ import annotations
from collections.abc import AsyncGenerator
from io import StringIO
from unittest.mock import Mock, patch
from aiowebdav2.exceptions import UnauthorizedError, WebDavError
import pytest
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
from tests.common import AsyncMock, MockConfigEntry
from tests.typing import ClientSessionGenerator, WebSocketGenerator
@pytest.fixture(autouse=True)
async def setup_backup_integration(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, webdav_client: AsyncMock
) -> AsyncGenerator[None]:
"""Set up webdav integration."""
with (
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)
await hass.async_block_till_done()
yield
async def test_agents_info(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test backup agent info."""
client = await hass_ws_client(hass)
await client.send_json_auto_id({"type": "backup/agents/info"})
response = await client.receive_json()
assert response["success"]
assert response["result"] == {
"agents": [
{"agent_id": "backup.local", "name": "local"},
{
"agent_id": f"{DOMAIN}.{mock_config_entry.entry_id}",
"name": mock_config_entry.title,
},
],
}
async def test_agents_list_backups(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test agent list backups."""
client = await hass_ws_client(hass)
await client.send_json_auto_id({"type": "backup/info"})
response = await client.receive_json()
assert response["success"]
assert response["result"]["agent_errors"] == {}
assert response["result"]["backups"] == [
{
"addons": [],
"agents": {
"webdav.01JKXV07ASC62D620DGYNG2R8H": {
"protected": False,
"size": 34519040,
}
},
"backup_id": "23e64aec",
"date": "2025-02-10T17:47:22.727189+01:00",
"database_included": True,
"extra_metadata": {},
"folders": [],
"homeassistant_included": True,
"homeassistant_version": "2025.2.1",
"name": "Automatic backup 2025.2.1",
"failed_agent_ids": [],
"with_automatic_settings": None,
}
]
async def test_agents_get_backup(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test agent get backup."""
backup_id = BACKUP_METADATA["backup_id"]
client = await hass_ws_client(hass)
await client.send_json_auto_id({"type": "backup/details", "backup_id": backup_id})
response = await client.receive_json()
assert response["success"]
assert response["result"]["agent_errors"] == {}
assert response["result"]["backup"] == {
"addons": [],
"agents": {
f"{DOMAIN}.{mock_config_entry.entry_id}": {
"protected": False,
"size": 34519040,
}
},
"backup_id": "23e64aec",
"date": "2025-02-10T17:47:22.727189+01:00",
"database_included": True,
"extra_metadata": {},
"folders": [],
"homeassistant_included": True,
"homeassistant_version": "2025.2.1",
"name": "Automatic backup 2025.2.1",
"failed_agent_ids": [],
"with_automatic_settings": None,
}
async def test_agents_delete(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
webdav_client: AsyncMock,
) -> None:
"""Test agent delete backup."""
client = await hass_ws_client(hass)
await client.send_json_auto_id(
{
"type": "backup/delete",
"backup_id": BACKUP_METADATA["backup_id"],
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {"agent_errors": {}}
assert webdav_client.clean.call_count == 2
async def test_agents_upload(
hass_client: ClientSessionGenerator,
webdav_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test agent upload backup."""
client = await hass_client()
test_backup = AgentBackup.from_dict(BACKUP_METADATA)
with (
patch(
"homeassistant.components.backup.manager.BackupManager.async_get_backup",
) as fetch_backup,
patch(
"homeassistant.components.backup.manager.read_backup",
return_value=test_backup,
),
patch("pathlib.Path.open") as mocked_open,
):
mocked_open.return_value.read = Mock(side_effect=[b"test", b""])
fetch_backup.return_value = test_backup
resp = await client.post(
f"/api/backup/upload?agent_id={DOMAIN}.{mock_config_entry.entry_id}",
data={"file": StringIO("test")},
)
assert resp.status == 201
assert webdav_client.upload_iter.call_count == 2
async def test_agents_download(
hass_client: ClientSessionGenerator,
webdav_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test agent download backup."""
client = await hass_client()
backup_id = BACKUP_METADATA["backup_id"]
resp = await client.get(
f"/api/backup/download/{backup_id}?agent_id={DOMAIN}.{mock_config_entry.entry_id}"
)
assert resp.status == 200
assert await resp.content.read() == b"backup data"
async def test_error_on_agents_download(
hass_client: ClientSessionGenerator,
webdav_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test we get not found on a not existing backup on download."""
client = await hass_client()
backup_id = BACKUP_METADATA["backup_id"]
webdav_client.list_files.return_value = []
resp = await client.get(
f"/api/backup/download/{backup_id}?agent_id={DOMAIN}.{mock_config_entry.entry_id}"
)
assert resp.status == 404
@pytest.mark.parametrize(
("side_effect", "error"),
[
(
WebDavError("Unknown path"),
"Backup operation failed: Unknown path",
),
(TimeoutError(), "Backup operation timed out"),
],
)
async def test_delete_error(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
webdav_client: AsyncMock,
mock_config_entry: MockConfigEntry,
side_effect: Exception,
error: str,
) -> None:
"""Test error during delete."""
webdav_client.clean.side_effect = side_effect
client = await hass_ws_client(hass)
await client.send_json_auto_id(
{
"type": "backup/delete",
"backup_id": BACKUP_METADATA["backup_id"],
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {
"agent_errors": {f"{DOMAIN}.{mock_config_entry.entry_id}": error}
}
async def test_agents_delete_not_found_does_not_throw(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
webdav_client: AsyncMock,
) -> None:
"""Test agent delete backup."""
webdav_client.list_files.return_value = {}
client = await hass_ws_client(hass)
await client.send_json_auto_id(
{
"type": "backup/delete",
"backup_id": BACKUP_METADATA["backup_id"],
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == {"agent_errors": {}}
async def test_agents_backup_not_found(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
webdav_client: AsyncMock,
) -> None:
"""Test backup not found."""
webdav_client.list_files.return_value = []
backup_id = BACKUP_METADATA["backup_id"]
client = await hass_ws_client(hass)
await client.send_json_auto_id({"type": "backup/details", "backup_id": backup_id})
response = await client.receive_json()
assert response["success"]
assert response["result"]["backup"] is None
async def test_raises_on_403(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
webdav_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test we raise on 403."""
webdav_client.list_files.side_effect = UnauthorizedError(
"https://webdav.example.com"
)
backup_id = BACKUP_METADATA["backup_id"]
client = await hass_ws_client(hass)
await client.send_json_auto_id({"type": "backup/details", "backup_id": backup_id})
response = await client.receive_json()
assert response["success"]
assert response["result"]["agent_errors"] == {
f"{DOMAIN}.{mock_config_entry.entry_id}": "Authentication error"
}
async def test_listeners_get_cleaned_up(hass: HomeAssistant) -> None:
"""Test listener gets cleaned up."""
listener = AsyncMock()
remove_listener = async_register_backup_agents_listener(hass, listener=listener)
# make sure it's the last listener
hass.data[DATA_BACKUP_AGENT_LISTENERS] = [listener]
remove_listener()
assert hass.data.get(DATA_BACKUP_AGENT_LISTENERS) is None