Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Mike Degatano
645cdf5692 Backup method to aiohasupervisor 2024-11-11 19:49:24 +00:00
8 changed files with 116 additions and 173 deletions

View file

@ -116,7 +116,6 @@ from .discovery import async_setup_discovery_view # noqa: F401
from .handler import ( # noqa: F401 from .handler import ( # noqa: F401
HassIO, HassIO,
HassioAPIError, HassioAPIError,
async_create_backup,
async_get_green_settings, async_get_green_settings,
async_get_yellow_settings, async_get_yellow_settings,
async_reboot_host, async_reboot_host,

View file

@ -15,13 +15,14 @@ from aiohasupervisor.models import (
AddonsOptions, AddonsOptions,
AddonState as SupervisorAddonState, AddonState as SupervisorAddonState,
InstalledAddonComplete, InstalledAddonComplete,
PartialBackupOptions,
StoreAddonUpdate, StoreAddonUpdate,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from .handler import HassioAPIError, async_create_backup, get_supervisor_client from .handler import get_supervisor_client
type _FuncType[_T, **_P, _R] = Callable[Concatenate[_T, _P], Awaitable[_R]] type _FuncType[_T, **_P, _R] = Callable[Concatenate[_T, _P], Awaitable[_R]]
type _ReturnFuncType[_T, **_P, _R] = Callable[ type _ReturnFuncType[_T, **_P, _R] = Callable[
@ -31,18 +32,15 @@ type _ReturnFuncType[_T, **_P, _R] = Callable[
def api_error[_AddonManagerT: AddonManager, **_P, _R]( def api_error[_AddonManagerT: AddonManager, **_P, _R](
error_message: str, error_message: str,
*,
expected_error_type: type[HassioAPIError | SupervisorError] | None = None,
) -> Callable[ ) -> Callable[
[_FuncType[_AddonManagerT, _P, _R]], _ReturnFuncType[_AddonManagerT, _P, _R] [_FuncType[_AddonManagerT, _P, _R]], _ReturnFuncType[_AddonManagerT, _P, _R]
]: ]:
"""Handle HassioAPIError and raise a specific AddonError.""" """Handle SupervisorError and raise a specific AddonError."""
error_type = expected_error_type or (HassioAPIError, SupervisorError)
def handle_hassio_api_error( def handle_supervisor_api_error(
func: _FuncType[_AddonManagerT, _P, _R], func: _FuncType[_AddonManagerT, _P, _R],
) -> _ReturnFuncType[_AddonManagerT, _P, _R]: ) -> _ReturnFuncType[_AddonManagerT, _P, _R]:
"""Handle a HassioAPIError.""" """Handle a SupervisorError."""
@wraps(func) @wraps(func)
async def wrapper( async def wrapper(
@ -51,7 +49,7 @@ def api_error[_AddonManagerT: AddonManager, **_P, _R](
"""Wrap an add-on manager method.""" """Wrap an add-on manager method."""
try: try:
return_value = await func(self, *args, **kwargs) return_value = await func(self, *args, **kwargs)
except error_type as err: except SupervisorError as err:
raise AddonError( raise AddonError(
f"{error_message.format(addon_name=self.addon_name)}: {err}" f"{error_message.format(addon_name=self.addon_name)}: {err}"
) from err ) from err
@ -60,7 +58,7 @@ def api_error[_AddonManagerT: AddonManager, **_P, _R](
return wrapper return wrapper
return handle_hassio_api_error return handle_supervisor_api_error
@dataclass @dataclass
@ -123,10 +121,7 @@ class AddonManager:
) )
) )
@api_error( @api_error("Failed to get the {addon_name} add-on discovery info")
"Failed to get the {addon_name} add-on discovery info",
expected_error_type=SupervisorError,
)
async def async_get_addon_discovery_info(self) -> dict: async def async_get_addon_discovery_info(self) -> dict:
"""Return add-on discovery info.""" """Return add-on discovery info."""
discovery_info = next( discovery_info = next(
@ -143,10 +138,7 @@ class AddonManager:
return discovery_info.config return discovery_info.config
@api_error( @api_error("Failed to get the {addon_name} add-on info")
"Failed to get the {addon_name} add-on info",
expected_error_type=SupervisorError,
)
async def async_get_addon_info(self) -> AddonInfo: async def async_get_addon_info(self) -> AddonInfo:
"""Return and cache manager add-on info.""" """Return and cache manager add-on info."""
addon_store_info = await self._supervisor_client.store.addon_info( addon_store_info = await self._supervisor_client.store.addon_info(
@ -188,10 +180,7 @@ class AddonManager:
return addon_state return addon_state
@api_error( @api_error("Failed to set the {addon_name} add-on options")
"Failed to set the {addon_name} add-on options",
expected_error_type=SupervisorError,
)
async def async_set_addon_options(self, config: dict) -> None: async def async_set_addon_options(self, config: dict) -> None:
"""Set manager add-on options.""" """Set manager add-on options."""
await self._supervisor_client.addons.set_addon_options( await self._supervisor_client.addons.set_addon_options(
@ -203,9 +192,7 @@ class AddonManager:
if not addon_info.available: if not addon_info.available:
raise AddonError(f"{self.addon_name} add-on is not available") raise AddonError(f"{self.addon_name} add-on is not available")
@api_error( @api_error("Failed to install the {addon_name} add-on")
"Failed to install the {addon_name} add-on", expected_error_type=SupervisorError
)
async def async_install_addon(self) -> None: async def async_install_addon(self) -> None:
"""Install the managed add-on.""" """Install the managed add-on."""
addon_info = await self.async_get_addon_info() addon_info = await self.async_get_addon_info()
@ -214,10 +201,7 @@ class AddonManager:
await self._supervisor_client.store.install_addon(self.addon_slug) await self._supervisor_client.store.install_addon(self.addon_slug)
@api_error( @api_error("Failed to uninstall the {addon_name} add-on")
"Failed to uninstall the {addon_name} add-on",
expected_error_type=SupervisorError,
)
async def async_uninstall_addon(self) -> None: async def async_uninstall_addon(self) -> None:
"""Uninstall the managed add-on.""" """Uninstall the managed add-on."""
await self._supervisor_client.addons.uninstall_addon(self.addon_slug) await self._supervisor_client.addons.uninstall_addon(self.addon_slug)
@ -240,23 +224,17 @@ class AddonManager:
self.addon_slug, StoreAddonUpdate(backup=False) self.addon_slug, StoreAddonUpdate(backup=False)
) )
@api_error( @api_error("Failed to start the {addon_name} add-on")
"Failed to start the {addon_name} add-on", expected_error_type=SupervisorError
)
async def async_start_addon(self) -> None: async def async_start_addon(self) -> None:
"""Start the managed add-on.""" """Start the managed add-on."""
await self._supervisor_client.addons.start_addon(self.addon_slug) await self._supervisor_client.addons.start_addon(self.addon_slug)
@api_error( @api_error("Failed to restart the {addon_name} add-on")
"Failed to restart the {addon_name} add-on", expected_error_type=SupervisorError
)
async def async_restart_addon(self) -> None: async def async_restart_addon(self) -> None:
"""Restart the managed add-on.""" """Restart the managed add-on."""
await self._supervisor_client.addons.restart_addon(self.addon_slug) await self._supervisor_client.addons.restart_addon(self.addon_slug)
@api_error( @api_error("Failed to stop the {addon_name} add-on")
"Failed to stop the {addon_name} add-on", expected_error_type=SupervisorError
)
async def async_stop_addon(self) -> None: async def async_stop_addon(self) -> None:
"""Stop the managed add-on.""" """Stop the managed add-on."""
await self._supervisor_client.addons.stop_addon(self.addon_slug) await self._supervisor_client.addons.stop_addon(self.addon_slug)
@ -268,10 +246,8 @@ class AddonManager:
name = f"addon_{self.addon_slug}_{addon_info.version}" name = f"addon_{self.addon_slug}_{addon_info.version}"
self._logger.debug("Creating backup: %s", name) self._logger.debug("Creating backup: %s", name)
await async_create_backup( await self._supervisor_client.backups.partial_backup(
self._hass, PartialBackupOptions(name=name, addons={self.addon_slug})
{"name": name, "addons": [self.addon_slug]},
partial=True,
) )
async def async_configure_addon( async def async_configure_addon(

View file

@ -76,21 +76,6 @@ async def async_update_diagnostics(hass: HomeAssistant, diagnostics: bool) -> bo
return await hassio.update_diagnostics(diagnostics) return await hassio.update_diagnostics(diagnostics)
@bind_hass
@api_data
async def async_create_backup(
hass: HomeAssistant, payload: dict, partial: bool = False
) -> dict:
"""Create a full or partial backup.
The caller of the function should handle HassioAPIError.
"""
hassio: HassIO = hass.data[DOMAIN]
backup_type = "partial" if partial else "full"
command = f"/backups/new/{backup_type}"
return await hassio.send_command(command, payload=payload, timeout=None)
@api_data @api_data
async def async_get_green_settings(hass: HomeAssistant) -> dict[str, bool]: async def async_get_green_settings(hass: HomeAssistant) -> dict[str, bool]:
"""Return settings specific to Home Assistant Green.""" """Return settings specific to Home Assistant Green."""

View file

@ -11,6 +11,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
from aiohasupervisor.models import ( from aiohasupervisor.models import (
Discovery, Discovery,
NewBackup,
Repository, Repository,
ResolutionInfo, ResolutionInfo,
StoreAddon, StoreAddon,
@ -417,13 +418,22 @@ def uninstall_addon_fixture(supervisor_client: AsyncMock) -> AsyncMock:
return supervisor_client.addons.uninstall_addon return supervisor_client.addons.uninstall_addon
@pytest.fixture(name="create_backup") @pytest.fixture(name="create_partial_backup")
def create_backup_fixture() -> Generator[AsyncMock]: def create_partial_backup_fixture(supervisor_client: AsyncMock) -> AsyncMock:
"""Mock create backup.""" """Mock create partial backup."""
# pylint: disable-next=import-outside-toplevel supervisor_client.backups.partial_backup.return_value = NewBackup(
from .hassio.common import mock_create_backup "job123", "backup123"
)
return supervisor_client.backups.partial_backup
yield from mock_create_backup()
@pytest.fixture(name="create_full_backup")
def create_backup_fixture(supervisor_client: AsyncMock) -> AsyncMock:
"""Mock create full backup."""
supervisor_client.backups.full_backup.return_value = NewBackup(
"job123", "backup123"
)
return supervisor_client.backups.full_backup
@pytest.fixture(name="update_addon") @pytest.fixture(name="update_addon")
@ -505,6 +515,7 @@ def supervisor_client() -> Generator[AsyncMock]:
"""Mock the supervisor client.""" """Mock the supervisor client."""
supervisor_client = AsyncMock() supervisor_client = AsyncMock()
supervisor_client.addons = AsyncMock() supervisor_client.addons = AsyncMock()
supervisor_client.backups = AsyncMock()
supervisor_client.discovery = AsyncMock() supervisor_client.discovery = AsyncMock()
supervisor_client.homeassistant = AsyncMock() supervisor_client.homeassistant = AsyncMock()
supervisor_client.os = AsyncMock() supervisor_client.os = AsyncMock()

View file

@ -2,12 +2,11 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Generator
from dataclasses import fields from dataclasses import fields
import logging import logging
from types import MethodType from types import MethodType
from typing import Any from typing import Any
from unittest.mock import AsyncMock, Mock, patch from unittest.mock import AsyncMock, Mock
from aiohasupervisor.models import ( from aiohasupervisor.models import (
AddonsOptions, AddonsOptions,
@ -198,14 +197,6 @@ def mock_set_addon_options_side_effect(addon_options: dict[str, Any]) -> Any | N
return set_addon_options return set_addon_options
def mock_create_backup() -> Generator[AsyncMock]:
"""Mock create backup."""
with patch(
"homeassistant.components.hassio.addon_manager.async_create_backup"
) as create_backup:
yield create_backup
def mock_addon_stats(supervisor_client: AsyncMock) -> AsyncMock: def mock_addon_stats(supervisor_client: AsyncMock) -> AsyncMock:
"""Mock addon stats.""" """Mock addon stats."""
supervisor_client.addons.addon_stats.return_value = addon_stats = Mock( supervisor_client.addons.addon_stats.return_value = addon_stats = Mock(

View file

@ -8,7 +8,7 @@ from unittest.mock import AsyncMock, call
from uuid import uuid4 from uuid import uuid4
from aiohasupervisor import SupervisorError from aiohasupervisor import SupervisorError
from aiohasupervisor.models import AddonsOptions, Discovery from aiohasupervisor.models import AddonsOptions, Discovery, PartialBackupOptions
import pytest import pytest
from homeassistant.components.hassio.addon_manager import ( from homeassistant.components.hassio.addon_manager import (
@ -17,7 +17,6 @@ from homeassistant.components.hassio.addon_manager import (
AddonManager, AddonManager,
AddonState, AddonState,
) )
from homeassistant.components.hassio.handler import HassioAPIError
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -502,7 +501,7 @@ async def test_update_addon(
addon_manager: AddonManager, addon_manager: AddonManager,
addon_info: AsyncMock, addon_info: AsyncMock,
addon_installed: AsyncMock, addon_installed: AsyncMock,
create_backup: AsyncMock, create_partial_backup: AsyncMock,
update_addon: AsyncMock, update_addon: AsyncMock,
) -> None: ) -> None:
"""Test update addon.""" """Test update addon."""
@ -511,9 +510,8 @@ async def test_update_addon(
await addon_manager.async_update_addon() await addon_manager.async_update_addon()
assert addon_info.call_count == 2 assert addon_info.call_count == 2
assert create_backup.call_count == 1 create_partial_backup.assert_called_once_with(
assert create_backup.call_args == call( PartialBackupOptions(name="addon_test_addon_1.0.0", addons={"test_addon"})
hass, {"name": "addon_test_addon_1.0.0", "addons": ["test_addon"]}, partial=True
) )
assert update_addon.call_count == 1 assert update_addon.call_count == 1
@ -522,7 +520,7 @@ async def test_update_addon_no_update(
addon_manager: AddonManager, addon_manager: AddonManager,
addon_info: AsyncMock, addon_info: AsyncMock,
addon_installed: AsyncMock, addon_installed: AsyncMock,
create_backup: AsyncMock, create_partial_backup: AsyncMock,
update_addon: AsyncMock, update_addon: AsyncMock,
) -> None: ) -> None:
"""Test update addon without update available.""" """Test update addon without update available."""
@ -531,7 +529,7 @@ async def test_update_addon_no_update(
await addon_manager.async_update_addon() await addon_manager.async_update_addon()
assert addon_info.call_count == 1 assert addon_info.call_count == 1
assert create_backup.call_count == 0 assert create_partial_backup.call_count == 0
assert update_addon.call_count == 0 assert update_addon.call_count == 0
@ -540,7 +538,7 @@ async def test_update_addon_error(
addon_manager: AddonManager, addon_manager: AddonManager,
addon_info: AsyncMock, addon_info: AsyncMock,
addon_installed: AsyncMock, addon_installed: AsyncMock,
create_backup: AsyncMock, create_partial_backup: AsyncMock,
update_addon: AsyncMock, update_addon: AsyncMock,
) -> None: ) -> None:
"""Test update addon raises error.""" """Test update addon raises error."""
@ -553,9 +551,8 @@ async def test_update_addon_error(
assert str(err.value) == "Failed to update the Test add-on: Boom" assert str(err.value) == "Failed to update the Test add-on: Boom"
assert addon_info.call_count == 2 assert addon_info.call_count == 2
assert create_backup.call_count == 1 create_partial_backup.assert_called_once_with(
assert create_backup.call_args == call( PartialBackupOptions(name="addon_test_addon_1.0.0", addons={"test_addon"})
hass, {"name": "addon_test_addon_1.0.0", "addons": ["test_addon"]}, partial=True
) )
assert update_addon.call_count == 1 assert update_addon.call_count == 1
@ -565,7 +562,7 @@ async def test_schedule_update_addon(
addon_manager: AddonManager, addon_manager: AddonManager,
addon_info: AsyncMock, addon_info: AsyncMock,
addon_installed: AsyncMock, addon_installed: AsyncMock,
create_backup: AsyncMock, create_partial_backup: AsyncMock,
update_addon: AsyncMock, update_addon: AsyncMock,
) -> None: ) -> None:
"""Test schedule update addon.""" """Test schedule update addon."""
@ -591,9 +588,8 @@ async def test_schedule_update_addon(
assert addon_manager.task_in_progress() is False assert addon_manager.task_in_progress() is False
assert addon_info.call_count == 3 assert addon_info.call_count == 3
assert create_backup.call_count == 1 create_partial_backup.assert_called_once_with(
assert create_backup.call_args == call( PartialBackupOptions(name="addon_test_addon_1.0.0", addons={"test_addon"})
hass, {"name": "addon_test_addon_1.0.0", "addons": ["test_addon"]}, partial=True
) )
assert update_addon.call_count == 1 assert update_addon.call_count == 1
@ -607,15 +603,15 @@ async def test_schedule_update_addon(
@pytest.mark.parametrize( @pytest.mark.parametrize(
( (
"create_backup_error", "create_partial_backup_error",
"create_backup_calls", "create_partial_backup_calls",
"update_addon_error", "update_addon_error",
"update_addon_calls", "update_addon_calls",
"error_message", "error_message",
), ),
[ [
( (
HassioAPIError("Boom"), SupervisorError("Boom"),
1, 1,
None, None,
0, 0,
@ -633,17 +629,17 @@ async def test_schedule_update_addon(
async def test_schedule_update_addon_error( async def test_schedule_update_addon_error(
addon_manager: AddonManager, addon_manager: AddonManager,
addon_installed: AsyncMock, addon_installed: AsyncMock,
create_backup: AsyncMock, create_partial_backup: AsyncMock,
update_addon: AsyncMock, update_addon: AsyncMock,
create_backup_error: Exception | None, create_partial_backup_error: Exception | None,
create_backup_calls: int, create_partial_backup_calls: int,
update_addon_error: Exception | None, update_addon_error: Exception | None,
update_addon_calls: int, update_addon_calls: int,
error_message: str, error_message: str,
) -> None: ) -> None:
"""Test schedule update addon raises error.""" """Test schedule update addon raises error."""
addon_installed.return_value.update_available = True addon_installed.return_value.update_available = True
create_backup.side_effect = create_backup_error create_partial_backup.side_effect = create_partial_backup_error
update_addon.side_effect = update_addon_error update_addon.side_effect = update_addon_error
with pytest.raises(AddonError) as err: with pytest.raises(AddonError) as err:
@ -651,21 +647,21 @@ async def test_schedule_update_addon_error(
assert str(err.value) == error_message assert str(err.value) == error_message
assert create_backup.call_count == create_backup_calls assert create_partial_backup.call_count == create_partial_backup_calls
assert update_addon.call_count == update_addon_calls assert update_addon.call_count == update_addon_calls
@pytest.mark.parametrize( @pytest.mark.parametrize(
( (
"create_backup_error", "create_partial_backup_error",
"create_backup_calls", "create_partial_backup_calls",
"update_addon_error", "update_addon_error",
"update_addon_calls", "update_addon_calls",
"error_log", "error_log",
), ),
[ [
( (
HassioAPIError("Boom"), SupervisorError("Boom"),
1, 1,
None, None,
0, 0,
@ -683,10 +679,10 @@ async def test_schedule_update_addon_error(
async def test_schedule_update_addon_logs_error( async def test_schedule_update_addon_logs_error(
addon_manager: AddonManager, addon_manager: AddonManager,
addon_installed: AsyncMock, addon_installed: AsyncMock,
create_backup: AsyncMock, create_partial_backup: AsyncMock,
update_addon: AsyncMock, update_addon: AsyncMock,
create_backup_error: Exception | None, create_partial_backup_error: Exception | None,
create_backup_calls: int, create_partial_backup_calls: int,
update_addon_error: Exception | None, update_addon_error: Exception | None,
update_addon_calls: int, update_addon_calls: int,
error_log: str, error_log: str,
@ -694,13 +690,13 @@ async def test_schedule_update_addon_logs_error(
) -> None: ) -> None:
"""Test schedule update addon logs error.""" """Test schedule update addon logs error."""
addon_installed.return_value.update_available = True addon_installed.return_value.update_available = True
create_backup.side_effect = create_backup_error create_partial_backup.side_effect = create_partial_backup_error
update_addon.side_effect = update_addon_error update_addon.side_effect = update_addon_error
await addon_manager.async_schedule_update_addon(catch_error=True) await addon_manager.async_schedule_update_addon(catch_error=True)
assert error_log in caplog.text assert error_log in caplog.text
assert create_backup.call_count == create_backup_calls assert create_partial_backup.call_count == create_partial_backup_calls
assert update_addon.call_count == update_addon_calls assert update_addon.call_count == update_addon_calls
@ -709,15 +705,14 @@ async def test_create_backup(
addon_manager: AddonManager, addon_manager: AddonManager,
addon_info: AsyncMock, addon_info: AsyncMock,
addon_installed: AsyncMock, addon_installed: AsyncMock,
create_backup: AsyncMock, create_partial_backup: AsyncMock,
) -> None: ) -> None:
"""Test creating a backup of the addon.""" """Test creating a backup of the addon."""
await addon_manager.async_create_backup() await addon_manager.async_create_backup()
assert addon_info.call_count == 1 assert addon_info.call_count == 1
assert create_backup.call_count == 1 create_partial_backup.assert_called_once_with(
assert create_backup.call_args == call( PartialBackupOptions(name="addon_test_addon_1.0.0", addons={"test_addon"})
hass, {"name": "addon_test_addon_1.0.0", "addons": ["test_addon"]}, partial=True
) )
@ -726,10 +721,10 @@ async def test_create_backup_error(
addon_manager: AddonManager, addon_manager: AddonManager,
addon_info: AsyncMock, addon_info: AsyncMock,
addon_installed: AsyncMock, addon_installed: AsyncMock,
create_backup: AsyncMock, create_partial_backup: AsyncMock,
) -> None: ) -> None:
"""Test creating a backup of the addon raises error.""" """Test creating a backup of the addon raises error."""
create_backup.side_effect = HassioAPIError("Boom") create_partial_backup.side_effect = SupervisorError("Boom")
with pytest.raises(AddonError) as err: with pytest.raises(AddonError) as err:
await addon_manager.async_create_backup() await addon_manager.async_create_backup()
@ -737,9 +732,8 @@ async def test_create_backup_error(
assert str(err.value) == "Failed to create a backup of the Test add-on: Boom" assert str(err.value) == "Failed to create a backup of the Test add-on: Boom"
assert addon_info.call_count == 1 assert addon_info.call_count == 1
assert create_backup.call_count == 1 create_partial_backup.assert_called_once_with(
assert create_backup.call_args == call( PartialBackupOptions(name="addon_test_addon_1.0.0", addons={"test_addon"})
hass, {"name": "addon_test_addon_1.0.0", "addons": ["test_addon"]}, partial=True
) )

View file

@ -7,6 +7,7 @@ from collections.abc import Generator
from unittest.mock import AsyncMock, MagicMock, call, patch from unittest.mock import AsyncMock, MagicMock, call, patch
from aiohasupervisor import SupervisorError from aiohasupervisor import SupervisorError
from aiohasupervisor.models import PartialBackupOptions
from matter_server.client.exceptions import ( from matter_server.client.exceptions import (
CannotConnect, CannotConnect,
NotConnected, NotConnected,
@ -16,7 +17,6 @@ from matter_server.client.exceptions import (
from matter_server.common.errors import MatterError from matter_server.common.errors import MatterError
import pytest import pytest
from homeassistant.components.hassio import HassioAPIError
from homeassistant.components.matter.const import DOMAIN from homeassistant.components.matter.const import DOMAIN
from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState
from homeassistant.const import STATE_UNAVAILABLE from homeassistant.const import STATE_UNAVAILABLE
@ -377,7 +377,7 @@ async def test_addon_info_failure(
"update_calls", "update_calls",
"backup_calls", "backup_calls",
"update_addon_side_effect", "update_addon_side_effect",
"create_backup_side_effect", "create_partial_backup_side_effect",
"connect_side_effect", "connect_side_effect",
), ),
[ [
@ -399,7 +399,7 @@ async def test_addon_info_failure(
0, 0,
1, 1,
None, None,
HassioAPIError("Boom"), SupervisorError("Boom"),
ServerVersionTooOld("Invalid version"), ServerVersionTooOld("Invalid version"),
), ),
], ],
@ -411,7 +411,7 @@ async def test_update_addon(
addon_info: AsyncMock, addon_info: AsyncMock,
install_addon: AsyncMock, install_addon: AsyncMock,
start_addon: AsyncMock, start_addon: AsyncMock,
create_backup: AsyncMock, create_partial_backup: AsyncMock,
update_addon: AsyncMock, update_addon: AsyncMock,
matter_client: MagicMock, matter_client: MagicMock,
addon_version: str, addon_version: str,
@ -419,13 +419,13 @@ async def test_update_addon(
update_calls: int, update_calls: int,
backup_calls: int, backup_calls: int,
update_addon_side_effect: Exception | None, update_addon_side_effect: Exception | None,
create_backup_side_effect: Exception | None, create_partial_backup_side_effect: Exception | None,
connect_side_effect: Exception, connect_side_effect: Exception,
) -> None: ) -> None:
"""Test update the Matter add-on during entry setup.""" """Test update the Matter add-on during entry setup."""
addon_info.return_value.version = addon_version addon_info.return_value.version = addon_version
addon_info.return_value.update_available = update_available addon_info.return_value.update_available = update_available
create_backup.side_effect = create_backup_side_effect create_partial_backup.side_effect = create_partial_backup_side_effect
update_addon.side_effect = update_addon_side_effect update_addon.side_effect = update_addon_side_effect
matter_client.connect.side_effect = connect_side_effect matter_client.connect.side_effect = connect_side_effect
entry = MockConfigEntry( entry = MockConfigEntry(
@ -442,7 +442,7 @@ async def test_update_addon(
await hass.async_block_till_done() await hass.async_block_till_done()
assert entry.state is ConfigEntryState.SETUP_RETRY assert entry.state is ConfigEntryState.SETUP_RETRY
assert create_backup.call_count == backup_calls assert create_partial_backup.call_count == backup_calls
assert update_addon.call_count == update_calls assert update_addon.call_count == update_calls
@ -548,7 +548,7 @@ async def test_remove_entry(
hass: HomeAssistant, hass: HomeAssistant,
addon_installed: AsyncMock, addon_installed: AsyncMock,
stop_addon: AsyncMock, stop_addon: AsyncMock,
create_backup: AsyncMock, create_partial_backup: AsyncMock,
uninstall_addon: AsyncMock, uninstall_addon: AsyncMock,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
) -> None: ) -> None:
@ -581,18 +581,17 @@ async def test_remove_entry(
assert stop_addon.call_count == 1 assert stop_addon.call_count == 1
assert stop_addon.call_args == call("core_matter_server") assert stop_addon.call_args == call("core_matter_server")
assert create_backup.call_count == 1 create_partial_backup.assert_called_once_with(
assert create_backup.call_args == call( PartialBackupOptions(
hass, name="addon_core_matter_server_1.0.0", addons={"core_matter_server"}
{"name": "addon_core_matter_server_1.0.0", "addons": ["core_matter_server"]}, )
partial=True,
) )
assert uninstall_addon.call_count == 1 assert uninstall_addon.call_count == 1
assert uninstall_addon.call_args == call("core_matter_server") assert uninstall_addon.call_args == call("core_matter_server")
assert entry.state is ConfigEntryState.NOT_LOADED assert entry.state is ConfigEntryState.NOT_LOADED
assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert len(hass.config_entries.async_entries(DOMAIN)) == 0
stop_addon.reset_mock() stop_addon.reset_mock()
create_backup.reset_mock() create_partial_backup.reset_mock()
uninstall_addon.reset_mock() uninstall_addon.reset_mock()
# test add-on stop failure # test add-on stop failure
@ -604,38 +603,37 @@ async def test_remove_entry(
assert stop_addon.call_count == 1 assert stop_addon.call_count == 1
assert stop_addon.call_args == call("core_matter_server") assert stop_addon.call_args == call("core_matter_server")
assert create_backup.call_count == 0 assert create_partial_backup.call_count == 0
assert uninstall_addon.call_count == 0 assert uninstall_addon.call_count == 0
assert entry.state is ConfigEntryState.NOT_LOADED assert entry.state is ConfigEntryState.NOT_LOADED
assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert len(hass.config_entries.async_entries(DOMAIN)) == 0
assert "Failed to stop the Matter Server add-on" in caplog.text assert "Failed to stop the Matter Server add-on" in caplog.text
stop_addon.side_effect = None stop_addon.side_effect = None
stop_addon.reset_mock() stop_addon.reset_mock()
create_backup.reset_mock() create_partial_backup.reset_mock()
uninstall_addon.reset_mock() uninstall_addon.reset_mock()
# test create backup failure # test create backup failure
entry.add_to_hass(hass) entry.add_to_hass(hass)
assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert len(hass.config_entries.async_entries(DOMAIN)) == 1
create_backup.side_effect = HassioAPIError() create_partial_backup.side_effect = SupervisorError()
await hass.config_entries.async_remove(entry.entry_id) await hass.config_entries.async_remove(entry.entry_id)
assert stop_addon.call_count == 1 assert stop_addon.call_count == 1
assert stop_addon.call_args == call("core_matter_server") assert stop_addon.call_args == call("core_matter_server")
assert create_backup.call_count == 1 create_partial_backup.assert_called_once_with(
assert create_backup.call_args == call( PartialBackupOptions(
hass, name="addon_core_matter_server_1.0.0", addons={"core_matter_server"}
{"name": "addon_core_matter_server_1.0.0", "addons": ["core_matter_server"]}, )
partial=True,
) )
assert uninstall_addon.call_count == 0 assert uninstall_addon.call_count == 0
assert entry.state is ConfigEntryState.NOT_LOADED assert entry.state is ConfigEntryState.NOT_LOADED
assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert len(hass.config_entries.async_entries(DOMAIN)) == 0
assert "Failed to create a backup of the Matter Server add-on" in caplog.text assert "Failed to create a backup of the Matter Server add-on" in caplog.text
create_backup.side_effect = None create_partial_backup.side_effect = None
stop_addon.reset_mock() stop_addon.reset_mock()
create_backup.reset_mock() create_partial_backup.reset_mock()
uninstall_addon.reset_mock() uninstall_addon.reset_mock()
# test add-on uninstall failure # test add-on uninstall failure
@ -647,11 +645,10 @@ async def test_remove_entry(
assert stop_addon.call_count == 1 assert stop_addon.call_count == 1
assert stop_addon.call_args == call("core_matter_server") assert stop_addon.call_args == call("core_matter_server")
assert create_backup.call_count == 1 create_partial_backup.assert_called_once_with(
assert create_backup.call_args == call( PartialBackupOptions(
hass, name="addon_core_matter_server_1.0.0", addons={"core_matter_server"}
{"name": "addon_core_matter_server_1.0.0", "addons": ["core_matter_server"]}, )
partial=True,
) )
assert uninstall_addon.call_count == 1 assert uninstall_addon.call_count == 1
assert uninstall_addon.call_args == call("core_matter_server") assert uninstall_addon.call_args == call("core_matter_server")

View file

@ -6,7 +6,7 @@ import logging
from unittest.mock import AsyncMock, call, patch from unittest.mock import AsyncMock, call, patch
from aiohasupervisor import SupervisorError from aiohasupervisor import SupervisorError
from aiohasupervisor.models import AddonsOptions from aiohasupervisor.models import AddonsOptions, PartialBackupOptions
import pytest import pytest
from zwave_js_server.client import Client from zwave_js_server.client import Client
from zwave_js_server.event import Event from zwave_js_server.event import Event
@ -14,7 +14,6 @@ from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVers
from zwave_js_server.model.node import Node from zwave_js_server.model.node import Node
from zwave_js_server.model.version import VersionInfo from zwave_js_server.model.version import VersionInfo
from homeassistant.components.hassio import HassioAPIError
from homeassistant.components.logger import DOMAIN as LOGGER_DOMAIN, SERVICE_SET_LEVEL from homeassistant.components.logger import DOMAIN as LOGGER_DOMAIN, SERVICE_SET_LEVEL
from homeassistant.components.persistent_notification import async_dismiss from homeassistant.components.persistent_notification import async_dismiss
from homeassistant.components.zwave_js import DOMAIN from homeassistant.components.zwave_js import DOMAIN
@ -743,13 +742,13 @@ async def test_addon_options_changed(
"update_calls", "update_calls",
"backup_calls", "backup_calls",
"update_addon_side_effect", "update_addon_side_effect",
"create_backup_side_effect", "create_partial_backup_side_effect",
), ),
[ [
("1.0.0", True, 1, 1, None, None), ("1.0.0", True, 1, 1, None, None),
("1.0.0", False, 0, 0, None, None), ("1.0.0", False, 0, 0, None, None),
("1.0.0", True, 1, 1, SupervisorError("Boom"), None), ("1.0.0", True, 1, 1, SupervisorError("Boom"), None),
("1.0.0", True, 0, 1, None, HassioAPIError("Boom")), ("1.0.0", True, 0, 1, None, SupervisorError("Boom")),
], ],
) )
async def test_update_addon( async def test_update_addon(
@ -758,7 +757,7 @@ async def test_update_addon(
addon_info, addon_info,
addon_installed, addon_installed,
addon_running, addon_running,
create_backup, create_partial_backup,
update_addon, update_addon,
addon_options, addon_options,
addon_version, addon_version,
@ -766,7 +765,7 @@ async def test_update_addon(
update_calls, update_calls,
backup_calls, backup_calls,
update_addon_side_effect, update_addon_side_effect,
create_backup_side_effect, create_partial_backup_side_effect,
version_state, version_state,
) -> None: ) -> None:
"""Test update the Z-Wave JS add-on during entry setup.""" """Test update the Z-Wave JS add-on during entry setup."""
@ -776,7 +775,7 @@ async def test_update_addon(
addon_options["network_key"] = network_key addon_options["network_key"] = network_key
addon_info.return_value.version = addon_version addon_info.return_value.version = addon_version
addon_info.return_value.update_available = update_available addon_info.return_value.update_available = update_available
create_backup.side_effect = create_backup_side_effect create_partial_backup.side_effect = create_partial_backup_side_effect
update_addon.side_effect = update_addon_side_effect update_addon.side_effect = update_addon_side_effect
client.connect.side_effect = InvalidServerVersion( client.connect.side_effect = InvalidServerVersion(
VersionInfo("a", "b", 1, 1, 1), 1, "Invalid version" VersionInfo("a", "b", 1, 1, 1), 1, "Invalid version"
@ -797,7 +796,7 @@ async def test_update_addon(
await hass.async_block_till_done() await hass.async_block_till_done()
assert entry.state is ConfigEntryState.SETUP_RETRY assert entry.state is ConfigEntryState.SETUP_RETRY
assert create_backup.call_count == backup_calls assert create_partial_backup.call_count == backup_calls
assert update_addon.call_count == update_calls assert update_addon.call_count == update_calls
@ -897,7 +896,7 @@ async def test_remove_entry(
hass: HomeAssistant, hass: HomeAssistant,
addon_installed, addon_installed,
stop_addon, stop_addon,
create_backup, create_partial_backup,
uninstall_addon, uninstall_addon,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
) -> None: ) -> None:
@ -930,18 +929,15 @@ async def test_remove_entry(
assert stop_addon.call_count == 1 assert stop_addon.call_count == 1
assert stop_addon.call_args == call("core_zwave_js") assert stop_addon.call_args == call("core_zwave_js")
assert create_backup.call_count == 1 create_partial_backup.assert_called_once_with(
assert create_backup.call_args == call( PartialBackupOptions(name="addon_core_zwave_js_1.0.0", addons={"core_zwave_js"})
hass,
{"name": "addon_core_zwave_js_1.0.0", "addons": ["core_zwave_js"]},
partial=True,
) )
assert uninstall_addon.call_count == 1 assert uninstall_addon.call_count == 1
assert uninstall_addon.call_args == call("core_zwave_js") assert uninstall_addon.call_args == call("core_zwave_js")
assert entry.state is ConfigEntryState.NOT_LOADED assert entry.state is ConfigEntryState.NOT_LOADED
assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert len(hass.config_entries.async_entries(DOMAIN)) == 0
stop_addon.reset_mock() stop_addon.reset_mock()
create_backup.reset_mock() create_partial_backup.reset_mock()
uninstall_addon.reset_mock() uninstall_addon.reset_mock()
# test add-on stop failure # test add-on stop failure
@ -953,38 +949,35 @@ async def test_remove_entry(
assert stop_addon.call_count == 1 assert stop_addon.call_count == 1
assert stop_addon.call_args == call("core_zwave_js") assert stop_addon.call_args == call("core_zwave_js")
assert create_backup.call_count == 0 assert create_partial_backup.call_count == 0
assert uninstall_addon.call_count == 0 assert uninstall_addon.call_count == 0
assert entry.state is ConfigEntryState.NOT_LOADED assert entry.state is ConfigEntryState.NOT_LOADED
assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert len(hass.config_entries.async_entries(DOMAIN)) == 0
assert "Failed to stop the Z-Wave JS add-on" in caplog.text assert "Failed to stop the Z-Wave JS add-on" in caplog.text
stop_addon.side_effect = None stop_addon.side_effect = None
stop_addon.reset_mock() stop_addon.reset_mock()
create_backup.reset_mock() create_partial_backup.reset_mock()
uninstall_addon.reset_mock() uninstall_addon.reset_mock()
# test create backup failure # test create backup failure
entry.add_to_hass(hass) entry.add_to_hass(hass)
assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert len(hass.config_entries.async_entries(DOMAIN)) == 1
create_backup.side_effect = HassioAPIError() create_partial_backup.side_effect = SupervisorError()
await hass.config_entries.async_remove(entry.entry_id) await hass.config_entries.async_remove(entry.entry_id)
assert stop_addon.call_count == 1 assert stop_addon.call_count == 1
assert stop_addon.call_args == call("core_zwave_js") assert stop_addon.call_args == call("core_zwave_js")
assert create_backup.call_count == 1 create_partial_backup.assert_called_once_with(
assert create_backup.call_args == call( PartialBackupOptions(name="addon_core_zwave_js_1.0.0", addons={"core_zwave_js"})
hass,
{"name": "addon_core_zwave_js_1.0.0", "addons": ["core_zwave_js"]},
partial=True,
) )
assert uninstall_addon.call_count == 0 assert uninstall_addon.call_count == 0
assert entry.state is ConfigEntryState.NOT_LOADED assert entry.state is ConfigEntryState.NOT_LOADED
assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert len(hass.config_entries.async_entries(DOMAIN)) == 0
assert "Failed to create a backup of the Z-Wave JS add-on" in caplog.text assert "Failed to create a backup of the Z-Wave JS add-on" in caplog.text
create_backup.side_effect = None create_partial_backup.side_effect = None
stop_addon.reset_mock() stop_addon.reset_mock()
create_backup.reset_mock() create_partial_backup.reset_mock()
uninstall_addon.reset_mock() uninstall_addon.reset_mock()
# test add-on uninstall failure # test add-on uninstall failure
@ -996,11 +989,8 @@ async def test_remove_entry(
assert stop_addon.call_count == 1 assert stop_addon.call_count == 1
assert stop_addon.call_args == call("core_zwave_js") assert stop_addon.call_args == call("core_zwave_js")
assert create_backup.call_count == 1 create_partial_backup.assert_called_once_with(
assert create_backup.call_args == call( PartialBackupOptions(name="addon_core_zwave_js_1.0.0", addons={"core_zwave_js"})
hass,
{"name": "addon_core_zwave_js_1.0.0", "addons": ["core_zwave_js"]},
partial=True,
) )
assert uninstall_addon.call_count == 1 assert uninstall_addon.call_count == 1
assert uninstall_addon.call_args == call("core_zwave_js") assert uninstall_addon.call_args == call("core_zwave_js")