From 1a3a089cdd7e7bbbf0ed4a9c9f169ef8467a35ca Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 19 Nov 2024 08:43:13 +0100 Subject: [PATCH] Add agent delete backup (#130921) * Add backup agent delete backup * Remove agents delete websocket command * Update docstring Co-authored-by: Erik Montnemery --------- Co-authored-by: Erik Montnemery --- homeassistant/components/backup/agent.py | 11 ++++++++ homeassistant/components/backup/backup.py | 4 +-- homeassistant/components/backup/manager.py | 4 +-- .../components/kitchen_sink/backup.py | 12 ++++++++ tests/components/backup/common.py | 7 +++++ .../backup/snapshots/test_websocket.ambr | 8 ++++++ tests/components/backup/test_websocket.py | 26 ++++++++++++++++- tests/components/kitchen_sink/test_backup.py | 28 +++++++++++++++++++ 8 files changed, 94 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/backup/agent.py b/homeassistant/components/backup/agent.py index ca1d3933f74..0913ef25896 100644 --- a/homeassistant/components/backup/agent.py +++ b/homeassistant/components/backup/agent.py @@ -55,6 +55,17 @@ class BackupAgent(abc.ABC): :param metadata: Metadata about the backup that should be uploaded. """ + @abc.abstractmethod + async def async_delete_backup( + self, + backup_id: str, + **kwargs: Any, + ) -> None: + """Delete a backup file. + + :param backup_id: The ID of the backup that was returned in async_list_backups. + """ + @abc.abstractmethod async def async_list_backups(self, **kwargs: Any) -> list[BaseBackup]: """List backups.""" diff --git a/homeassistant/components/backup/backup.py b/homeassistant/components/backup/backup.py index 69bd4c83dec..d163f25df3b 100644 --- a/homeassistant/components/backup/backup.py +++ b/homeassistant/components/backup/backup.py @@ -124,8 +124,8 @@ class CoreLocalBackupAgent(LocalBackupAgent): """Return the local path to a backup.""" return self._backup_dir / f"{backup_id}.tar" - async def async_remove_backup(self, backup_id: str, **kwargs: Any) -> None: - """Remove a backup.""" + async def async_delete_backup(self, backup_id: str, **kwargs: Any) -> None: + """Delete a backup file.""" if await self.async_get_backup(backup_id) is None: return diff --git a/homeassistant/components/backup/manager.py b/homeassistant/components/backup/manager.py index c075920a1b4..1c639c2dfc9 100644 --- a/homeassistant/components/backup/manager.py +++ b/homeassistant/components/backup/manager.py @@ -356,9 +356,7 @@ class BackupManager(BaseBackupManager[Backup]): async def async_remove_backup(self, backup_id: str, **kwargs: Any) -> None: """Remove a backup.""" for agent in self.backup_agents.values(): - if not hasattr(agent, "async_remove_backup"): - continue - await agent.async_remove_backup(backup_id) + await agent.async_delete_backup(backup_id) async def async_receive_backup( self, diff --git a/homeassistant/components/kitchen_sink/backup.py b/homeassistant/components/kitchen_sink/backup.py index 3755d351140..51e14a1a074 100644 --- a/homeassistant/components/kitchen_sink/backup.py +++ b/homeassistant/components/kitchen_sink/backup.py @@ -43,6 +43,7 @@ class KitchenSinkBackupAgent(BackupAgent): async def async_download_backup( self, backup_id: str, + *, path: Path, **kwargs: Any, ) -> None: @@ -68,6 +69,17 @@ class KitchenSinkBackupAgent(BackupAgent): ) ) + async def async_delete_backup( + self, + backup_id: str, + **kwargs: Any, + ) -> None: + """Delete a backup file.""" + self._uploads = [ + upload for upload in self._uploads if upload.backup_id != backup_id + ] + LOGGER.info("Deleted backup %s", backup_id) + async def async_list_backups(self, **kwargs: Any) -> list[BaseBackup]: """List synced backups.""" return self._uploads diff --git a/tests/components/backup/common.py b/tests/components/backup/common.py index 6392bf6fde2..d310005fcbd 100644 --- a/tests/components/backup/common.py +++ b/tests/components/backup/common.py @@ -99,6 +99,13 @@ class BackupAgentTest(BackupAgent): """Return a backup.""" return self._backups.get(backup_id) + async def async_delete_backup( + self, + backup_id: str, + **kwargs: Any, + ) -> None: + """Delete a backup file.""" + async def setup_backup_integration( hass: HomeAssistant, diff --git a/tests/components/backup/snapshots/test_websocket.ambr b/tests/components/backup/snapshots/test_websocket.ambr index d59c11c915e..f644871523b 100644 --- a/tests/components/backup/snapshots/test_websocket.ambr +++ b/tests/components/backup/snapshots/test_websocket.ambr @@ -1227,6 +1227,14 @@ 'type': 'result', }) # --- +# name: test_remove_agents_delete + dict({ + 'id': 1, + 'result': None, + 'success': True, + 'type': 'result', + }) +# --- # name: test_restore_local_agent[with_hassio-backups0] dict({ 'error': dict({ diff --git a/tests/components/backup/test_websocket.py b/tests/components/backup/test_websocket.py index cd7b111fe1c..4b5eb55e025 100644 --- a/tests/components/backup/test_websocket.py +++ b/tests/components/backup/test_websocket.py @@ -2,7 +2,7 @@ from pathlib import Path from typing import Any -from unittest.mock import ANY, patch +from unittest.mock import ANY, call, patch from freezegun.api import FrozenDateTimeFactory import pytest @@ -224,6 +224,30 @@ async def test_remove( assert await client.receive_json() == snapshot +async def test_remove_agents_delete( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + snapshot: SnapshotAssertion, +) -> None: + """Test removing a backup file with a mock agent.""" + await setup_backup_integration(hass) + hass.data[DATA_MANAGER].backup_agents = {"domain.test": BackupAgentTest("test")} + + client = await hass_ws_client(hass) + await hass.async_block_till_done() + + with patch.object(BackupAgentTest, "async_delete_backup") as delete_mock: + await client.send_json_auto_id( + { + "type": "backup/remove", + "backup_id": "abc123", + } + ) + assert await client.receive_json() == snapshot + + assert delete_mock.call_args == call("abc123") + + @pytest.mark.parametrize( "data", [ diff --git a/tests/components/kitchen_sink/test_backup.py b/tests/components/kitchen_sink/test_backup.py index 603a9eec1c1..263932cb266 100644 --- a/tests/components/kitchen_sink/test_backup.py +++ b/tests/components/kitchen_sink/test_backup.py @@ -152,3 +152,31 @@ async def test_agents_upload( "protected": test_backup.protected, "size": 0.0, } + + +async def test_agents_delete( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test backup agents delete.""" + client = await hass_ws_client(hass) + backup_id = "abc123" + + await client.send_json_auto_id( + { + "type": "backup/remove", + "backup_id": backup_id, + } + ) + response = await client.receive_json() + + assert response["success"] + assert f"Deleted backup {backup_id}" in caplog.text + + await client.send_json_auto_id({"type": "backup/agents/list_backups"}) + response = await client.receive_json() + + assert response["success"] + backup_list = response["result"] + assert not backup_list