"""Test the builtin backup platform.""" from __future__ import annotations from collections.abc import Generator from io import StringIO import json from pathlib import Path from tarfile import TarError from unittest.mock import MagicMock, mock_open, patch import pytest 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 ( TEST_BACKUP_ABC123, TEST_BACKUP_DEF456, TEST_BACKUP_PATH_ABC123, TEST_BACKUP_PATH_DEF456, ) from tests.typing import ClientSessionGenerator, WebSocketGenerator def mock_read_backup(backup_path: Path) -> AgentBackup: """Mock read backup.""" mock_backups = { "abc123": TEST_BACKUP_ABC123, "custom_def456": TEST_BACKUP_DEF456, } return mock_backups[backup_path.stem] @pytest.fixture(name="read_backup") def read_backup_fixture(path_glob: MagicMock) -> Generator[MagicMock]: """Mock read backup.""" with patch( "homeassistant.components.backup.backup.read_backup", side_effect=mock_read_backup, ) as read_backup: yield read_backup @pytest.mark.parametrize( "side_effect", [ mock_read_backup, OSError("Boom"), TarError("Boom"), json.JSONDecodeError("Boom", "test", 1), KeyError("Boom"), ], ) async def test_load_backups( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, snapshot: SnapshotAssertion, read_backup: MagicMock, 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) read_backup.side_effect = side_effect # list agents await client.send_json_auto_id({"type": "backup/agents/info"}) assert await client.receive_json() == snapshot # load and list backups await client.send_json_auto_id({"type": "backup/info"}) assert await client.receive_json() == snapshot async def test_upload( hass: HomeAssistant, 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() open_mock = mock_open() with ( patch("pathlib.Path.open", open_mock), patch("shutil.move") as move_mock, patch( "homeassistant.components.backup.manager.read_backup", return_value=TEST_BACKUP_ABC123, ), ): resp = await client.post( "/api/backup/upload?agent_id=backup.local", data={"file": StringIO("test")}, ) assert resp.status == 201 assert open_mock.call_count == 1 assert move_mock.call_count == 1 assert move_mock.mock_calls[0].args[1].name == "Test_1970-01-01_00.00_00000000.tar" @pytest.mark.usefixtures("read_backup") @pytest.mark.parametrize( ("found_backups", "backup_id", "unlink_calls", "unlink_path"), [ ( [TEST_BACKUP_PATH_ABC123, TEST_BACKUP_PATH_DEF456], TEST_BACKUP_ABC123.backup_id, 1, TEST_BACKUP_PATH_ABC123, ), ( [TEST_BACKUP_PATH_ABC123, TEST_BACKUP_PATH_DEF456], TEST_BACKUP_DEF456.backup_id, 1, TEST_BACKUP_PATH_DEF456, ), (([], TEST_BACKUP_ABC123.backup_id, 0, None)), ], ) async def test_delete_backup( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, hass_ws_client: WebSocketGenerator, snapshot: SnapshotAssertion, path_glob: MagicMock, found_backups: list[Path], backup_id: str, unlink_calls: int, 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) path_glob.return_value = found_backups with ( patch("pathlib.Path.unlink", autospec=True) as unlink, ): await client.send_json_auto_id( {"type": "backup/delete", "backup_id": backup_id} ) assert await client.receive_json() == snapshot assert unlink.call_count == unlink_calls for call in unlink.mock_calls: assert call.args[0] == unlink_path