* Reapply "Make WS command backup/generate send events" (#131530)
This reverts commit 9b8316df3f.
* MVP implementation of Backup sync agents (#126122)
* init sync agent
* add syncing
* root import
* rename list to info and add sync state
* Add base backup class
* Revert unneded change
* adjust tests
* move to kitchen_sink
* split
* move
* Adjustments
* Adjustment
* update
* Tests
* Test unknown agent
* adjust
* Adjust for different test environments
* Change /info WS to contain a dictinary
* reorder
* Add websocket command to trigger sync from the supervisor
* cleanup
* Make mypy happier
---------
Co-authored-by: Erik <erik@montnemery.com>
* Make BackupSyncMetadata model a dataclass (#130555)
Make backup BackupSyncMetadata model a dataclass
* Rename backup sync agent to backup agent (#130575)
* Rename sync agent module to agent
* Rename BackupSyncAgent to BackupAgent
* Fix test typo
* Rename async_get_backup_sync_agents to async_get_backup_agents
* Rename and clean up remaining sync things
* Update kitchen sink
* Apply suggestions from code review
* Update test_manager.py
---------
Co-authored-by: Erik Montnemery <erik@montnemery.com>
* Add additional options to WS command backup/generate (#130530)
* Add additional options to WS command backup/generate
* Improve test
* Improve test
* Align parameter names in backup/agents/* WS commands (#130590)
* Allow setting password for backups (#110630)
* Allow setting password for backups
* use is_hassio from helpers
* move it
* Fix getting psw
* Fix restoring with psw
* Address review comments
* Improve docstring
* Adjust kitchen sink
* Adjust
---------
Co-authored-by: Erik <erik@montnemery.com>
* Export relevant names from backup integration (#130596)
* Tweak backup agent interface (#130613)
* Tweak backup agent interface
* Adjust kitchen_sink
* Test kitchen sink backup (#130609)
* Test agents_list_backups
* Test agents_info
* Test agents_download
* Export Backup from manager
* Test agents_upload
* Update tests after rebase
* Use backup domain
* Remove WS command backup/upload (#130588)
* Remove WS command backup/upload
* Disable failing kitchen_sink test
* Make local backup a backup agent (#130623)
* Make local backup a backup agent
* Adjust
* Adjust
* Adjust
* Adjust tests
* Adjust
* Adjust
* Adjust docstring
* Adjust
* Protect members of CoreLocalBackupAgent
* Remove redundant check for file
* Make the backup.create service use the first local agent
* Add BackupAgent.async_get_backup
* Fix some TODOs
* Add support for downloading backup from a remote agent
* Fix restore
* Fix test
* Adjust kitchen_sink test
* Remove unused method BackupManager.async_get_backup_path
* Re-enable kitchen sink test
* Remove BaseBackupManager.async_upload_backup
* Support restore from remote agent
* Fix review comments
* Include backup agent error in response to WS command backup/info (#130884)
* Adjust code related to WS command backup/info (#130890)
* Include backup agent error in response to WS command backup/details (#130892)
* Remove LOCAL_AGENT_ID constant from backup manager (#130895)
* Add backup config storage (#130871)
* Add base for backup config
* Allow updating backup config
* Test loading backup config
* Add backup config update method
* Add temporary check for BackupAgent.async_remove_backup (#130893)
* Rename backup slug to backup_id (#130902)
* Improve backup websocket API tests (#130912)
* Improve backup websocket API tests
* Add missing snapshot
* Fix tests leaving files behind
* Improve backup manager backup creation tests (#130916)
* Remove class backup.backup.LocalBackup (#130919)
* Add agent delete backup (#130921)
* Add backup agent delete backup
* Remove agents delete websocket command
* Update docstring
Co-authored-by: Erik Montnemery <erik@montnemery.com>
---------
Co-authored-by: Erik Montnemery <erik@montnemery.com>
* Disable core local backup agent in hassio (#130933)
* Rename remove backup to delete backup (#130940)
* Rename remove backup to delete backup
* Revert "backup/delete"
* Refactor BackupManager (#130947)
* Refactor BackupManager
* Adjust
* Adjust backup creation
* Copy in executor
* Fix BackupManager.async_get_backup (#130975)
* Fix typo in backup tests (#130978)
* Adjust backup NewBackup class (#130976)
* Remove class backup.BackupUploadMetadata (#130977)
Remove class backup.BackupMetadata
* Report backup size in bytes instead of MB (#131028)
Co-authored-by: Robert Resch <robert@resch.dev>
* Speed up CI for feature branch (#131030)
* Speed up CI for feature branch
* adjust
* fix
* fix
* fix
* fix
* Rename remove to delete in backup websocket type (#131023)
* Revert "Speed up CI for feature branch" (#131074)
Revert "Speed up CI for feature branch (#131030)"
This reverts commit 791280506d1859b1a722f5064d75bcbe48acc1c3.
* Rename class BaseBackup to AgentBackup (#131083)
* Rename class BaseBackup to AgentBackup
* Update tests
* Speed up CI for backup feature branch (#131079)
* Add backup platform to the hassio integration (#130991)
* Add backup platform to the hassio integration
* Add hassio to after_dependencies of backup
* Address review comments
* Remove redundant hassio parametrization of tests
* Add tests
* Address review comments
* Bump CI cache version
* Revert "Bump CI cache version"
This reverts commit 2ab4d2b1795c953ccfc9b17c47f9df3faac83749.
* Extend backup info class AgentBackup (#131110)
* Extend backup info class AgentBackup
* Update kitchen sink
* Update kitchen sink test
* Update kitchen sink test
* Exclude cloud and hassio from core files (#131117)
* Remove unnecessary **kwargs from backup API (#131124)
* Fix backup tests (#131128)
* Freeze backup dataclasses (#131122)
* Protect CoreLocalBackupAgent.load_backups (#131126)
* Use backup metadata v2 in core/container backups (#131125)
* Extend backup creation API (#131121)
* Extend backup creation API
* Add tests
* Fix merge
* Fix merge
* Return agent errors when deleting a backup (#131142)
* Return agent errors when deleting a backup
* Remove redundant calls to dict.keys()
* Add enum type for backup folder (#131158)
* Add method AgentBackup.from_dict (#131164)
* Remove WS command backup/agents/list_backups (#131163)
* Handle backup schedule (#131127)
* Add backup schedule handling
* Fix unrelated incorrect type annotation in test
* Clarify delay save
* Make the backup time compatible with the recorder nightly job
* Update create backup parameters
* Use typed dict for create backup parameters
* Simplify schedule state
* Group create backup parameters
* Move parameter
* Fix typo
* Use Folder model
* Handle deserialization of folders better
* Fail on attempt to include addons or folders in core backup (#131204)
* Fix AgentBackup test (#131201)
* Add options to WS command backup/restore (#131194)
* Add options to WS command backup/restore
* Add tests
* Fix test
* Teach core backup to restore only database or only settings (#131225)
* Exclude tmp_backups/*.tar from backups (#131243)
* Add WS command backup/subscribe_events (#131250)
* Clean up temporary directory after restoring backup (#131263)
* Improve hassio backup agent list (#131268)
* Include `last_automatic_backup` in reply to backup/info (#131293)
Include last_automatic_backup in reply to backup/info
* Handle backup delete after config (#131259)
* Handle delete after copies
* Handle delete after days
* Add some test examples
* Test config_delete_after_logic
* Test config_delete_after_copies_logic
* Test more delete after days
* Add debug logs
* Always delete the oldest backup first
* Never remove the last backup
* Clean up words
Co-authored-by: Erik Montnemery <erik@montnemery.com>
* Fix after cleaning words
* Use utcnow
* Remove duplicate guard
* Simplify sorting
* Delete backups even if there are agent errors on get backups
---------
Co-authored-by: Erik Montnemery <erik@montnemery.com>
* Rename backup delete after to backup retention (#131364)
* Rename backup delete after to backup retention
* Tweak
* Remove length limit on `agent_ids` when configuring backup (#132057)
Remove length limit on agent_ids when configuring backup
* Rename backup retention_config to retention (#132068)
* Modify backup agent API to be stream oriented (#132090)
* Modify backup agent API to be stream oriented
* Fix tests
* Adjust after code review
* Remove no longer needed pylint override
* Improve test coverage
* Change BackupAgent API to work with AsyncIterator objects
* Don't close files in the event loop
* Don't close files in the event loop
* Fix backup manager create backup log (#132174)
* Fix debug log level (#132186)
* Add cloud backup agent (#129621)
* Init cloud backup sync
* Add more metadata
* Fix typo
* Adjust to base changes
* Don't raise on list if more than one backup is available
* Adjust to base branch
* Fetch always and verify on download
* Update homeassistant/components/cloud/backup.py
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
* Adjust to base branch changes
* Not required anymore
* Workaround
* Fix blocking event loop
* Fix
* Add some tests
* some tests
* Add cloud backup delete functionality
* Enable check
* Fix ruff
* Use fixture
* Use iter_chunks instead
* Remove read
* Remove explicit export of read_backup
* Align with BackupAgent API changes
* Improve test coverage
* Improve error handling
* Adjust docstrings
* Catch aiohttp.ClientError bubbling up from hass_nabucasa
* Improve iteration
---------
Co-authored-by: Erik <erik@montnemery.com>
Co-authored-by: Robert Resch <robert@resch.dev>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Krisjanis Lejejs <krisjanis.lejejs@gmail.com>
* Extract file receiver from `BackupManager.async_receive_backup` to util (#132271)
* Extract file receiver from BackupManager.async_receive_backup to util
* Apply suggestions from code review
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
---------
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
* Make sure backup directory exists (#132269)
* Make sure backup directory exists
* Hand off directory creation to executor
* Use mkdir's exist_ok feeature
* Organize BackupManager instance attributes (#132277)
* Don't store received backups in a TempDir (#132272)
* Don't store received backups in a TempDir
* Fix tests
* Make sure backup directory exists
* Address review comments
* Fix tests
* Rewrite backup manager state handling (#132375)
* Rewrite backup manager state handling
* Address review comments
* Modify backup reader/writer API to be stream oriented (#132464)
* Internalize backup tasks (#132482)
* Internalize backup tasks
* Update test after rebase
* Handle backup error during automatic backup (#132511)
* Improve backup manager state logging (#132549)
* Fix backup manager state when restore completes (#132548)
* Remove WS command backup/agents/download (#132664)
* Add WS command backup/generate_with_stored_settings (#132671)
* Add WS command backup/generate_with_stored_settings
* Register the new command, add tests
* Refactor local agent backup tests (#132683)
* Refactor test_load_backups
* Refactor test loading agents
* Refactor test_delete_backup
* Refactor test_upload
* Clean up duplicate tests
* Refactor backup manager receive tests (#132701)
* Refactor backup manager receive tests
* Clean up
* Refactor pre and post platform tests (#132708)
* Refactor backup pre platform test
* Refactor backup post platform test
* Bump aiohasupervisor to version 0.2.2b0 (#132704)
* Bump aiohasupervisor to version 0.2.2b0
* Adjust tests
* Publish event when manager is idle after creating backup (#132724)
* Handle busy backup manager when uploading backup (#132736)
* Adjust hassio backup agent to supervisor changes (#132732)
* Adjust hassio backup agent to supervisor changes
* Fix typo
* Refactor test for create backup with wrong parameters (#132763)
* Refactor test not loading bad backup platforms (#132769)
* Improve receive backup coverage (#132758)
* Refactor initiate backup test (#132829)
* Rename Backup to ManagerBackup (#132841)
* Refactor backup config (#132845)
* Refactor backup config
* Remove unnecessary condition
* Adjust tests
* Improve initiate backup test (#132858)
* Store the time of automatic backup attempts (#132860)
* Store the time of automatic backup attempts
* Address review comments
* Update test
* Update cloud test
* Save agent failures when creating backups (#132850)
* Save agent failures when creating backups
* Update tests
* Store KnownBackups
* Add test
* Only clear known_backups on no error, add tests
* Address review comments
* Store known backups as a list
* Update tests
* Track all backups created with backup strategy settings (#132916)
* Track all backups created with saved settings
* Rename
* Add explicit call to save the store
* Don't register service backup.create in HassOS installations (#132932)
* Revert changes to action service backup.create (#132938)
* Fix logic for cleaning up temporary backup file (#132934)
* Fix logic for cleaning up temporary backup file
* Reduce scope of patch
* Fix with_strategy_settings info not sent over websocket (#132939)
* Fix with_strategy_settings info not sent over websocket
* Fix kitchen sink tests
* Fix cloud and hassio tests
* Revert backup ci changes (#132955)
Revert changes speeding up CI
* Fix revert of CI changes (#132960)
---------
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Robert Resch <robert@resch.dev>
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
Co-authored-by: Krisjanis Lejejs <krisjanis.lejejs@gmail.com>
1754 lines
55 KiB
Python
1754 lines
55 KiB
Python
"""Tests for the Backup integration."""
|
|
|
|
from asyncio import Future
|
|
from collections.abc import Generator
|
|
from datetime import datetime
|
|
from typing import Any
|
|
from unittest.mock import ANY, AsyncMock, MagicMock, call, patch
|
|
|
|
from freezegun.api import FrozenDateTimeFactory
|
|
import pytest
|
|
from syrupy import SnapshotAssertion
|
|
|
|
from homeassistant.components.backup import AgentBackup, BackupAgentError
|
|
from homeassistant.components.backup.agent import BackupAgentUnreachableError
|
|
from homeassistant.components.backup.const import DATA_MANAGER, DOMAIN
|
|
from homeassistant.components.backup.manager import (
|
|
CreateBackupEvent,
|
|
CreateBackupState,
|
|
NewBackup,
|
|
WrittenBackup,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.exceptions import HomeAssistantError
|
|
|
|
from .common import (
|
|
LOCAL_AGENT_ID,
|
|
TEST_BACKUP_ABC123,
|
|
TEST_BACKUP_DEF456,
|
|
BackupAgentTest,
|
|
setup_backup_integration,
|
|
)
|
|
|
|
from tests.common import async_fire_time_changed, async_mock_service
|
|
from tests.typing import WebSocketGenerator
|
|
|
|
BACKUP_CALL = call(
|
|
agent_ids=["test.test-agent"],
|
|
backup_name="test-name",
|
|
include_addons=["test-addon"],
|
|
include_all_addons=False,
|
|
include_database=True,
|
|
include_folders=["media"],
|
|
include_homeassistant=True,
|
|
password="test-password",
|
|
on_progress=ANY,
|
|
)
|
|
|
|
DEFAULT_STORAGE_DATA = {
|
|
"backups": {},
|
|
"config": {
|
|
"create_backup": {
|
|
"agent_ids": [],
|
|
"include_addons": None,
|
|
"include_all_addons": False,
|
|
"include_database": True,
|
|
"include_folders": None,
|
|
"name": None,
|
|
"password": None,
|
|
},
|
|
"last_attempted_strategy_backup": None,
|
|
"last_completed_strategy_backup": None,
|
|
"retention": {
|
|
"copies": None,
|
|
"days": None,
|
|
},
|
|
"schedule": {
|
|
"state": "never",
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def sync_access_token_proxy(
|
|
access_token_fixture_name: str,
|
|
request: pytest.FixtureRequest,
|
|
) -> str:
|
|
"""Non-async proxy for the *_access_token fixture.
|
|
|
|
Workaround for https://github.com/pytest-dev/pytest-asyncio/issues/112
|
|
"""
|
|
return request.getfixturevalue(access_token_fixture_name)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def mock_delay_save() -> Generator[None]:
|
|
"""Mock the delay save constant."""
|
|
with patch("homeassistant.components.backup.store.STORE_DELAY_SAVE", 0):
|
|
yield
|
|
|
|
|
|
@pytest.fixture(name="create_backup")
|
|
def mock_create_backup() -> Generator[AsyncMock]:
|
|
"""Mock manager create backup."""
|
|
mock_written_backup = MagicMock(spec_set=WrittenBackup)
|
|
mock_written_backup.backup.backup_id = "abc123"
|
|
mock_written_backup.open_stream = AsyncMock()
|
|
mock_written_backup.release_stream = AsyncMock()
|
|
fut = Future()
|
|
fut.set_result(mock_written_backup)
|
|
with patch(
|
|
"homeassistant.components.backup.CoreBackupReaderWriter.async_create_backup"
|
|
) as mock_create_backup:
|
|
mock_create_backup.return_value = (MagicMock(), fut)
|
|
yield mock_create_backup
|
|
|
|
|
|
@pytest.fixture(name="delete_backup")
|
|
def mock_delete_backup() -> Generator[AsyncMock]:
|
|
"""Mock manager delete backup."""
|
|
with patch(
|
|
"homeassistant.components.backup.BackupManager.async_delete_backup"
|
|
) as mock_delete_backup:
|
|
yield mock_delete_backup
|
|
|
|
|
|
@pytest.fixture(name="get_backups")
|
|
def mock_get_backups() -> Generator[AsyncMock]:
|
|
"""Mock manager get backups."""
|
|
with patch(
|
|
"homeassistant.components.backup.BackupManager.async_get_backups"
|
|
) as mock_get_backups:
|
|
yield mock_get_backups
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("remote_agents", "remote_backups"),
|
|
[
|
|
([], {}),
|
|
(["remote"], {}),
|
|
(["remote"], {"test.remote": [TEST_BACKUP_ABC123]}),
|
|
(["remote"], {"test.remote": [TEST_BACKUP_DEF456]}),
|
|
],
|
|
)
|
|
async def test_info(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
remote_agents: list[str],
|
|
remote_backups: dict[str, list[AgentBackup]],
|
|
snapshot: SnapshotAssertion,
|
|
) -> None:
|
|
"""Test getting backup info."""
|
|
await setup_backup_integration(
|
|
hass,
|
|
with_hassio=False,
|
|
backups={LOCAL_AGENT_ID: [TEST_BACKUP_ABC123]} | remote_backups,
|
|
remote_agents=remote_agents,
|
|
)
|
|
|
|
client = await hass_ws_client(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
await client.send_json_auto_id({"type": "backup/info"})
|
|
assert await client.receive_json() == snapshot
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"side_effect", [HomeAssistantError("Boom!"), BackupAgentUnreachableError]
|
|
)
|
|
async def test_info_with_errors(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
side_effect: Exception,
|
|
snapshot: SnapshotAssertion,
|
|
) -> None:
|
|
"""Test getting backup info with one unavailable agent."""
|
|
await setup_backup_integration(
|
|
hass, with_hassio=False, backups={LOCAL_AGENT_ID: [TEST_BACKUP_ABC123]}
|
|
)
|
|
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_list_backups", side_effect=side_effect):
|
|
await client.send_json_auto_id({"type": "backup/info"})
|
|
assert await client.receive_json() == snapshot
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("remote_agents", "backups"),
|
|
[
|
|
([], {}),
|
|
(["remote"], {LOCAL_AGENT_ID: [TEST_BACKUP_ABC123]}),
|
|
(["remote"], {"test.remote": [TEST_BACKUP_ABC123]}),
|
|
(["remote"], {"test.remote": [TEST_BACKUP_DEF456]}),
|
|
(
|
|
["remote"],
|
|
{
|
|
LOCAL_AGENT_ID: [TEST_BACKUP_ABC123],
|
|
"test.remote": [TEST_BACKUP_ABC123],
|
|
},
|
|
),
|
|
],
|
|
)
|
|
async def test_details(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
remote_agents: list[str],
|
|
backups: dict[str, list[AgentBackup]],
|
|
snapshot: SnapshotAssertion,
|
|
) -> None:
|
|
"""Test getting backup info."""
|
|
await setup_backup_integration(
|
|
hass, with_hassio=False, backups=backups, remote_agents=remote_agents
|
|
)
|
|
|
|
client = await hass_ws_client(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
with patch("pathlib.Path.exists", return_value=True):
|
|
await client.send_json_auto_id(
|
|
{"type": "backup/details", "backup_id": "abc123"}
|
|
)
|
|
assert await client.receive_json() == snapshot
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"side_effect", [HomeAssistantError("Boom!"), BackupAgentUnreachableError]
|
|
)
|
|
async def test_details_with_errors(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
side_effect: Exception,
|
|
snapshot: SnapshotAssertion,
|
|
) -> None:
|
|
"""Test getting backup info with one unavailable agent."""
|
|
await setup_backup_integration(
|
|
hass, with_hassio=False, backups={LOCAL_AGENT_ID: [TEST_BACKUP_ABC123]}
|
|
)
|
|
hass.data[DATA_MANAGER].backup_agents["domain.test"] = BackupAgentTest("test")
|
|
|
|
client = await hass_ws_client(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
with (
|
|
patch("pathlib.Path.exists", return_value=True),
|
|
patch.object(BackupAgentTest, "async_get_backup", side_effect=side_effect),
|
|
):
|
|
await client.send_json_auto_id(
|
|
{"type": "backup/details", "backup_id": "abc123"}
|
|
)
|
|
assert await client.receive_json() == snapshot
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("remote_agents", "backups"),
|
|
[
|
|
([], {}),
|
|
(["remote"], {LOCAL_AGENT_ID: [TEST_BACKUP_ABC123]}),
|
|
(["remote"], {"test.remote": [TEST_BACKUP_ABC123]}),
|
|
(["remote"], {"test.remote": [TEST_BACKUP_DEF456]}),
|
|
(
|
|
["remote"],
|
|
{
|
|
LOCAL_AGENT_ID: [TEST_BACKUP_ABC123],
|
|
"test.remote": [TEST_BACKUP_ABC123],
|
|
},
|
|
),
|
|
],
|
|
)
|
|
async def test_delete(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
remote_agents: list[str],
|
|
backups: dict[str, list[AgentBackup]],
|
|
snapshot: SnapshotAssertion,
|
|
) -> None:
|
|
"""Test deleting a backup file."""
|
|
await setup_backup_integration(
|
|
hass, with_hassio=False, backups=backups, remote_agents=remote_agents
|
|
)
|
|
|
|
client = await hass_ws_client(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
await client.send_json_auto_id({"type": "backup/info"})
|
|
assert await client.receive_json() == snapshot
|
|
|
|
await client.send_json_auto_id({"type": "backup/delete", "backup_id": "abc123"})
|
|
assert await client.receive_json() == snapshot
|
|
|
|
await client.send_json_auto_id({"type": "backup/info"})
|
|
assert await client.receive_json() == snapshot
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"storage_data",
|
|
[
|
|
DEFAULT_STORAGE_DATA,
|
|
DEFAULT_STORAGE_DATA
|
|
| {
|
|
"backups": [
|
|
{
|
|
"backup_id": "abc123",
|
|
"failed_agent_ids": ["test.remote"],
|
|
"with_strategy_settings": False,
|
|
}
|
|
]
|
|
},
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"side_effect", [None, HomeAssistantError("Boom!"), BackupAgentUnreachableError]
|
|
)
|
|
async def test_delete_with_errors(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
hass_storage: dict[str, Any],
|
|
side_effect: Exception,
|
|
storage_data: dict[str, Any] | None,
|
|
snapshot: SnapshotAssertion,
|
|
) -> None:
|
|
"""Test deleting a backup with one unavailable agent."""
|
|
hass_storage[DOMAIN] = {
|
|
"data": storage_data,
|
|
"key": DOMAIN,
|
|
"version": 1,
|
|
}
|
|
await setup_backup_integration(
|
|
hass, with_hassio=False, backups={LOCAL_AGENT_ID: [TEST_BACKUP_ABC123]}
|
|
)
|
|
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", side_effect=side_effect):
|
|
await client.send_json_auto_id({"type": "backup/delete", "backup_id": "abc123"})
|
|
assert await client.receive_json() == snapshot
|
|
|
|
await client.send_json_auto_id({"type": "backup/info"})
|
|
assert await client.receive_json() == snapshot
|
|
|
|
|
|
async def test_agent_delete_backup(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
snapshot: SnapshotAssertion,
|
|
) -> None:
|
|
"""Test deleting 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/delete",
|
|
"backup_id": "abc123",
|
|
}
|
|
)
|
|
assert await client.receive_json() == snapshot
|
|
|
|
assert delete_mock.call_args == call("abc123")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"data",
|
|
[
|
|
None,
|
|
{},
|
|
{"password": "abc123"},
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("mock_backup_generation")
|
|
async def test_generate(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
data: dict[str, Any] | None,
|
|
freezer: FrozenDateTimeFactory,
|
|
snapshot: SnapshotAssertion,
|
|
) -> None:
|
|
"""Test generating a backup."""
|
|
await setup_backup_integration(hass, with_hassio=False)
|
|
|
|
client = await hass_ws_client(hass)
|
|
freezer.move_to("2024-11-13 12:01:00+01:00")
|
|
await hass.async_block_till_done()
|
|
|
|
await client.send_json_auto_id({"type": "backup/subscribe_events"})
|
|
assert await client.receive_json() == snapshot
|
|
await client.send_json_auto_id(
|
|
{"type": "backup/generate", **{"agent_ids": ["backup.local"]} | (data or {})}
|
|
)
|
|
for _ in range(6):
|
|
assert await client.receive_json() == snapshot
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("parameters", "expected_error"),
|
|
[
|
|
(
|
|
{"include_homeassistant": False},
|
|
"Home Assistant must be included in backup",
|
|
),
|
|
(
|
|
{"include_addons": ["blah"]},
|
|
"Addons and folders are not supported by core backup",
|
|
),
|
|
(
|
|
{"include_all_addons": True},
|
|
"Addons and folders are not supported by core backup",
|
|
),
|
|
(
|
|
{"include_folders": ["ssl"]},
|
|
"Addons and folders are not supported by core backup",
|
|
),
|
|
],
|
|
)
|
|
async def test_generate_wrong_parameters(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
parameters: dict[str, Any],
|
|
expected_error: str,
|
|
) -> None:
|
|
"""Test generating a backup."""
|
|
await setup_backup_integration(hass, with_hassio=False)
|
|
|
|
client = await hass_ws_client(hass)
|
|
|
|
default_parameters = {"type": "backup/generate", "agent_ids": ["backup.local"]}
|
|
|
|
await client.send_json_auto_id(default_parameters | parameters)
|
|
response = await client.receive_json()
|
|
assert not response["success"]
|
|
assert response["error"] == {
|
|
"code": "home_assistant_error",
|
|
"message": expected_error,
|
|
}
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_backup_generation")
|
|
@pytest.mark.parametrize(
|
|
("params", "expected_extra_call_params"),
|
|
[
|
|
({"agent_ids": ["backup.local"]}, {"agent_ids": ["backup.local"]}),
|
|
(
|
|
{
|
|
"agent_ids": ["backup.local"],
|
|
"include_database": False,
|
|
"name": "abc123",
|
|
},
|
|
{
|
|
"agent_ids": ["backup.local"],
|
|
"include_addons": None,
|
|
"include_database": False,
|
|
"include_folders": None,
|
|
"name": "abc123",
|
|
},
|
|
),
|
|
],
|
|
)
|
|
async def test_generate_calls_create(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
freezer: FrozenDateTimeFactory,
|
|
snapshot: SnapshotAssertion,
|
|
params: dict[str, Any],
|
|
expected_extra_call_params: dict[str, Any],
|
|
) -> None:
|
|
"""Test translation of WS parameter to backup/generate to async_initiate_backup."""
|
|
await setup_backup_integration(hass, with_hassio=False)
|
|
|
|
client = await hass_ws_client(hass)
|
|
freezer.move_to("2024-11-13 12:01:00+01:00")
|
|
await hass.async_block_till_done()
|
|
|
|
with patch(
|
|
"homeassistant.components.backup.manager.BackupManager.async_initiate_backup",
|
|
return_value=NewBackup(backup_job_id="abc123"),
|
|
) as generate_backup:
|
|
await client.send_json_auto_id({"type": "backup/generate"} | params)
|
|
result = await client.receive_json()
|
|
assert result["success"]
|
|
assert result["result"] == {"backup_job_id": "abc123"}
|
|
generate_backup.assert_called_once_with(
|
|
**{
|
|
"include_all_addons": False,
|
|
"include_homeassistant": True,
|
|
"include_addons": None,
|
|
"include_database": True,
|
|
"include_folders": None,
|
|
"name": None,
|
|
"password": None,
|
|
}
|
|
| expected_extra_call_params
|
|
)
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_backup_generation")
|
|
@pytest.mark.parametrize(
|
|
("create_backup_settings", "expected_call_params"),
|
|
[
|
|
(
|
|
{},
|
|
{
|
|
"agent_ids": [],
|
|
"include_addons": None,
|
|
"include_all_addons": False,
|
|
"include_database": True,
|
|
"include_folders": None,
|
|
"include_homeassistant": True,
|
|
"name": None,
|
|
"password": None,
|
|
"with_strategy_settings": True,
|
|
},
|
|
),
|
|
(
|
|
{
|
|
"agent_ids": ["test-agent"],
|
|
"include_addons": ["test-addon"],
|
|
"include_all_addons": False,
|
|
"include_database": True,
|
|
"include_folders": ["media"],
|
|
"name": "test-name",
|
|
"password": "test-password",
|
|
},
|
|
{
|
|
"agent_ids": ["test-agent"],
|
|
"include_addons": ["test-addon"],
|
|
"include_all_addons": False,
|
|
"include_database": True,
|
|
"include_folders": ["media"],
|
|
"include_homeassistant": True,
|
|
"name": "test-name",
|
|
"password": "test-password",
|
|
"with_strategy_settings": True,
|
|
},
|
|
),
|
|
],
|
|
)
|
|
async def test_generate_with_default_settings_calls_create(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
freezer: FrozenDateTimeFactory,
|
|
snapshot: SnapshotAssertion,
|
|
create_backup_settings: dict[str, Any],
|
|
expected_call_params: dict[str, Any],
|
|
) -> None:
|
|
"""Test backup/generate_with_strategy_settings calls async_initiate_backup."""
|
|
await setup_backup_integration(hass, with_hassio=False)
|
|
|
|
client = await hass_ws_client(hass)
|
|
freezer.move_to("2024-11-13 12:01:00+01:00")
|
|
await hass.async_block_till_done()
|
|
|
|
await client.send_json_auto_id(
|
|
{"type": "backup/config/update", "create_backup": create_backup_settings}
|
|
)
|
|
result = await client.receive_json()
|
|
assert result["success"]
|
|
|
|
with patch(
|
|
"homeassistant.components.backup.manager.BackupManager.async_initiate_backup",
|
|
return_value=NewBackup(backup_job_id="abc123"),
|
|
) as generate_backup:
|
|
await client.send_json_auto_id(
|
|
{"type": "backup/generate_with_strategy_settings"}
|
|
)
|
|
result = await client.receive_json()
|
|
assert result["success"]
|
|
assert result["result"] == {"backup_job_id": "abc123"}
|
|
generate_backup.assert_called_once_with(**expected_call_params)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"backups",
|
|
[
|
|
{},
|
|
{LOCAL_AGENT_ID: [TEST_BACKUP_ABC123]},
|
|
],
|
|
)
|
|
async def test_restore_local_agent(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
backups: dict[str, list[AgentBackup]],
|
|
snapshot: SnapshotAssertion,
|
|
) -> None:
|
|
"""Test calling the restore command."""
|
|
await setup_backup_integration(hass, with_hassio=False, backups=backups)
|
|
restart_calls = async_mock_service(hass, "homeassistant", "restart")
|
|
|
|
client = await hass_ws_client(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
with (
|
|
patch("pathlib.Path.exists", return_value=True),
|
|
patch("pathlib.Path.write_text"),
|
|
):
|
|
await client.send_json_auto_id(
|
|
{
|
|
"type": "backup/restore",
|
|
"backup_id": "abc123",
|
|
"agent_id": "backup.local",
|
|
}
|
|
)
|
|
assert await client.receive_json() == snapshot
|
|
assert len(restart_calls) == snapshot
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("remote_agents", "backups"),
|
|
[
|
|
(["remote"], {}),
|
|
(["remote"], {"test.remote": [TEST_BACKUP_ABC123]}),
|
|
],
|
|
)
|
|
async def test_restore_remote_agent(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
remote_agents: list[str],
|
|
backups: dict[str, list[AgentBackup]],
|
|
snapshot: SnapshotAssertion,
|
|
) -> None:
|
|
"""Test calling the restore command."""
|
|
await setup_backup_integration(
|
|
hass, with_hassio=False, backups=backups, remote_agents=remote_agents
|
|
)
|
|
restart_calls = async_mock_service(hass, "homeassistant", "restart")
|
|
|
|
client = await hass_ws_client(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
with patch("pathlib.Path.write_text"), patch("pathlib.Path.open"):
|
|
await client.send_json_auto_id(
|
|
{
|
|
"type": "backup/restore",
|
|
"backup_id": "abc123",
|
|
"agent_id": "test.remote",
|
|
}
|
|
)
|
|
assert await client.receive_json() == snapshot
|
|
assert len(restart_calls) == snapshot
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"access_token_fixture_name",
|
|
["hass_access_token", "hass_supervisor_access_token"],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("with_hassio"),
|
|
[
|
|
pytest.param(True, id="with_hassio"),
|
|
pytest.param(False, id="without_hassio"),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("supervisor_client")
|
|
async def test_backup_end(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
snapshot: SnapshotAssertion,
|
|
sync_access_token_proxy: str,
|
|
*,
|
|
access_token_fixture_name: str,
|
|
with_hassio: bool,
|
|
) -> None:
|
|
"""Test handling of post backup actions from a WS command."""
|
|
await setup_backup_integration(hass, with_hassio=with_hassio)
|
|
|
|
client = await hass_ws_client(hass, sync_access_token_proxy)
|
|
await hass.async_block_till_done()
|
|
|
|
with patch(
|
|
"homeassistant.components.backup.manager.BackupManager.async_post_backup_actions",
|
|
):
|
|
await client.send_json_auto_id({"type": "backup/end"})
|
|
assert await client.receive_json() == snapshot
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"access_token_fixture_name",
|
|
["hass_access_token", "hass_supervisor_access_token"],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("with_hassio"),
|
|
[
|
|
pytest.param(True, id="with_hassio"),
|
|
pytest.param(False, id="without_hassio"),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("supervisor_client")
|
|
async def test_backup_start(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
snapshot: SnapshotAssertion,
|
|
sync_access_token_proxy: str,
|
|
*,
|
|
access_token_fixture_name: str,
|
|
with_hassio: bool,
|
|
) -> None:
|
|
"""Test handling of pre backup actions from a WS command."""
|
|
await setup_backup_integration(hass, with_hassio=with_hassio)
|
|
|
|
client = await hass_ws_client(hass, sync_access_token_proxy)
|
|
await hass.async_block_till_done()
|
|
|
|
with patch(
|
|
"homeassistant.components.backup.manager.BackupManager.async_pre_backup_actions",
|
|
):
|
|
await client.send_json_auto_id({"type": "backup/start"})
|
|
assert await client.receive_json() == snapshot
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"exception",
|
|
[
|
|
TimeoutError(),
|
|
HomeAssistantError("Boom"),
|
|
Exception("Boom"),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("supervisor_client")
|
|
async def test_backup_end_exception(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
snapshot: SnapshotAssertion,
|
|
hass_supervisor_access_token: str,
|
|
exception: Exception,
|
|
) -> None:
|
|
"""Test exception handling while running post backup actions from a WS command."""
|
|
await setup_backup_integration(hass, with_hassio=True)
|
|
|
|
client = await hass_ws_client(hass, hass_supervisor_access_token)
|
|
await hass.async_block_till_done()
|
|
|
|
with patch(
|
|
"homeassistant.components.backup.manager.BackupManager.async_post_backup_actions",
|
|
side_effect=exception,
|
|
):
|
|
await client.send_json_auto_id({"type": "backup/end"})
|
|
assert await client.receive_json() == snapshot
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"exception",
|
|
[
|
|
TimeoutError(),
|
|
HomeAssistantError("Boom"),
|
|
Exception("Boom"),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("supervisor_client")
|
|
async def test_backup_start_exception(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
snapshot: SnapshotAssertion,
|
|
hass_supervisor_access_token: str,
|
|
exception: Exception,
|
|
) -> None:
|
|
"""Test exception handling while running pre backup actions from a WS command."""
|
|
await setup_backup_integration(hass, with_hassio=True)
|
|
|
|
client = await hass_ws_client(hass, hass_supervisor_access_token)
|
|
await hass.async_block_till_done()
|
|
|
|
with patch(
|
|
"homeassistant.components.backup.manager.BackupManager.async_pre_backup_actions",
|
|
side_effect=exception,
|
|
):
|
|
await client.send_json_auto_id({"type": "backup/start"})
|
|
assert await client.receive_json() == snapshot
|
|
|
|
|
|
async def test_agents_info(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
snapshot: SnapshotAssertion,
|
|
) -> None:
|
|
"""Test getting backup agents info."""
|
|
await setup_backup_integration(hass, with_hassio=False)
|
|
hass.data[DATA_MANAGER].backup_agents["domain.test"] = BackupAgentTest("test")
|
|
|
|
client = await hass_ws_client(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
await client.send_json_auto_id({"type": "backup/agents/info"})
|
|
assert await client.receive_json() == snapshot
|
|
|
|
|
|
@pytest.mark.usefixtures("create_backup", "delete_backup", "get_backups")
|
|
@pytest.mark.parametrize(
|
|
"storage_data",
|
|
[
|
|
None,
|
|
{
|
|
"backups": {},
|
|
"config": {
|
|
"create_backup": {
|
|
"agent_ids": ["test-agent"],
|
|
"include_addons": ["test-addon"],
|
|
"include_all_addons": True,
|
|
"include_database": True,
|
|
"include_folders": ["media"],
|
|
"name": "test-name",
|
|
"password": "test-password",
|
|
},
|
|
"retention": {"copies": 3, "days": 7},
|
|
"last_attempted_strategy_backup": datetime.fromisoformat(
|
|
"2024-10-26T04:45:00+01:00"
|
|
),
|
|
"last_completed_strategy_backup": datetime.fromisoformat(
|
|
"2024-10-26T04:45:00+01:00"
|
|
),
|
|
"schedule": {"state": "daily"},
|
|
},
|
|
},
|
|
{
|
|
"backups": {},
|
|
"config": {
|
|
"create_backup": {
|
|
"agent_ids": ["test-agent"],
|
|
"include_addons": None,
|
|
"include_all_addons": False,
|
|
"include_database": False,
|
|
"include_folders": None,
|
|
"name": None,
|
|
"password": None,
|
|
},
|
|
"retention": {"copies": 3, "days": None},
|
|
"last_attempted_strategy_backup": None,
|
|
"last_completed_strategy_backup": None,
|
|
"schedule": {"state": "never"},
|
|
},
|
|
},
|
|
{
|
|
"backups": {},
|
|
"config": {
|
|
"create_backup": {
|
|
"agent_ids": ["test-agent"],
|
|
"include_addons": None,
|
|
"include_all_addons": False,
|
|
"include_database": False,
|
|
"include_folders": None,
|
|
"name": None,
|
|
"password": None,
|
|
},
|
|
"retention": {"copies": None, "days": 7},
|
|
"last_attempted_strategy_backup": datetime.fromisoformat(
|
|
"2024-10-27T04:45:00+01:00"
|
|
),
|
|
"last_completed_strategy_backup": datetime.fromisoformat(
|
|
"2024-10-26T04:45:00+01:00"
|
|
),
|
|
"schedule": {"state": "never"},
|
|
},
|
|
},
|
|
{
|
|
"backups": {},
|
|
"config": {
|
|
"create_backup": {
|
|
"agent_ids": ["test-agent"],
|
|
"include_addons": None,
|
|
"include_all_addons": False,
|
|
"include_database": False,
|
|
"include_folders": None,
|
|
"name": None,
|
|
"password": None,
|
|
},
|
|
"retention": {"copies": None, "days": None},
|
|
"last_attempted_strategy_backup": None,
|
|
"last_completed_strategy_backup": None,
|
|
"schedule": {"state": "mon"},
|
|
},
|
|
},
|
|
{
|
|
"backups": {},
|
|
"config": {
|
|
"create_backup": {
|
|
"agent_ids": ["test-agent"],
|
|
"include_addons": None,
|
|
"include_all_addons": False,
|
|
"include_database": False,
|
|
"include_folders": None,
|
|
"name": None,
|
|
"password": None,
|
|
},
|
|
"retention": {"copies": None, "days": None},
|
|
"last_attempted_strategy_backup": None,
|
|
"last_completed_strategy_backup": None,
|
|
"schedule": {"state": "sat"},
|
|
},
|
|
},
|
|
],
|
|
)
|
|
async def test_config_info(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
snapshot: SnapshotAssertion,
|
|
hass_storage: dict[str, Any],
|
|
storage_data: dict[str, Any] | None,
|
|
) -> None:
|
|
"""Test getting backup config info."""
|
|
hass_storage[DOMAIN] = {
|
|
"data": storage_data,
|
|
"key": DOMAIN,
|
|
"version": 1,
|
|
}
|
|
|
|
await setup_backup_integration(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
client = await hass_ws_client(hass)
|
|
|
|
await client.send_json_auto_id({"type": "backup/config/info"})
|
|
assert await client.receive_json() == snapshot
|
|
|
|
|
|
@pytest.mark.usefixtures("create_backup", "delete_backup", "get_backups")
|
|
@pytest.mark.parametrize(
|
|
"command",
|
|
[
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"retention": {"copies": None, "days": 7},
|
|
},
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"schedule": "daily",
|
|
},
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"schedule": "mon",
|
|
},
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"schedule": "never",
|
|
},
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {
|
|
"agent_ids": ["test-agent"],
|
|
"include_addons": ["test-addon"],
|
|
"include_folders": ["media"],
|
|
"name": "test-name",
|
|
"password": "test-password",
|
|
},
|
|
"schedule": "daily",
|
|
},
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"retention": {"copies": 3, "days": 7},
|
|
"schedule": "daily",
|
|
},
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"retention": {"copies": None, "days": None},
|
|
"schedule": "daily",
|
|
},
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"retention": {"copies": 3, "days": None},
|
|
"schedule": "daily",
|
|
},
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"retention": {"copies": None, "days": 7},
|
|
"schedule": "daily",
|
|
},
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"retention": {"copies": 3},
|
|
"schedule": "daily",
|
|
},
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"retention": {"days": 7},
|
|
"schedule": "daily",
|
|
},
|
|
],
|
|
)
|
|
async def test_config_update(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
snapshot: SnapshotAssertion,
|
|
command: dict[str, Any],
|
|
hass_storage: dict[str, Any],
|
|
) -> None:
|
|
"""Test updating the backup config."""
|
|
await setup_backup_integration(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
client = await hass_ws_client(hass)
|
|
|
|
await client.send_json_auto_id({"type": "backup/config/info"})
|
|
assert await client.receive_json() == snapshot
|
|
|
|
await client.send_json_auto_id(command)
|
|
result = await client.receive_json()
|
|
|
|
assert result["success"]
|
|
|
|
await client.send_json_auto_id({"type": "backup/config/info"})
|
|
assert await client.receive_json() == snapshot
|
|
await hass.async_block_till_done()
|
|
|
|
assert hass_storage[DOMAIN] == snapshot
|
|
|
|
|
|
@pytest.mark.usefixtures("create_backup", "delete_backup", "get_backups")
|
|
@pytest.mark.parametrize(
|
|
"command",
|
|
[
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"schedule": "someday",
|
|
},
|
|
],
|
|
)
|
|
async def test_config_update_errors(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
snapshot: SnapshotAssertion,
|
|
command: dict[str, Any],
|
|
) -> None:
|
|
"""Test errors when updating the backup config."""
|
|
await setup_backup_integration(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
client = await hass_ws_client(hass)
|
|
|
|
await client.send_json_auto_id({"type": "backup/config/info"})
|
|
assert await client.receive_json() == snapshot
|
|
|
|
await client.send_json_auto_id(command)
|
|
result = await client.receive_json()
|
|
|
|
assert not result["success"]
|
|
|
|
await client.send_json_auto_id({"type": "backup/config/info"})
|
|
assert await client.receive_json() == snapshot
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
(
|
|
"command",
|
|
"last_completed_strategy_backup",
|
|
"time_1",
|
|
"time_2",
|
|
"attempted_backup_time",
|
|
"completed_backup_time",
|
|
"backup_calls_1",
|
|
"backup_calls_2",
|
|
"call_args",
|
|
"create_backup_side_effect",
|
|
),
|
|
[
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test.test-agent"]},
|
|
"schedule": "daily",
|
|
},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
"2024-11-13T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
1,
|
|
2,
|
|
BACKUP_CALL,
|
|
None,
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test.test-agent"]},
|
|
"schedule": "mon",
|
|
},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-18T04:45:00+01:00",
|
|
"2024-11-25T04:45:00+01:00",
|
|
"2024-11-18T04:45:00+01:00",
|
|
"2024-11-18T04:45:00+01:00",
|
|
1,
|
|
2,
|
|
BACKUP_CALL,
|
|
None,
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test.test-agent"]},
|
|
"schedule": "never",
|
|
},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2034-11-11T12:00:00+01:00", # ten years later and still no backups
|
|
"2034-11-11T13:00:00+01:00",
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-11T04:45:00+01:00",
|
|
0,
|
|
0,
|
|
None,
|
|
None,
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test.test-agent"]},
|
|
"schedule": "daily",
|
|
},
|
|
"2024-10-26T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
"2024-11-13T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
1,
|
|
2,
|
|
BACKUP_CALL,
|
|
None,
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test.test-agent"]},
|
|
"schedule": "mon",
|
|
},
|
|
"2024-10-26T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
"2024-11-13T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00", # missed event uses daily schedule once
|
|
"2024-11-12T04:45:00+01:00", # missed event uses daily schedule once
|
|
1,
|
|
1,
|
|
BACKUP_CALL,
|
|
None,
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test.test-agent"]},
|
|
"schedule": "never",
|
|
},
|
|
"2024-10-26T04:45:00+01:00",
|
|
"2034-11-11T12:00:00+01:00", # ten years later and still no backups
|
|
"2034-11-12T12:00:00+01:00",
|
|
"2024-10-26T04:45:00+01:00",
|
|
"2024-10-26T04:45:00+01:00",
|
|
0,
|
|
0,
|
|
None,
|
|
None,
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test.test-agent"]},
|
|
"schedule": "daily",
|
|
},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
"2024-11-13T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00", # attempted to create backup but failed
|
|
"2024-11-11T04:45:00+01:00",
|
|
1,
|
|
2,
|
|
BACKUP_CALL,
|
|
[Exception("Boom"), None],
|
|
),
|
|
],
|
|
)
|
|
async def test_config_schedule_logic(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
freezer: FrozenDateTimeFactory,
|
|
hass_storage: dict[str, Any],
|
|
create_backup: AsyncMock,
|
|
command: dict[str, Any],
|
|
last_completed_strategy_backup: str,
|
|
time_1: str,
|
|
time_2: str,
|
|
attempted_backup_time: str,
|
|
completed_backup_time: str,
|
|
backup_calls_1: int,
|
|
backup_calls_2: int,
|
|
call_args: Any,
|
|
create_backup_side_effect: list[Exception | None] | None,
|
|
) -> None:
|
|
"""Test config schedule logic."""
|
|
client = await hass_ws_client(hass)
|
|
storage_data = {
|
|
"backups": {},
|
|
"config": {
|
|
"create_backup": {
|
|
"agent_ids": ["test-agent"],
|
|
"include_addons": ["test-addon"],
|
|
"include_all_addons": False,
|
|
"include_database": True,
|
|
"include_folders": ["media"],
|
|
"name": "test-name",
|
|
"password": "test-password",
|
|
},
|
|
"retention": {"copies": None, "days": None},
|
|
"last_attempted_strategy_backup": datetime.fromisoformat(
|
|
last_completed_strategy_backup
|
|
),
|
|
"last_completed_strategy_backup": datetime.fromisoformat(
|
|
last_completed_strategy_backup
|
|
),
|
|
"schedule": {"state": "daily"},
|
|
},
|
|
}
|
|
hass_storage[DOMAIN] = {
|
|
"data": storage_data,
|
|
"key": DOMAIN,
|
|
"version": 1,
|
|
}
|
|
create_backup.side_effect = create_backup_side_effect
|
|
await hass.config.async_set_time_zone("Europe/Amsterdam")
|
|
freezer.move_to("2024-11-11 12:00:00+01:00")
|
|
|
|
await setup_backup_integration(hass, remote_agents=["test-agent"])
|
|
await hass.async_block_till_done()
|
|
|
|
await client.send_json_auto_id(command)
|
|
result = await client.receive_json()
|
|
|
|
assert result["success"]
|
|
|
|
freezer.move_to(time_1)
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
assert create_backup.call_count == backup_calls_1
|
|
assert create_backup.call_args == call_args
|
|
async_fire_time_changed(hass, fire_all=True) # flush out storage save
|
|
await hass.async_block_till_done()
|
|
assert (
|
|
hass_storage[DOMAIN]["data"]["config"]["last_attempted_strategy_backup"]
|
|
== attempted_backup_time
|
|
)
|
|
assert (
|
|
hass_storage[DOMAIN]["data"]["config"]["last_completed_strategy_backup"]
|
|
== completed_backup_time
|
|
)
|
|
|
|
freezer.move_to(time_2)
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
assert create_backup.call_count == backup_calls_2
|
|
assert create_backup.call_args == call_args
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
(
|
|
"command",
|
|
"backups",
|
|
"get_backups_agent_errors",
|
|
"delete_backup_agent_errors",
|
|
"last_backup_time",
|
|
"next_time",
|
|
"backup_time",
|
|
"backup_calls",
|
|
"get_backups_calls",
|
|
"delete_calls",
|
|
"delete_args_list",
|
|
),
|
|
[
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test.test-agent"]},
|
|
"retention": {"copies": None, "days": None},
|
|
"schedule": "daily",
|
|
},
|
|
{
|
|
"backup-1": MagicMock(date="2024-11-10T04:45:00+01:00"),
|
|
"backup-2": MagicMock(date="2024-11-11T04:45:00+01:00"),
|
|
"backup-3": MagicMock(date="2024-11-12T04:45:00+01:00"),
|
|
},
|
|
{},
|
|
{},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
1,
|
|
1, # we get backups even if backup retention copies is None
|
|
0,
|
|
[],
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test.test-agent"]},
|
|
"retention": {"copies": 3, "days": None},
|
|
"schedule": "daily",
|
|
},
|
|
{
|
|
"backup-1": MagicMock(date="2024-11-10T04:45:00+01:00"),
|
|
"backup-2": MagicMock(date="2024-11-11T04:45:00+01:00"),
|
|
"backup-3": MagicMock(date="2024-11-12T04:45:00+01:00"),
|
|
},
|
|
{},
|
|
{},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
1,
|
|
1,
|
|
0,
|
|
[],
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test.test-agent"]},
|
|
"retention": {"copies": 3, "days": None},
|
|
"schedule": "daily",
|
|
},
|
|
{
|
|
"backup-1": MagicMock(date="2024-11-09T04:45:00+01:00"),
|
|
"backup-2": MagicMock(date="2024-11-10T04:45:00+01:00"),
|
|
"backup-3": MagicMock(date="2024-11-11T04:45:00+01:00"),
|
|
"backup-4": MagicMock(date="2024-11-12T04:45:00+01:00"),
|
|
},
|
|
{},
|
|
{},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
1,
|
|
1,
|
|
1,
|
|
[call("backup-1")],
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test.test-agent"]},
|
|
"retention": {"copies": 2, "days": None},
|
|
"schedule": "daily",
|
|
},
|
|
{
|
|
"backup-1": MagicMock(date="2024-11-09T04:45:00+01:00"),
|
|
"backup-2": MagicMock(date="2024-11-10T04:45:00+01:00"),
|
|
"backup-3": MagicMock(date="2024-11-11T04:45:00+01:00"),
|
|
"backup-4": MagicMock(date="2024-11-12T04:45:00+01:00"),
|
|
},
|
|
{},
|
|
{},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
1,
|
|
1,
|
|
2,
|
|
[call("backup-1"), call("backup-2")],
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test.test-agent"]},
|
|
"retention": {"copies": 2, "days": None},
|
|
"schedule": "daily",
|
|
},
|
|
{
|
|
"backup-1": MagicMock(date="2024-11-10T04:45:00+01:00"),
|
|
"backup-2": MagicMock(date="2024-11-11T04:45:00+01:00"),
|
|
"backup-3": MagicMock(date="2024-11-12T04:45:00+01:00"),
|
|
},
|
|
{"test-agent": BackupAgentError("Boom!")},
|
|
{},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
1,
|
|
1,
|
|
1,
|
|
[call("backup-1")],
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test.test-agent"]},
|
|
"retention": {"copies": 2, "days": None},
|
|
"schedule": "daily",
|
|
},
|
|
{
|
|
"backup-1": MagicMock(date="2024-11-10T04:45:00+01:00"),
|
|
"backup-2": MagicMock(date="2024-11-11T04:45:00+01:00"),
|
|
"backup-3": MagicMock(date="2024-11-12T04:45:00+01:00"),
|
|
},
|
|
{},
|
|
{"test-agent": BackupAgentError("Boom!")},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
1,
|
|
1,
|
|
1,
|
|
[call("backup-1")],
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test.test-agent"]},
|
|
"retention": {"copies": 0, "days": None},
|
|
"schedule": "daily",
|
|
},
|
|
{
|
|
"backup-1": MagicMock(date="2024-11-09T04:45:00+01:00"),
|
|
"backup-2": MagicMock(date="2024-11-10T04:45:00+01:00"),
|
|
"backup-3": MagicMock(date="2024-11-11T04:45:00+01:00"),
|
|
"backup-4": MagicMock(date="2024-11-12T04:45:00+01:00"),
|
|
},
|
|
{},
|
|
{},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
1,
|
|
1,
|
|
3,
|
|
[call("backup-1"), call("backup-2"), call("backup-3")],
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test.test-agent"]},
|
|
"retention": {"copies": 0, "days": None},
|
|
"schedule": "daily",
|
|
},
|
|
{
|
|
"backup-1": MagicMock(date="2024-11-12T04:45:00+01:00"),
|
|
},
|
|
{},
|
|
{},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
"2024-11-12T04:45:00+01:00",
|
|
1,
|
|
1,
|
|
0,
|
|
[],
|
|
),
|
|
],
|
|
)
|
|
async def test_config_retention_copies_logic(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
freezer: FrozenDateTimeFactory,
|
|
hass_storage: dict[str, Any],
|
|
create_backup: AsyncMock,
|
|
delete_backup: AsyncMock,
|
|
get_backups: AsyncMock,
|
|
command: dict[str, Any],
|
|
backups: dict[str, Any],
|
|
get_backups_agent_errors: dict[str, Exception],
|
|
delete_backup_agent_errors: dict[str, Exception],
|
|
last_backup_time: str,
|
|
next_time: str,
|
|
backup_time: str,
|
|
backup_calls: int,
|
|
get_backups_calls: int,
|
|
delete_calls: int,
|
|
delete_args_list: Any,
|
|
) -> None:
|
|
"""Test config backup retention copies logic."""
|
|
client = await hass_ws_client(hass)
|
|
storage_data = {
|
|
"backups": {},
|
|
"config": {
|
|
"create_backup": {
|
|
"agent_ids": ["test-agent"],
|
|
"include_addons": ["test-addon"],
|
|
"include_all_addons": False,
|
|
"include_database": True,
|
|
"include_folders": ["media"],
|
|
"name": "test-name",
|
|
"password": "test-password",
|
|
},
|
|
"retention": {"copies": None, "days": None},
|
|
"last_attempted_strategy_backup": None,
|
|
"last_completed_strategy_backup": datetime.fromisoformat(last_backup_time),
|
|
"schedule": {"state": "daily"},
|
|
},
|
|
}
|
|
hass_storage[DOMAIN] = {
|
|
"data": storage_data,
|
|
"key": DOMAIN,
|
|
"version": 1,
|
|
}
|
|
get_backups.return_value = (backups, get_backups_agent_errors)
|
|
delete_backup.return_value = delete_backup_agent_errors
|
|
await hass.config.async_set_time_zone("Europe/Amsterdam")
|
|
freezer.move_to("2024-11-11 12:00:00+01:00")
|
|
|
|
await setup_backup_integration(hass, remote_agents=["test-agent"])
|
|
await hass.async_block_till_done()
|
|
|
|
await client.send_json_auto_id(command)
|
|
result = await client.receive_json()
|
|
|
|
assert result["success"]
|
|
|
|
freezer.move_to(next_time)
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
assert create_backup.call_count == backup_calls
|
|
assert get_backups.call_count == get_backups_calls
|
|
assert delete_backup.call_count == delete_calls
|
|
assert delete_backup.call_args_list == delete_args_list
|
|
async_fire_time_changed(hass, fire_all=True) # flush out storage save
|
|
await hass.async_block_till_done()
|
|
assert (
|
|
hass_storage[DOMAIN]["data"]["config"]["last_attempted_strategy_backup"]
|
|
== backup_time
|
|
)
|
|
assert (
|
|
hass_storage[DOMAIN]["data"]["config"]["last_completed_strategy_backup"]
|
|
== backup_time
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
(
|
|
"command",
|
|
"backups",
|
|
"get_backups_agent_errors",
|
|
"delete_backup_agent_errors",
|
|
"last_backup_time",
|
|
"start_time",
|
|
"next_time",
|
|
"get_backups_calls",
|
|
"delete_calls",
|
|
"delete_args_list",
|
|
),
|
|
[
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"retention": {"copies": None, "days": 2},
|
|
"schedule": "never",
|
|
},
|
|
{
|
|
"backup-1": MagicMock(date="2024-11-10T04:45:00+01:00"),
|
|
"backup-2": MagicMock(date="2024-11-11T04:45:00+01:00"),
|
|
},
|
|
{},
|
|
{},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-11T12:00:00+01:00",
|
|
"2024-11-12T12:00:00+01:00",
|
|
1,
|
|
1,
|
|
[call("backup-1")],
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"retention": {"copies": None, "days": 3},
|
|
"schedule": "never",
|
|
},
|
|
{
|
|
"backup-1": MagicMock(date="2024-11-10T04:45:00+01:00"),
|
|
"backup-2": MagicMock(date="2024-11-11T04:45:00+01:00"),
|
|
},
|
|
{},
|
|
{},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-11T12:00:00+01:00",
|
|
"2024-11-12T12:00:00+01:00",
|
|
1,
|
|
0,
|
|
[],
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"retention": {"copies": None, "days": 2},
|
|
"schedule": "never",
|
|
},
|
|
{
|
|
"backup-1": MagicMock(date="2024-11-09T04:45:00+01:00"),
|
|
"backup-2": MagicMock(date="2024-11-10T04:45:00+01:00"),
|
|
"backup-3": MagicMock(date="2024-11-11T04:45:00+01:00"),
|
|
},
|
|
{},
|
|
{},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-11T12:00:00+01:00",
|
|
"2024-11-12T12:00:00+01:00",
|
|
1,
|
|
2,
|
|
[call("backup-1"), call("backup-2")],
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"retention": {"copies": None, "days": 2},
|
|
"schedule": "never",
|
|
},
|
|
{
|
|
"backup-1": MagicMock(date="2024-11-10T04:45:00+01:00"),
|
|
"backup-2": MagicMock(date="2024-11-11T04:45:00+01:00"),
|
|
},
|
|
{"test-agent": BackupAgentError("Boom!")},
|
|
{},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-11T12:00:00+01:00",
|
|
"2024-11-12T12:00:00+01:00",
|
|
1,
|
|
1,
|
|
[call("backup-1")],
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"retention": {"copies": None, "days": 2},
|
|
"schedule": "never",
|
|
},
|
|
{
|
|
"backup-1": MagicMock(date="2024-11-10T04:45:00+01:00"),
|
|
"backup-2": MagicMock(date="2024-11-11T04:45:00+01:00"),
|
|
},
|
|
{},
|
|
{"test-agent": BackupAgentError("Boom!")},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-11T12:00:00+01:00",
|
|
"2024-11-12T12:00:00+01:00",
|
|
1,
|
|
1,
|
|
[call("backup-1")],
|
|
),
|
|
(
|
|
{
|
|
"type": "backup/config/update",
|
|
"create_backup": {"agent_ids": ["test-agent"]},
|
|
"retention": {"copies": None, "days": 0},
|
|
"schedule": "never",
|
|
},
|
|
{
|
|
"backup-1": MagicMock(date="2024-11-09T04:45:00+01:00"),
|
|
"backup-2": MagicMock(date="2024-11-10T04:45:00+01:00"),
|
|
"backup-3": MagicMock(date="2024-11-11T04:45:00+01:00"),
|
|
},
|
|
{},
|
|
{},
|
|
"2024-11-11T04:45:00+01:00",
|
|
"2024-11-11T12:00:00+01:00",
|
|
"2024-11-12T12:00:00+01:00",
|
|
1,
|
|
2,
|
|
[call("backup-1"), call("backup-2")],
|
|
),
|
|
],
|
|
)
|
|
async def test_config_retention_days_logic(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
freezer: FrozenDateTimeFactory,
|
|
hass_storage: dict[str, Any],
|
|
delete_backup: AsyncMock,
|
|
get_backups: AsyncMock,
|
|
command: dict[str, Any],
|
|
backups: dict[str, Any],
|
|
get_backups_agent_errors: dict[str, Exception],
|
|
delete_backup_agent_errors: dict[str, Exception],
|
|
last_backup_time: str,
|
|
start_time: str,
|
|
next_time: str,
|
|
get_backups_calls: int,
|
|
delete_calls: int,
|
|
delete_args_list: list[Any],
|
|
) -> None:
|
|
"""Test config backup retention logic."""
|
|
client = await hass_ws_client(hass)
|
|
storage_data = {
|
|
"backups": {},
|
|
"config": {
|
|
"create_backup": {
|
|
"agent_ids": ["test-agent"],
|
|
"include_addons": ["test-addon"],
|
|
"include_all_addons": False,
|
|
"include_database": True,
|
|
"include_folders": ["media"],
|
|
"name": "test-name",
|
|
"password": "test-password",
|
|
},
|
|
"retention": {"copies": None, "days": None},
|
|
"last_attempted_strategy_backup": None,
|
|
"last_completed_strategy_backup": datetime.fromisoformat(last_backup_time),
|
|
"schedule": {"state": "never"},
|
|
},
|
|
}
|
|
hass_storage[DOMAIN] = {
|
|
"data": storage_data,
|
|
"key": DOMAIN,
|
|
"version": 1,
|
|
}
|
|
get_backups.return_value = (backups, get_backups_agent_errors)
|
|
delete_backup.return_value = delete_backup_agent_errors
|
|
await hass.config.async_set_time_zone("Europe/Amsterdam")
|
|
freezer.move_to(start_time)
|
|
|
|
await setup_backup_integration(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
await client.send_json_auto_id(command)
|
|
result = await client.receive_json()
|
|
|
|
assert result["success"]
|
|
|
|
freezer.move_to(next_time)
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
assert get_backups.call_count == get_backups_calls
|
|
assert delete_backup.call_count == delete_calls
|
|
assert delete_backup.call_args_list == delete_args_list
|
|
async_fire_time_changed(hass, fire_all=True) # flush out storage save
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
async def test_subscribe_event(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
snapshot: SnapshotAssertion,
|
|
) -> None:
|
|
"""Test generating a backup."""
|
|
await setup_backup_integration(hass, with_hassio=False)
|
|
|
|
manager = hass.data[DATA_MANAGER]
|
|
|
|
client = await hass_ws_client(hass)
|
|
|
|
await client.send_json_auto_id({"type": "backup/subscribe_events"})
|
|
assert await client.receive_json() == snapshot
|
|
assert await client.receive_json() == snapshot
|
|
|
|
manager.async_on_backup_event(
|
|
CreateBackupEvent(stage=None, state=CreateBackupState.IN_PROGRESS)
|
|
)
|
|
assert await client.receive_json() == snapshot
|