Remaining addon management to aiohasupervisor (#128484)

* Move set addon options to aiohasupervisor

* addon stats to aiohasupervisor and test fixes

* addon changelogs to aiohasupervisor

* Raise correct error for library in tests

* Cache client in instance property

* Use singleton method rather then HassIO instance method

* Mock supervisor client in more tests
This commit is contained in:
Mike Degatano 2024-10-21 10:41:00 -04:00 committed by GitHub
parent 9b3ac49298
commit ad55c9cc19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 384 additions and 495 deletions

View File

@ -105,10 +105,8 @@ from .handler import ( # noqa: F401
async_get_green_settings,
async_get_yellow_settings,
async_reboot_host,
async_set_addon_options,
async_set_green_settings,
async_set_yellow_settings,
async_update_addon,
async_update_core,
async_update_diagnostics,
async_update_os,
@ -432,6 +430,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
async def update_info_data(_: datetime | None = None) -> None:
"""Update last available supervisor information."""
supervisor_client = get_supervisor_client(hass)
try:
(
@ -445,7 +444,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
) = await asyncio.gather(
create_eager_task(hassio.get_info()),
create_eager_task(hassio.get_host_info()),
create_eager_task(hassio.client.store.info()),
create_eager_task(supervisor_client.store.info()),
create_eager_task(hassio.get_core_info()),
create_eager_task(hassio.get_supervisor_info()),
create_eager_task(hassio.get_os_info()),

View File

@ -10,10 +10,12 @@ from functools import partial, wraps
import logging
from typing import Any, Concatenate
from aiohasupervisor import SupervisorClient, SupervisorError
from aiohasupervisor import SupervisorError
from aiohasupervisor.models import (
AddonsOptions,
AddonState as SupervisorAddonState,
InstalledAddonComplete,
StoreAddonUpdate,
)
from homeassistant.core import HomeAssistant, callback
@ -23,8 +25,6 @@ from .handler import (
HassioAPIError,
async_create_backup,
async_get_addon_discovery_info,
async_set_addon_options,
async_update_addon,
get_supervisor_client,
)
@ -36,10 +36,13 @@ type _ReturnFuncType[_T, **_P, _R] = Callable[
def api_error[_AddonManagerT: AddonManager, **_P, _R](
error_message: str,
*,
expected_error_type: type[HassioAPIError | SupervisorError] | None = None,
) -> Callable[
[_FuncType[_AddonManagerT, _P, _R]], _ReturnFuncType[_AddonManagerT, _P, _R]
]:
"""Handle HassioAPIError and raise a specific AddonError."""
error_type = expected_error_type or (HassioAPIError, SupervisorError)
def handle_hassio_api_error(
func: _FuncType[_AddonManagerT, _P, _R],
@ -53,7 +56,7 @@ def api_error[_AddonManagerT: AddonManager, **_P, _R](
"""Wrap an add-on manager method."""
try:
return_value = await func(self, *args, **kwargs)
except (HassioAPIError, SupervisorError) as err:
except error_type as err:
raise AddonError(
f"{error_message.format(addon_name=self.addon_name)}: {err}"
) from err
@ -111,14 +114,7 @@ class AddonManager:
self._restart_task: asyncio.Task | None = None
self._start_task: asyncio.Task | None = None
self._update_task: asyncio.Task | None = None
self._client: SupervisorClient | None = None
@property
def _supervisor_client(self) -> SupervisorClient:
"""Get supervisor client."""
if not self._client:
self._client = get_supervisor_client(self._hass)
return self._client
self._supervisor_client = get_supervisor_client(hass)
def task_in_progress(self) -> bool:
"""Return True if any of the add-on tasks are in progress."""
@ -145,7 +141,10 @@ class AddonManager:
discovery_info_config: dict = discovery_info["config"]
return discovery_info_config
@api_error("Failed to get the {addon_name} add-on info")
@api_error(
"Failed to get the {addon_name} add-on info",
expected_error_type=SupervisorError,
)
async def async_get_addon_info(self) -> AddonInfo:
"""Return and cache manager add-on info."""
addon_store_info = await self._supervisor_client.store.addon_info(
@ -187,19 +186,24 @@ class AddonManager:
return addon_state
@api_error("Failed to set the {addon_name} add-on options")
@api_error(
"Failed to set the {addon_name} add-on options",
expected_error_type=SupervisorError,
)
async def async_set_addon_options(self, config: dict) -> None:
"""Set manager add-on options."""
options = {"options": config}
await async_set_addon_options(self._hass, self.addon_slug, options)
await self._supervisor_client.addons.addon_options(
self.addon_slug, AddonsOptions(config=config)
)
def _check_addon_available(self, addon_info: AddonInfo) -> None:
"""Check if the managed add-on is available."""
if not addon_info.available:
raise AddonError(f"{self.addon_name} add-on is not available")
@api_error("Failed to install the {addon_name} add-on")
@api_error(
"Failed to install the {addon_name} add-on", expected_error_type=SupervisorError
)
async def async_install_addon(self) -> None:
"""Install the managed add-on."""
addon_info = await self.async_get_addon_info()
@ -208,7 +212,10 @@ class AddonManager:
await self._supervisor_client.store.install_addon(self.addon_slug)
@api_error("Failed to uninstall the {addon_name} add-on")
@api_error(
"Failed to uninstall the {addon_name} add-on",
expected_error_type=SupervisorError,
)
async def async_uninstall_addon(self) -> None:
"""Uninstall the managed add-on."""
await self._supervisor_client.addons.uninstall_addon(self.addon_slug)
@ -227,19 +234,27 @@ class AddonManager:
return
await self.async_create_backup()
await async_update_addon(self._hass, self.addon_slug)
await self._supervisor_client.store.update_addon(
self.addon_slug, StoreAddonUpdate(backup=False)
)
@api_error("Failed to start the {addon_name} add-on")
@api_error(
"Failed to start the {addon_name} add-on", expected_error_type=SupervisorError
)
async def async_start_addon(self) -> None:
"""Start the managed add-on."""
await self._supervisor_client.addons.start_addon(self.addon_slug)
@api_error("Failed to restart the {addon_name} add-on")
@api_error(
"Failed to restart the {addon_name} add-on", expected_error_type=SupervisorError
)
async def async_restart_addon(self) -> None:
"""Restart the managed add-on."""
await self._supervisor_client.addons.restart_addon(self.addon_slug)
@api_error("Failed to stop the {addon_name} add-on")
@api_error(
"Failed to stop the {addon_name} add-on", expected_error_type=SupervisorError
)
async def async_stop_addon(self) -> None:
"""Stop the managed add-on."""
await self._supervisor_client.addons.stop_addon(self.addon_slug)

View File

@ -56,7 +56,7 @@ from .const import (
SUPERVISOR_CONTAINER,
SupervisorEntityModel,
)
from .handler import HassIO, HassioAPIError
from .handler import HassIO, HassioAPIError, get_supervisor_client
if TYPE_CHECKING:
from .issues import SupervisorIssues
@ -318,6 +318,7 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator):
self._container_updates: defaultdict[str, dict[str, set[str]]] = defaultdict(
lambda: defaultdict(set)
)
self._supervisor_client = get_supervisor_client(hass)
async def _async_update_data(self) -> dict[str, Any]:
"""Update data via library."""
@ -502,17 +503,17 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator):
async def _update_addon_stats(self, slug: str) -> tuple[str, dict[str, Any] | None]:
"""Update single addon stats."""
try:
stats = await self.hassio.get_addon_stats(slug)
except HassioAPIError as err:
stats = await self._supervisor_client.addons.addon_stats(slug)
except SupervisorError as err:
_LOGGER.warning("Could not fetch stats for %s: %s", slug, err)
return (slug, None)
return (slug, stats)
return (slug, stats.to_dict())
async def _update_addon_changelog(self, slug: str) -> tuple[str, str | None]:
"""Return the changelog for an add-on."""
try:
changelog = await self.hassio.get_addon_changelog(slug)
except HassioAPIError as err:
changelog = await self._supervisor_client.store.addon_changelog(slug)
except SupervisorError as err:
_LOGGER.warning("Could not fetch changelog for %s: %s", slug, err)
return (slug, None)
return (slug, changelog)
@ -520,7 +521,7 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator):
async def _update_addon_info(self, slug: str) -> tuple[str, dict[str, Any] | None]:
"""Return the info for an add-on."""
try:
info = await self.hassio.client.addons.addon_info(slug)
info = await self._supervisor_client.addons.addon_info(slug)
except SupervisorError as err:
_LOGGER.warning("Could not fetch info for %s: %s", slug, err)
return (slug, None)

View File

@ -7,6 +7,7 @@ from dataclasses import dataclass
import logging
from typing import Any
from aiohasupervisor import SupervisorError
from aiohttp import web
from aiohttp.web_exceptions import HTTPServiceUnavailable
@ -19,7 +20,7 @@ from homeassistant.helpers import discovery_flow
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import ATTR_ADDON, ATTR_CONFIG, ATTR_DISCOVERY, ATTR_UUID, DOMAIN
from .handler import HassIO, HassioAPIError
from .handler import HassIO, HassioAPIError, get_supervisor_client
_LOGGER = logging.getLogger(__name__)
@ -88,6 +89,7 @@ class HassIODiscovery(HomeAssistantView):
"""Initialize WebView."""
self.hass = hass
self.hassio = hassio
self._supervisor_client = get_supervisor_client(hass)
async def post(self, request: web.Request, uuid: str) -> web.Response:
"""Handle new discovery requests."""
@ -126,8 +128,8 @@ class HassIODiscovery(HomeAssistantView):
# Read additional Add-on info
try:
addon_info = await self.hassio.client.addons.addon_info(slug)
except HassioAPIError as err:
addon_info = await self._supervisor_client.addons.addon_info(slug)
except SupervisorError as err:
_LOGGER.error("Can't read add-on info: %s", err)
return

View File

@ -21,12 +21,15 @@ from homeassistant.components.http import (
)
from homeassistant.const import SERVER_PORT
from homeassistant.core import HomeAssistant
from homeassistant.helpers.singleton import singleton
from homeassistant.loader import bind_hass
from .const import ATTR_DISCOVERY, ATTR_MESSAGE, ATTR_RESULT, DOMAIN, X_HASS_SOURCE
_LOGGER = logging.getLogger(__name__)
KEY_SUPERVISOR_CLIENT = "supervisor_client"
class HassioAPIError(RuntimeError):
"""Return if a API trow a error."""
@ -73,40 +76,6 @@ async def async_update_diagnostics(hass: HomeAssistant, diagnostics: bool) -> bo
return await hassio.update_diagnostics(diagnostics)
@bind_hass
@api_data
async def async_update_addon(
hass: HomeAssistant,
slug: str,
backup: bool = False,
) -> dict:
"""Update add-on.
The caller of the function should handle HassioAPIError.
"""
hassio: HassIO = hass.data[DOMAIN]
command = f"/addons/{slug}/update"
return await hassio.send_command(
command,
payload={"backup": backup},
timeout=None,
)
@bind_hass
@api_data
async def async_set_addon_options(
hass: HomeAssistant, slug: str, options: dict
) -> dict:
"""Set add-on options.
The caller of the function should handle HassioAPIError.
"""
hassio: HassIO = hass.data[DOMAIN]
command = f"/addons/{slug}/options"
return await hassio.send_command(command, payload=options)
@bind_hass
async def async_get_addon_discovery_info(hass: HomeAssistant, slug: str) -> dict | None:
"""Return discovery data for an add-on."""
@ -253,14 +222,11 @@ class HassIO:
self._ip = ip
base_url = f"http://{ip}"
self._base_url = URL(base_url)
self._client = SupervisorClient(
base_url, os.environ.get("SUPERVISOR_TOKEN", ""), session=websession
)
@property
def client(self) -> SupervisorClient:
"""Return aiohasupervisor client."""
return self._client
def base_url(self) -> URL:
"""Return base url for Supervisor."""
return self._base_url
@_api_bool
def is_connected(self) -> Coroutine:
@ -326,14 +292,6 @@ class HassIO:
"""
return self.send_command("/core/stats", method="get")
@api_data
def get_addon_stats(self, addon: str) -> Coroutine:
"""Return stats for an Add-on.
This method returns a coroutine.
"""
return self.send_command(f"/addons/{addon}/stats", method="get")
@api_data
def get_supervisor_stats(self) -> Coroutine:
"""Return stats for the supervisor.
@ -342,15 +300,6 @@ class HassIO:
"""
return self.send_command("/supervisor/stats", method="get")
def get_addon_changelog(self, addon: str) -> Coroutine:
"""Return changelog for an Add-on.
This method returns a coroutine.
"""
return self.send_command(
f"/addons/{addon}/changelog", method="get", return_text=True
)
@api_data
def get_ingress_panels(self) -> Coroutine:
"""Return data for Add-on ingress panels.
@ -531,7 +480,12 @@ class HassIO:
raise HassioAPIError
@singleton(KEY_SUPERVISOR_CLIENT)
def get_supervisor_client(hass: HomeAssistant) -> SupervisorClient:
"""Return supervisor client."""
hassio: HassIO = hass.data[DOMAIN]
return hassio.client
return SupervisorClient(
hassio.base_url,
os.environ.get("SUPERVISOR_TOKEN", ""),
session=hassio.websession,
)

View File

@ -4,6 +4,8 @@ from __future__ import annotations
from typing import Any
from aiohasupervisor import SupervisorError
from aiohasupervisor.models import StoreAddonUpdate
from awesomeversion import AwesomeVersion, AwesomeVersionStrategy
from homeassistant.components.update import (
@ -15,6 +17,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ICON, ATTR_NAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import (
@ -28,6 +31,7 @@ from .const import (
DATA_KEY_OS,
DATA_KEY_SUPERVISOR,
)
from .coordinator import HassioDataUpdateCoordinator
from .entity import (
HassioAddonEntity,
HassioCoreEntity,
@ -36,10 +40,10 @@ from .entity import (
)
from .handler import (
HassioAPIError,
async_update_addon,
async_update_core,
async_update_os,
async_update_supervisor,
get_supervisor_client,
)
ENTITY_DESCRIPTION = UpdateEntityDescription(
@ -96,6 +100,16 @@ class SupervisorAddonUpdateEntity(HassioAddonEntity, UpdateEntity):
| UpdateEntityFeature.RELEASE_NOTES
)
def __init__(
self,
coordinator: HassioDataUpdateCoordinator,
entity_description: EntityDescription,
addon: dict[str, Any],
) -> None:
"""Initialize object."""
super().__init__(coordinator, entity_description, addon)
self._supervisor_client = get_supervisor_client(self.hass)
@property
def _addon_data(self) -> dict:
"""Return the add-on data."""
@ -165,8 +179,10 @@ class SupervisorAddonUpdateEntity(HassioAddonEntity, UpdateEntity):
) -> None:
"""Install an update."""
try:
await async_update_addon(self.hass, slug=self._addon_slug, backup=backup)
except HassioAPIError as err:
await self._supervisor_client.store.update_addon(
self._addon_slug, StoreAddonUpdate(backup=backup)
)
except SupervisorError as err:
raise HomeAssistantError(f"Error updating {self.title}: {err}") from err
await self.coordinator.force_info_update_supervisor()

View File

@ -6,7 +6,7 @@ from collections.abc import Callable, Generator
from importlib.util import find_spec
from pathlib import Path
from typing import TYPE_CHECKING, Any
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
from unittest.mock import AsyncMock, MagicMock, patch
from aiohasupervisor.models import Repository, StoreAddon, StoreInfo
import pytest
@ -194,7 +194,9 @@ def mock_legacy_device_tracker_setup() -> Callable[[HomeAssistant, MockScanner],
@pytest.fixture(name="addon_manager")
def addon_manager_fixture(hass: HomeAssistant) -> AddonManager:
def addon_manager_fixture(
hass: HomeAssistant, supervisor_client: AsyncMock
) -> AddonManager:
"""Return an AddonManager instance."""
# pylint: disable-next=import-outside-toplevel
from .hassio.common import mock_addon_manager
@ -363,10 +365,7 @@ def stop_addon_fixture(supervisor_client: AsyncMock) -> AsyncMock:
@pytest.fixture(name="addon_options")
def addon_options_fixture(addon_info: AsyncMock) -> dict[str, Any]:
"""Mock add-on options."""
# pylint: disable-next=import-outside-toplevel
from .hassio.common import mock_addon_options
return mock_addon_options(addon_info)
return addon_info.return_value.options
@pytest.fixture(name="set_addon_options_side_effect")
@ -382,13 +381,12 @@ def set_addon_options_side_effect_fixture(
@pytest.fixture(name="set_addon_options")
def set_addon_options_fixture(
supervisor_client: AsyncMock,
set_addon_options_side_effect: Any | None,
) -> Generator[AsyncMock]:
) -> AsyncMock:
"""Mock set add-on options."""
# pylint: disable-next=import-outside-toplevel
from .hassio.common import mock_set_addon_options
yield from mock_set_addon_options(set_addon_options_side_effect)
supervisor_client.addons.addon_options.side_effect = set_addon_options_side_effect
return supervisor_client.addons.addon_options
@pytest.fixture(name="uninstall_addon")
@ -407,12 +405,9 @@ def create_backup_fixture() -> Generator[AsyncMock]:
@pytest.fixture(name="update_addon")
def update_addon_fixture() -> Generator[AsyncMock]:
def update_addon_fixture(supervisor_client: AsyncMock) -> AsyncMock:
"""Mock update add-on."""
# pylint: disable-next=import-outside-toplevel
from .hassio.common import mock_update_addon
yield from mock_update_addon()
return supervisor_client.store.update_addon
@pytest.fixture(name="store_addons")
@ -440,6 +435,22 @@ def store_info_fixture(
return supervisor_client.store.info
@pytest.fixture(name="addon_stats")
def addon_stats_fixture(supervisor_client: AsyncMock) -> AsyncMock:
"""Mock addon stats info."""
# pylint: disable-next=import-outside-toplevel
from .hassio.common import mock_addon_stats
return mock_addon_stats(supervisor_client)
@pytest.fixture(name="addon_changelog")
def addon_changelog_fixture(supervisor_client: AsyncMock) -> AsyncMock:
"""Mock addon changelog."""
supervisor_client.store.addon_changelog.return_value = ""
return supervisor_client.store.addon_changelog
@pytest.fixture(name="supervisor_client")
def supervisor_client() -> Generator[AsyncMock]:
"""Mock the supervisor client."""
@ -459,8 +470,20 @@ def supervisor_client() -> Generator[AsyncMock]:
return_value=supervisor_client,
),
patch(
"homeassistant.components.hassio.handler.HassIO.client",
new=PropertyMock(return_value=supervisor_client),
"homeassistant.components.hassio.discovery.get_supervisor_client",
return_value=supervisor_client,
),
patch(
"homeassistant.components.hassio.coordinator.get_supervisor_client",
return_value=supervisor_client,
),
patch(
"homeassistant.components.hassio.update.get_supervisor_client",
return_value=supervisor_client,
),
patch(
"homeassistant.components.hassio.get_supervisor_client",
return_value=supervisor_client,
),
):
yield supervisor_client

View File

@ -10,6 +10,8 @@ from typing import Any
from unittest.mock import DEFAULT, AsyncMock, Mock, patch
from aiohasupervisor.models import (
AddonsOptions,
AddonsStats,
AddonStage,
InstalledAddonComplete,
Repository,
@ -23,6 +25,7 @@ from homeassistant.core import HomeAssistant
LOGGER = logging.getLogger(__name__)
INSTALLED_ADDON_FIELDS = [field.name for field in fields(InstalledAddonComplete)]
STORE_ADDON_FIELDS = [field.name for field in fields(StoreAddonComplete)]
ADDONS_STATS_FIELDS = [field.name for field in fields(AddonsStats)]
MOCK_STORE_ADDONS = [
StoreAddon(
@ -202,32 +205,16 @@ def mock_start_addon_side_effect(
return start_addon
def mock_addon_options(addon_info: AsyncMock) -> dict[str, Any]:
"""Mock add-on options."""
return addon_info.return_value.options
def mock_set_addon_options_side_effect(addon_options: dict[str, Any]) -> Any | None:
"""Return the set add-on options side effect."""
async def set_addon_options(hass: HomeAssistant, slug: str, options: dict) -> None:
async def set_addon_options(slug: str, options: AddonsOptions) -> None:
"""Mock set add-on options."""
addon_options.update(options["options"])
addon_options.update(options.config)
return set_addon_options
def mock_set_addon_options(
set_addon_options_side_effect: Any | None,
) -> Generator[AsyncMock]:
"""Mock set add-on options."""
with patch(
"homeassistant.components.hassio.addon_manager.async_set_addon_options",
side_effect=set_addon_options_side_effect,
) as set_options:
yield set_options
def mock_create_backup() -> Generator[AsyncMock]:
"""Mock create backup."""
with patch(
@ -236,9 +223,21 @@ def mock_create_backup() -> Generator[AsyncMock]:
yield create_backup
def mock_update_addon() -> Generator[AsyncMock]:
"""Mock update add-on."""
with patch(
"homeassistant.components.hassio.addon_manager.async_update_addon"
) as update_addon:
yield update_addon
def mock_addon_stats(supervisor_client: AsyncMock) -> AsyncMock:
"""Mock addon stats."""
supervisor_client.addons.addon_stats.return_value = addon_stats = Mock(
spec=AddonsStats,
cpu_percent=0.99,
memory_usage=182611968,
memory_limit=3977146368,
memory_percent=4.59,
network_rx=362570232,
network_tx=82374138,
blk_read=46010945536,
blk_write=15051526144,
)
addon_stats.to_dict = MethodType(
lambda self: mock_to_dict(self, ADDONS_STATS_FIELDS),
addon_stats,
)
return supervisor_client.addons.addon_stats

View File

@ -5,7 +5,7 @@ import os
import re
from unittest.mock import AsyncMock, Mock, patch
from aiohasupervisor.models import AddonState
from aiohasupervisor.models import AddonsStats, AddonState
from aiohttp.test_utils import TestClient
import pytest
@ -55,6 +55,7 @@ def hassio_stubs(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
aioclient_mock: AiohttpClientMocker,
supervisor_client: AsyncMock,
) -> RefreshToken:
"""Create mock hassio http client."""
with (
@ -133,7 +134,9 @@ def all_setup_requests(
aioclient_mock: AiohttpClientMocker,
request: pytest.FixtureRequest,
addon_installed: AsyncMock,
store_info,
store_info: AsyncMock,
addon_changelog: AsyncMock,
addon_stats: AsyncMock,
) -> None:
"""Mock all setup requests."""
include_addons = hasattr(request, "param") and request.param.get(
@ -249,8 +252,6 @@ def all_setup_requests(
addon_installed.side_effect = mock_addon_info
aioclient_mock.get("http://127.0.0.1/addons/test/changelog", text="")
aioclient_mock.get("http://127.0.0.1/addons/test2/changelog", text="")
aioclient_mock.get(
"http://127.0.0.1/core/stats",
json={
@ -283,38 +284,32 @@ def all_setup_requests(
},
},
)
aioclient_mock.get(
"http://127.0.0.1/addons/test/stats",
json={
"result": "ok",
"data": {
"cpu_percent": 0.99,
"memory_usage": 182611968,
"memory_limit": 3977146368,
"memory_percent": 4.59,
"network_rx": 362570232,
"network_tx": 82374138,
"blk_read": 46010945536,
"blk_write": 15051526144,
},
},
)
aioclient_mock.get(
"http://127.0.0.1/addons/test2/stats",
json={
"result": "ok",
"data": {
"cpu_percent": 0.8,
"memory_usage": 51941376,
"memory_limit": 3977146368,
"memory_percent": 1.31,
"network_rx": 31338284,
"network_tx": 15692900,
"blk_read": 740077568,
"blk_write": 6004736,
},
},
)
async def mock_addon_stats(addon: str) -> AddonsStats:
"""Mock addon stats for test and test2."""
if addon == "test2":
return AddonsStats(
cpu_percent=0.8,
memory_usage=51941376,
memory_limit=3977146368,
memory_percent=1.31,
network_rx=31338284,
network_tx=15692900,
blk_read=740077568,
blk_write=6004736,
)
return AddonsStats(
cpu_percent=0.99,
memory_usage=182611968,
memory_limit=3977146368,
memory_percent=4.59,
network_rx=362570232,
network_tx=82374138,
blk_read=46010945536,
blk_write=15051526144,
)
addon_stats.side_effect = mock_addon_stats
aioclient_mock.get(
"http://127.0.0.1/network/info",
json={

View File

@ -7,6 +7,7 @@ from typing import Any
from unittest.mock import AsyncMock, call
from aiohasupervisor import SupervisorError
from aiohasupervisor.models import AddonsOptions
import pytest
from homeassistant.components.hassio.addon_manager import (
@ -137,7 +138,7 @@ async def test_get_addon_info(
"addon_store_info_error",
"addon_store_info_calls",
),
[(SupervisorError("Boom"), 1, None, 1), (None, 0, HassioAPIError("Boom"), 1)],
[(SupervisorError("Boom"), 1, None, 1), (None, 0, SupervisorError("Boom"), 1)],
)
async def test_get_addon_info_error(
addon_manager: AddonManager,
@ -170,7 +171,7 @@ async def test_set_addon_options(
assert set_addon_options.call_count == 1
assert set_addon_options.call_args == call(
hass, "test_addon", {"options": {"test_key": "test"}}
"test_addon", AddonsOptions(config={"test_key": "test"})
)
@ -178,7 +179,7 @@ async def test_set_addon_options_error(
hass: HomeAssistant, addon_manager: AddonManager, set_addon_options: AsyncMock
) -> None:
"""Test set addon options raises error."""
set_addon_options.side_effect = HassioAPIError("Boom")
set_addon_options.side_effect = SupervisorError("Boom")
with pytest.raises(AddonError) as err:
await addon_manager.async_set_addon_options({"test_key": "test"})
@ -187,7 +188,7 @@ async def test_set_addon_options_error(
assert set_addon_options.call_count == 1
assert set_addon_options.call_args == call(
hass, "test_addon", {"options": {"test_key": "test"}}
"test_addon", AddonsOptions(config={"test_key": "test"})
)
@ -215,7 +216,7 @@ async def test_install_addon_error(
"""Test install addon raises error."""
addon_store_info.return_value.available = True
addon_info.return_value.available = True
install_addon.side_effect = HassioAPIError("Boom")
install_addon.side_effect = SupervisorError("Boom")
with pytest.raises(AddonError) as err:
await addon_manager.async_install_addon()
@ -266,7 +267,7 @@ async def test_schedule_install_addon_error(
install_addon: AsyncMock,
) -> None:
"""Test schedule install addon raises error."""
install_addon.side_effect = HassioAPIError("Boom")
install_addon.side_effect = SupervisorError("Boom")
with pytest.raises(AddonError) as err:
await addon_manager.async_schedule_install_addon()
@ -283,7 +284,7 @@ async def test_schedule_install_addon_logs_error(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test schedule install addon logs error."""
install_addon.side_effect = HassioAPIError("Boom")
install_addon.side_effect = SupervisorError("Boom")
await addon_manager.async_schedule_install_addon(catch_error=True)
@ -541,7 +542,7 @@ async def test_update_addon_error(
) -> None:
"""Test update addon raises error."""
addon_info.return_value.update_available = True
update_addon.side_effect = HassioAPIError("Boom")
update_addon.side_effect = SupervisorError("Boom")
with pytest.raises(AddonError) as err:
await addon_manager.async_update_addon()
@ -620,7 +621,7 @@ async def test_schedule_update_addon(
(
None,
1,
HassioAPIError("Boom"),
SupervisorError("Boom"),
1,
"Failed to update the Test add-on: Boom",
),
@ -670,7 +671,7 @@ async def test_schedule_update_addon_error(
(
None,
1,
HassioAPIError("Boom"),
SupervisorError("Boom"),
1,
"Failed to update the Test add-on: Boom",
),
@ -790,7 +791,7 @@ async def test_schedule_install_setup_addon(
),
[
(
HassioAPIError("Boom"),
SupervisorError("Boom"),
1,
None,
0,
@ -801,7 +802,7 @@ async def test_schedule_install_setup_addon(
(
None,
1,
HassioAPIError("Boom"),
SupervisorError("Boom"),
1,
None,
0,
@ -859,7 +860,7 @@ async def test_schedule_install_setup_addon_error(
),
[
(
HassioAPIError("Boom"),
SupervisorError("Boom"),
1,
None,
0,
@ -870,7 +871,7 @@ async def test_schedule_install_setup_addon_error(
(
None,
1,
HassioAPIError("Boom"),
SupervisorError("Boom"),
1,
None,
0,
@ -956,7 +957,7 @@ async def test_schedule_setup_addon(
),
[
(
HassioAPIError("Boom"),
SupervisorError("Boom"),
1,
None,
0,
@ -1005,7 +1006,7 @@ async def test_schedule_setup_addon_error(
),
[
(
HassioAPIError("Boom"),
SupervisorError("Boom"),
1,
None,
0,

View File

@ -19,7 +19,13 @@ MOCK_ENVIRON = {"SUPERVISOR": "127.0.0.1", "SUPERVISOR_TOKEN": "abcdefgh"}
@pytest.fixture(autouse=True)
def mock_all(aioclient_mock: AiohttpClientMocker, addon_installed, store_info) -> None:
def mock_all(
aioclient_mock: AiohttpClientMocker,
addon_installed: AsyncMock,
store_info: AsyncMock,
addon_changelog: AsyncMock,
addon_stats: AsyncMock,
) -> None:
"""Mock all setup requests."""
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"})
@ -100,22 +106,6 @@ def mock_all(aioclient_mock: AiohttpClientMocker, addon_installed, store_info) -
},
},
)
aioclient_mock.get(
"http://127.0.0.1/addons/test/stats",
json={
"result": "ok",
"data": {
"cpu_percent": 0.99,
"memory_usage": 182611968,
"memory_limit": 3977146368,
"memory_percent": 4.59,
"network_rx": 362570232,
"network_tx": 82374138,
"blk_read": 46010945536,
"blk_write": 15051526144,
},
},
)
aioclient_mock.get(
"http://127.0.0.1/core/stats",
json={
@ -148,8 +138,6 @@ def mock_all(aioclient_mock: AiohttpClientMocker, addon_installed, store_info) -
},
},
)
aioclient_mock.get("http://127.0.0.1/addons/test/changelog", text="")
aioclient_mock.get("http://127.0.0.1/addons/test2/changelog", text="")
aioclient_mock.get(
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)

View File

@ -1,7 +1,7 @@
"""Test Supervisor diagnostics."""
import os
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
import pytest
@ -18,7 +18,13 @@ MOCK_ENVIRON = {"SUPERVISOR": "127.0.0.1", "SUPERVISOR_TOKEN": "abcdefgh"}
@pytest.fixture(autouse=True)
def mock_all(aioclient_mock: AiohttpClientMocker, addon_installed, store_info) -> None:
def mock_all(
aioclient_mock: AiohttpClientMocker,
addon_installed: AsyncMock,
store_info: AsyncMock,
addon_stats: AsyncMock,
addon_changelog: AsyncMock,
) -> None:
"""Mock all setup requests."""
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"})
@ -103,22 +109,6 @@ def mock_all(aioclient_mock: AiohttpClientMocker, addon_installed, store_info) -
},
},
)
aioclient_mock.get(
"http://127.0.0.1/addons/test/stats",
json={
"result": "ok",
"data": {
"cpu_percent": 0.99,
"memory_usage": 182611968,
"memory_limit": 3977146368,
"memory_percent": 4.59,
"network_rx": 362570232,
"network_tx": 82374138,
"blk_read": 46010945536,
"blk_write": 15051526144,
},
},
)
aioclient_mock.get(
"http://127.0.0.1/core/stats",
json={
@ -151,8 +141,6 @@ def mock_all(aioclient_mock: AiohttpClientMocker, addon_installed, store_info) -
},
},
)
aioclient_mock.get("http://127.0.0.1/addons/test/changelog", text="")
aioclient_mock.get("http://127.0.0.1/addons/test2/changelog", text="")
aioclient_mock.get(
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)

View File

@ -201,20 +201,6 @@ async def test_api_homeassistant_restart(
assert aioclient_mock.call_count == 1
async def test_api_addon_stats(
hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test setup with API Add-on stats."""
aioclient_mock.get(
"http://127.0.0.1/addons/test/stats",
json={"result": "ok", "data": {"memory_percent": 0.01}},
)
data = await hassio_handler.get_addon_stats("test")
assert data["memory_percent"] == 0.01
assert aioclient_mock.call_count == 1
async def test_api_core_stats(
hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:

View File

@ -5,6 +5,7 @@ import os
from typing import Any
from unittest.mock import AsyncMock, patch
from aiohasupervisor.models import AddonsStats
import pytest
from voluptuous import Invalid
@ -52,7 +53,12 @@ def os_info(extra_os_info):
@pytest.fixture(autouse=True)
def mock_all(
aioclient_mock: AiohttpClientMocker, os_info, store_info, addon_info
aioclient_mock: AiohttpClientMocker,
os_info: AsyncMock,
store_info: AsyncMock,
addon_info: AsyncMock,
addon_stats: AsyncMock,
addon_changelog: AsyncMock,
) -> None:
"""Mock all setup requests."""
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
@ -156,64 +162,38 @@ def mock_all(
},
},
)
aioclient_mock.get(
"http://127.0.0.1/addons/test/stats",
json={
"result": "ok",
"data": {
"cpu_percent": 0.99,
"memory_usage": 182611968,
"memory_limit": 3977146368,
"memory_percent": 4.59,
"network_rx": 362570232,
"network_tx": 82374138,
"blk_read": 46010945536,
"blk_write": 15051526144,
},
},
)
aioclient_mock.get(
"http://127.0.0.1/addons/test2/stats",
json={
"result": "ok",
"data": {
"cpu_percent": 0.8,
"memory_usage": 51941376,
"memory_limit": 3977146368,
"memory_percent": 1.31,
"network_rx": 31338284,
"network_tx": 15692900,
"blk_read": 740077568,
"blk_write": 6004736,
},
},
)
aioclient_mock.get(
"http://127.0.0.1/addons/test3/stats",
json={
"result": "ok",
"data": {
"cpu_percent": 0.8,
"memory_usage": 51941376,
"memory_limit": 3977146368,
"memory_percent": 1.31,
"network_rx": 31338284,
"network_tx": 15692900,
"blk_read": 740077568,
"blk_write": 6004736,
},
},
)
aioclient_mock.get("http://127.0.0.1/addons/test/changelog", text="")
aioclient_mock.get(
"http://127.0.0.1/addons/test/info",
json={"result": "ok", "data": {"auto_update": True}},
)
aioclient_mock.get("http://127.0.0.1/addons/test2/changelog", text="")
aioclient_mock.get(
"http://127.0.0.1/addons/test2/info",
json={"result": "ok", "data": {"auto_update": False}},
)
async def mock_addon_stats(addon: str) -> AddonsStats:
"""Mock addon stats for test and test2."""
if addon in {"test2", "test3"}:
return AddonsStats(
cpu_percent=0.8,
memory_usage=51941376,
memory_limit=3977146368,
memory_percent=1.31,
network_rx=31338284,
network_tx=15692900,
blk_read=740077568,
blk_write=6004736,
)
return AddonsStats(
cpu_percent=0.99,
memory_usage=182611968,
memory_limit=3977146368,
memory_percent=4.59,
network_rx=362570232,
network_tx=82374138,
blk_read=46010945536,
blk_write=15051526144,
)
addon_stats.side_effect = mock_addon_stats
def mock_addon_info(slug: str):
addon_info.return_value.auto_update = slug == "test"
return addon_info.return_value
addon_info.side_effect = mock_addon_info
aioclient_mock.get(
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)

View File

@ -4,15 +4,12 @@ from datetime import timedelta
import os
from unittest.mock import AsyncMock, patch
from aiohasupervisor import SupervisorError
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant import config_entries
from homeassistant.components.hassio import (
DOMAIN,
HASSIO_UPDATE_INTERVAL,
HassioAPIError,
)
from homeassistant.components.hassio import DOMAIN, HASSIO_UPDATE_INTERVAL
from homeassistant.components.hassio.const import REQUEST_REFRESH_DELAY
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_UNAVAILABLE
@ -34,38 +31,11 @@ def mock_all(
aioclient_mock: AiohttpClientMocker,
addon_installed: AsyncMock,
store_info: AsyncMock,
addon_stats: AsyncMock,
addon_changelog: AsyncMock,
) -> None:
"""Mock all setup requests."""
_install_default_mocks(aioclient_mock)
_install_test_addon_stats_mock(aioclient_mock)
def _install_test_addon_stats_mock(aioclient_mock: AiohttpClientMocker):
"""Install mock to provide valid stats for the test addon."""
aioclient_mock.get(
"http://127.0.0.1/addons/test/stats",
json={
"result": "ok",
"data": {
"cpu_percent": 0.99,
"memory_usage": 182611968,
"memory_limit": 3977146368,
"memory_percent": 4.59,
"network_rx": 362570232,
"network_tx": 82374138,
"blk_read": 46010945536,
"blk_write": 15051526144,
},
},
)
def _install_test_addon_stats_failure_mock(aioclient_mock: AiohttpClientMocker):
"""Install mocks to raise an exception when fetching stats for the test addon."""
aioclient_mock.get(
"http://127.0.0.1/addons/test/stats",
exc=HassioAPIError,
)
def _install_default_mocks(aioclient_mock: AiohttpClientMocker):
@ -174,8 +144,6 @@ def _install_default_mocks(aioclient_mock: AiohttpClientMocker):
},
},
)
aioclient_mock.get("http://127.0.0.1/addons/test/changelog", text="")
aioclient_mock.get("http://127.0.0.1/addons/test2/changelog", text="")
aioclient_mock.get(
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)
@ -285,6 +253,7 @@ async def test_stats_addon_sensor(
entity_registry: er.EntityRegistry,
caplog: pytest.LogCaptureFixture,
freezer: FrozenDateTimeFactory,
addon_stats: AsyncMock,
) -> None:
"""Test stats addons sensor."""
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
@ -302,7 +271,7 @@ async def test_stats_addon_sensor(
aioclient_mock.clear_requests()
_install_default_mocks(aioclient_mock)
_install_test_addon_stats_failure_mock(aioclient_mock)
addon_stats.side_effect = SupervisorError
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
async_fire_time_changed(hass)
@ -312,7 +281,7 @@ async def test_stats_addon_sensor(
aioclient_mock.clear_requests()
_install_default_mocks(aioclient_mock)
_install_test_addon_stats_mock(aioclient_mock)
addon_stats.side_effect = None
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
async_fire_time_changed(hass)
@ -345,7 +314,7 @@ async def test_stats_addon_sensor(
aioclient_mock.clear_requests()
_install_default_mocks(aioclient_mock)
_install_test_addon_stats_failure_mock(aioclient_mock)
addon_stats.side_effect = SupervisorError
freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1))
async_fire_time_changed(hass)

View File

@ -4,7 +4,8 @@ from datetime import timedelta
import os
from unittest.mock import AsyncMock, patch
from aiohasupervisor import SupervisorBadRequestError
from aiohasupervisor import SupervisorBadRequestError, SupervisorError
from aiohasupervisor.models import StoreAddonUpdate
import pytest
from homeassistant.components.hassio import DOMAIN, HassioAPIError
@ -22,7 +23,13 @@ MOCK_ENVIRON = {"SUPERVISOR": "127.0.0.1", "SUPERVISOR_TOKEN": "abcdefgh"}
@pytest.fixture(autouse=True)
def mock_all(aioclient_mock: AiohttpClientMocker, addon_installed, store_info) -> None:
def mock_all(
aioclient_mock: AiohttpClientMocker,
addon_installed: AsyncMock,
store_info: AsyncMock,
addon_stats: AsyncMock,
addon_changelog: AsyncMock,
) -> None:
"""Mock all setup requests."""
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"})
@ -108,22 +115,6 @@ def mock_all(aioclient_mock: AiohttpClientMocker, addon_installed, store_info) -
},
},
)
aioclient_mock.get(
"http://127.0.0.1/addons/test/stats",
json={
"result": "ok",
"data": {
"cpu_percent": 0.99,
"memory_usage": 182611968,
"memory_limit": 3977146368,
"memory_percent": 4.59,
"network_rx": 362570232,
"network_tx": 82374138,
"blk_read": 46010945536,
"blk_write": 15051526144,
},
},
)
aioclient_mock.get(
"http://127.0.0.1/core/stats",
json={
@ -156,8 +147,6 @@ def mock_all(aioclient_mock: AiohttpClientMocker, addon_installed, store_info) -
},
},
)
aioclient_mock.get("http://127.0.0.1/addons/test/changelog", text="")
aioclient_mock.get("http://127.0.0.1/addons/test2/changelog", text="")
aioclient_mock.get(
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)
@ -227,9 +216,7 @@ async def test_update_entities(
assert state.attributes["auto_update"] is auto_update
async def test_update_addon(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
async def test_update_addon(hass: HomeAssistant, update_addon: AsyncMock) -> None:
"""Test updating addon update entity."""
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
config_entry.add_to_hass(hass)
@ -243,17 +230,13 @@ async def test_update_addon(
assert result
await hass.async_block_till_done()
aioclient_mock.post(
"http://127.0.0.1/addons/test/update",
json={"result": "ok", "data": {}},
)
await hass.services.async_call(
"update",
"install",
{"entity_id": "update.test_update"},
blocking=True,
)
update_addon.assert_called_once_with("test", StoreAddonUpdate(backup=False))
async def test_update_os(
@ -344,7 +327,8 @@ async def test_update_supervisor(
async def test_update_addon_with_error(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
hass: HomeAssistant,
update_addon: AsyncMock,
) -> None:
"""Test updating addon update entity with error."""
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
@ -358,11 +342,7 @@ async def test_update_addon_with_error(
)
await hass.async_block_till_done()
aioclient_mock.post(
"http://127.0.0.1/addons/test/update",
exc=HassioAPIError,
)
update_addon.side_effect = SupervisorError
with pytest.raises(HomeAssistantError, match=r"^Error updating test:"):
assert not await hass.services.async_call(
"update",
@ -610,19 +590,15 @@ async def test_setting_up_core_update_when_addon_fails(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
addon_installed: AsyncMock,
addon_stats: AsyncMock,
addon_changelog: AsyncMock,
) -> None:
"""Test setting up core update when single addon fails."""
addon_installed.side_effect = SupervisorBadRequestError("Addon Test does not exist")
addon_stats.side_effect = SupervisorBadRequestError("add-on is not running")
addon_changelog.side_effect = SupervisorBadRequestError("add-on is not running")
with (
patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.hassio.HassIO.get_addon_stats",
side_effect=HassioAPIError("add-on is not running"),
),
patch(
"homeassistant.components.hassio.HassIO.get_addon_changelog",
side_effect=HassioAPIError("add-on is not running"),
),
):
result = await async_setup_component(
hass,

View File

@ -7,15 +7,10 @@ from typing import Any
from unittest.mock import AsyncMock, Mock, patch
from aiohasupervisor import SupervisorError
from aiohasupervisor.models import AddonsOptions
import pytest
from homeassistant.components.hassio import (
AddonError,
AddonInfo,
AddonState,
HassIO,
HassioAPIError,
)
from homeassistant.components.hassio import AddonError, AddonInfo, AddonState, HassIO
from homeassistant.components.homeassistant_hardware import silabs_multiprotocol_addon
from homeassistant.components.zha import DOMAIN as ZHA_DOMAIN
from homeassistant.config_entries import ConfigEntry, ConfigFlow
@ -38,6 +33,11 @@ TEST_DOMAIN = "test"
TEST_DOMAIN_2 = "test_2"
@pytest.fixture(autouse=True)
def mock_supervisor_client(supervisor_client: AsyncMock) -> None:
"""Mock supervisor client."""
class FakeConfigFlow(ConfigFlow):
"""Handle a config flow for the silabs multiprotocol add-on."""
@ -253,16 +253,15 @@ async def test_option_flow_install_multi_pan_addon(
assert result["type"] is FlowResultType.SHOW_PROGRESS
assert result["step_id"] == "start_addon"
set_addon_options.assert_called_once_with(
hass,
"core_silabs_multiprotocol",
{
"options": {
AddonsOptions(
config={
"autoflash_firmware": True,
"device": "/dev/ttyTEST123",
"baudrate": "115200",
"flow_control": True,
}
},
),
)
await hass.async_block_till_done()
@ -336,16 +335,15 @@ async def test_option_flow_install_multi_pan_addon_zha(
assert result["type"] is FlowResultType.SHOW_PROGRESS
assert result["step_id"] == "start_addon"
set_addon_options.assert_called_once_with(
hass,
"core_silabs_multiprotocol",
{
"options": {
AddonsOptions(
config={
"autoflash_firmware": True,
"device": "/dev/ttyTEST123",
"baudrate": "115200",
"flow_control": True,
}
},
),
)
# Check the channel is initialized from ZHA
assert multipan_manager._channel == 11
@ -424,16 +422,15 @@ async def test_option_flow_install_multi_pan_addon_zha_other_radio(
assert result["type"] is FlowResultType.SHOW_PROGRESS
assert result["step_id"] == "start_addon"
set_addon_options.assert_called_once_with(
hass,
"core_silabs_multiprotocol",
{
"options": {
AddonsOptions(
config={
"autoflash_firmware": True,
"device": "/dev/ttyTEST123",
"baudrate": "115200",
"flow_control": True,
}
},
),
)
await hass.async_block_till_done()
@ -1204,7 +1201,7 @@ async def test_option_flow_install_multi_pan_addon_install_fails(
) -> None:
"""Test installing the multi pan addon."""
install_addon.side_effect = HassioAPIError("Boom")
install_addon.side_effect = SupervisorError("Boom")
# Setup the config entry
config_entry = MockConfigEntry(
@ -1283,16 +1280,15 @@ async def test_option_flow_install_multi_pan_addon_start_fails(
assert result["type"] is FlowResultType.SHOW_PROGRESS
assert result["step_id"] == "start_addon"
set_addon_options.assert_called_once_with(
hass,
"core_silabs_multiprotocol",
{
"options": {
AddonsOptions(
config={
"autoflash_firmware": True,
"device": "/dev/ttyTEST123",
"baudrate": "115200",
"flow_control": True,
}
},
),
)
await hass.async_block_till_done()
@ -1317,7 +1313,7 @@ async def test_option_flow_install_multi_pan_addon_set_options_fails(
) -> None:
"""Test installing the multi pan addon."""
set_addon_options.side_effect = HassioAPIError("Boom")
set_addon_options.side_effect = SupervisorError("Boom")
# Setup the config entry
config_entry = MockConfigEntry(
@ -1361,7 +1357,7 @@ async def test_option_flow_addon_info_fails(
) -> None:
"""Test installing the multi pan addon."""
addon_store_info.side_effect = HassioAPIError("Boom")
addon_store_info.side_effect = SupervisorError("Boom")
# Setup the config entry
config_entry = MockConfigEntry(
@ -1494,16 +1490,15 @@ async def test_option_flow_install_multi_pan_addon_zha_migration_fails_step_2(
assert result["type"] is FlowResultType.SHOW_PROGRESS
assert result["step_id"] == "start_addon"
set_addon_options.assert_called_once_with(
hass,
"core_silabs_multiprotocol",
{
"options": {
AddonsOptions(
config={
"autoflash_firmware": True,
"device": "/dev/ttyTEST123",
"baudrate": "115200",
"flow_control": True,
}
},
),
)
await hass.async_block_till_done()
@ -1668,7 +1663,7 @@ async def test_check_multi_pan_addon_info_error(
) -> None:
"""Test `check_multi_pan_addon` where the addon info cannot be read."""
addon_store_info.side_effect = HassioAPIError("Boom")
addon_store_info.side_effect = SupervisorError("Boom")
with pytest.raises(HomeAssistantError):
await silabs_multiprotocol_addon.check_multi_pan_addon(hass)

View File

@ -1318,7 +1318,7 @@ async def test_addon_not_installed_failures(
install_addon: AsyncMock,
) -> None:
"""Test add-on install failure."""
install_addon.side_effect = HassioAPIError()
install_addon.side_effect = SupervisorError()
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
@ -1355,7 +1355,7 @@ async def test_addon_not_installed_failures_zeroconf(
zeroconf_info: ZeroconfServiceInfo,
) -> None:
"""Test add-on install failure."""
install_addon.side_effect = HassioAPIError()
install_addon.side_effect = SupervisorError()
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf_info

View File

@ -389,7 +389,7 @@ async def test_addon_info_failure(
True,
1,
1,
HassioAPIError("Boom"),
SupervisorError("Boom"),
None,
ServerVersionTooOld("Invalid version"),
),

View File

@ -14,11 +14,7 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components import mqtt
from homeassistant.components.hassio import (
AddonError,
HassioAPIError,
HassioServiceInfo,
)
from homeassistant.components.hassio import AddonError, HassioServiceInfo
from homeassistant.components.mqtt.config_flow import PWD_NOT_CHANGED
from homeassistant.const import (
CONF_CLIENT_ID,
@ -253,7 +249,7 @@ async def test_user_connection_works(
assert len(mock_finish_setup.mock_calls) == 1
@pytest.mark.usefixtures("mqtt_client_mock", "supervisor")
@pytest.mark.usefixtures("mqtt_client_mock", "supervisor", "supervisor_client")
async def test_user_connection_works_with_supervisor(
hass: HomeAssistant,
mock_try_connection: MagicMock,
@ -856,7 +852,7 @@ async def test_addon_not_installed_failures(
Case: The Mosquitto add-on install fails.
"""
install_addon.side_effect = HassioAPIError()
install_addon.side_effect = SupervisorError()
result = await hass.config_entries.flow.async_init(
"mqtt", context={"source": config_entries.SOURCE_USER}

View File

@ -47,6 +47,7 @@ def enable_mocks_fixture(
"""Enable API mocks."""
@pytest.mark.usefixtures("supervisor_client")
async def test_import_dataset(
hass: HomeAssistant,
mock_async_zeroconf: MagicMock,
@ -201,6 +202,7 @@ async def test_import_share_radio_no_channel_collision(
)
@pytest.mark.usefixtures("supervisor_client")
@pytest.mark.parametrize("enable_compute_pskc", [True])
@pytest.mark.parametrize(
"dataset", [DATASET_INSECURE_NW_KEY, DATASET_INSECURE_PASSPHRASE]
@ -310,6 +312,7 @@ async def test_config_entry_update(hass: HomeAssistant) -> None:
mock_otrb_api.assert_called_once_with(new_config_entry_data["url"], ANY, ANY)
@pytest.mark.usefixtures("supervisor_client")
async def test_remove_entry(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, otbr_config_entry_multipan
) -> None:

View File

@ -1,6 +1,6 @@
"""Test OTBR Silicon Labs Multiprotocol support."""
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
import pytest
from python_otbr_api import ActiveDataSet, tlv_parser
@ -31,6 +31,11 @@ DATASET_CH16_PENDING = (
)
@pytest.fixture(autouse=True)
def mock_supervisor_client(supervisor_client: AsyncMock) -> None:
"""Mock supervisor client."""
async def test_async_change_channel(
hass: HomeAssistant, otbr_config_entry_multipan
) -> None:

View File

@ -13,6 +13,11 @@ OTBR_MULTIPAN_URL = "http://core-silabs-multiprotocol:8081"
OTBR_NON_MULTIPAN_URL = "/dev/ttyAMA1"
@pytest.fixture(autouse=True)
def mock_supervisor_client(supervisor_client: AsyncMock) -> None:
"""Mock supervisor client."""
async def test_get_allowed_channel(
hass: HomeAssistant, multiprotocol_addon_manager_mock
) -> None:

View File

@ -1,6 +1,6 @@
"""Test OTBR Websocket API."""
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
import pytest
import python_otbr_api
@ -29,6 +29,11 @@ async def websocket_client(
return await hass_ws_client(hass)
@pytest.fixture(autouse=True)
def mock_supervisor_client(supervisor_client: AsyncMock) -> None:
"""Mock supervisor client."""
async def test_get_info(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,

View File

@ -121,6 +121,11 @@ def backup(make_backup):
return make_backup()
@pytest.fixture(autouse=True)
def mock_supervisor_client(supervisor_client: AsyncMock) -> None:
"""Mock supervisor client."""
def mock_detect_radio_type(
radio_type: RadioType = RadioType.ezsp,
ret: ProbeResult = ProbeResult.RADIO_TYPE_DETECTED,
@ -772,6 +777,7 @@ async def test_user_flow_show_form(hass: HomeAssistant) -> None:
assert result["step_id"] == "choose_serial_port"
@pytest.mark.usefixtures("addon_not_installed")
@patch("serial.tools.list_ports.comports", MagicMock(return_value=[]))
async def test_user_flow_show_manual(hass: HomeAssistant) -> None:
"""Test user flow manual entry when no comport detected."""

View File

@ -7,6 +7,8 @@ from ipaddress import ip_address
from typing import Any
from unittest.mock import AsyncMock, MagicMock, call, patch
from aiohasupervisor import SupervisorError
from aiohasupervisor.models import AddonsOptions
import aiohttp
import pytest
from serial.tools.list_ports_common import ListPortInfo
@ -601,10 +603,9 @@ async def test_usb_discovery(
)
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{
"options": {
AddonsOptions(
config={
"device": USB_DISCOVERY_INFO.device,
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
@ -613,7 +614,7 @@ async def test_usb_discovery(
"lr_s2_access_control_key": "new654",
"lr_s2_authenticated_key": "new321",
}
},
),
)
assert result["type"] is FlowResultType.SHOW_PROGRESS
@ -702,10 +703,9 @@ async def test_usb_discovery_addon_not_running(
)
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{
"options": {
AddonsOptions(
config={
"device": USB_DISCOVERY_INFO.device,
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
@ -714,7 +714,7 @@ async def test_usb_discovery_addon_not_running(
"lr_s2_access_control_key": "new654",
"lr_s2_authenticated_key": "new321",
}
},
),
)
assert result["type"] is FlowResultType.SHOW_PROGRESS
@ -797,10 +797,9 @@ async def test_discovery_addon_not_running(
)
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{
"options": {
AddonsOptions(
config={
"device": "/test",
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
@ -809,7 +808,7 @@ async def test_discovery_addon_not_running(
"lr_s2_access_control_key": "new654",
"lr_s2_authenticated_key": "new321",
}
},
),
)
assert result["type"] is FlowResultType.SHOW_PROGRESS
@ -900,10 +899,9 @@ async def test_discovery_addon_not_installed(
)
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{
"options": {
AddonsOptions(
config={
"device": "/test",
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
@ -912,7 +910,7 @@ async def test_discovery_addon_not_installed(
"lr_s2_access_control_key": "new654",
"lr_s2_authenticated_key": "new321",
}
},
),
)
assert result["type"] is FlowResultType.SHOW_PROGRESS
@ -1182,7 +1180,7 @@ async def test_addon_running(
{"config": ADDON_DISCOVERY_INFO},
None,
None,
HassioAPIError(),
SupervisorError(),
"addon_info_failed",
),
],
@ -1313,10 +1311,9 @@ async def test_addon_installed(
)
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{
"options": {
AddonsOptions(
config={
"device": "/test",
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
@ -1325,7 +1322,7 @@ async def test_addon_installed(
"lr_s2_access_control_key": "new654",
"lr_s2_authenticated_key": "new321",
}
},
),
)
assert result["type"] is FlowResultType.SHOW_PROGRESS
@ -1366,7 +1363,7 @@ async def test_addon_installed(
@pytest.mark.parametrize(
("discovery_info", "start_addon_side_effect"),
[({"config": ADDON_DISCOVERY_INFO}, HassioAPIError())],
[({"config": ADDON_DISCOVERY_INFO}, SupervisorError())],
)
async def test_addon_installed_start_failure(
hass: HomeAssistant,
@ -1407,10 +1404,9 @@ async def test_addon_installed_start_failure(
)
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{
"options": {
AddonsOptions(
config={
"device": "/test",
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
@ -1419,7 +1415,7 @@ async def test_addon_installed_start_failure(
"lr_s2_access_control_key": "new654",
"lr_s2_authenticated_key": "new321",
}
},
),
)
assert result["type"] is FlowResultType.SHOW_PROGRESS
@ -1486,10 +1482,9 @@ async def test_addon_installed_failures(
)
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{
"options": {
AddonsOptions(
config={
"device": "/test",
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
@ -1498,7 +1493,7 @@ async def test_addon_installed_failures(
"lr_s2_access_control_key": "new654",
"lr_s2_authenticated_key": "new321",
}
},
),
)
assert result["type"] is FlowResultType.SHOW_PROGRESS
@ -1515,7 +1510,7 @@ async def test_addon_installed_failures(
@pytest.mark.parametrize(
("set_addon_options_side_effect", "discovery_info"),
[(HassioAPIError(), {"config": ADDON_DISCOVERY_INFO})],
[(SupervisorError(), {"config": ADDON_DISCOVERY_INFO})],
)
async def test_addon_installed_set_options_failure(
hass: HomeAssistant,
@ -1556,10 +1551,9 @@ async def test_addon_installed_set_options_failure(
)
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{
"options": {
AddonsOptions(
config={
"device": "/test",
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
@ -1568,7 +1562,7 @@ async def test_addon_installed_set_options_failure(
"lr_s2_access_control_key": "new654",
"lr_s2_authenticated_key": "new321",
}
},
),
)
assert result["type"] is FlowResultType.ABORT
@ -1634,10 +1628,9 @@ async def test_addon_installed_already_configured(
)
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{
"options": {
AddonsOptions(
config={
"device": "/new",
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
@ -1646,7 +1639,7 @@ async def test_addon_installed_already_configured(
"lr_s2_access_control_key": "new654",
"lr_s2_authenticated_key": "new321",
}
},
),
)
assert result["type"] is FlowResultType.SHOW_PROGRESS
@ -1719,10 +1712,9 @@ async def test_addon_not_installed(
)
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{
"options": {
AddonsOptions(
config={
"device": "/test",
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
@ -1731,7 +1723,7 @@ async def test_addon_not_installed(
"lr_s2_access_control_key": "new654",
"lr_s2_authenticated_key": "new321",
}
},
),
)
assert result["type"] is FlowResultType.SHOW_PROGRESS
@ -1774,7 +1766,7 @@ async def test_install_addon_failure(
hass: HomeAssistant, supervisor, addon_not_installed, install_addon
) -> None:
"""Test add-on install failure."""
install_addon.side_effect = HassioAPIError()
install_addon.side_effect = SupervisorError()
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
@ -1994,9 +1986,8 @@ async def test_options_addon_running(
new_addon_options["device"] = new_addon_options.pop("usb_path")
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{"options": new_addon_options},
AddonsOptions(config=new_addon_options),
)
assert client.disconnect.call_count == disconnect_calls
@ -2275,9 +2266,7 @@ async def test_options_different_device(
assert set_addon_options.call_count == 1
new_addon_options["device"] = new_addon_options.pop("usb_path")
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{"options": new_addon_options},
"core_zwave_js", AddonsOptions(config=new_addon_options)
)
assert client.disconnect.call_count == disconnect_calls
assert result["type"] is FlowResultType.SHOW_PROGRESS
@ -2298,9 +2287,7 @@ async def test_options_different_device(
assert set_addon_options.call_count == 2
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{"options": addon_options},
"core_zwave_js", AddonsOptions(config=addon_options)
)
assert result["type"] is FlowResultType.SHOW_PROGRESS
assert result["step_id"] == "start_addon"
@ -2357,7 +2344,7 @@ async def test_options_different_device(
"emulate_hardware": False,
},
0,
[HassioAPIError(), None],
[SupervisorError(), None],
),
(
{"config": ADDON_DISCOVERY_INFO},
@ -2387,8 +2374,8 @@ async def test_options_different_device(
},
0,
[
HassioAPIError(),
HassioAPIError(),
SupervisorError(),
SupervisorError(),
],
),
],
@ -2441,9 +2428,7 @@ async def test_options_addon_restart_failed(
assert set_addon_options.call_count == 1
new_addon_options["device"] = new_addon_options.pop("usb_path")
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{"options": new_addon_options},
"core_zwave_js", AddonsOptions(config=new_addon_options)
)
assert client.disconnect.call_count == disconnect_calls
assert result["type"] is FlowResultType.SHOW_PROGRESS
@ -2461,9 +2446,7 @@ async def test_options_addon_restart_failed(
old_addon_options.pop("network_key")
assert set_addon_options.call_count == 2
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{"options": old_addon_options},
"core_zwave_js", AddonsOptions(config=old_addon_options)
)
assert result["type"] is FlowResultType.SHOW_PROGRESS
assert result["step_id"] == "start_addon"
@ -2697,9 +2680,7 @@ async def test_options_addon_not_installed(
new_addon_options["device"] = new_addon_options.pop("usb_path")
assert set_addon_options.call_args == call(
hass,
"core_zwave_js",
{"options": new_addon_options},
"core_zwave_js", AddonsOptions(config=new_addon_options)
)
assert client.disconnect.call_count == disconnect_calls

View File

@ -6,6 +6,7 @@ import logging
from unittest.mock import AsyncMock, call, patch
from aiohasupervisor import SupervisorError
from aiohasupervisor.models import AddonsOptions
import pytest
from zwave_js_server.client import Client
from zwave_js_server.event import Event
@ -554,7 +555,7 @@ async def test_start_addon(
assert install_addon.call_count == 0
assert set_addon_options.call_count == 1
assert set_addon_options.call_args == call(
hass, "core_zwave_js", {"options": addon_options}
"core_zwave_js", AddonsOptions(config=addon_options)
)
assert start_addon.call_count == 1
assert start_addon.call_args == call("core_zwave_js")
@ -603,13 +604,13 @@ async def test_install_addon(
assert install_addon.call_args == call("core_zwave_js")
assert set_addon_options.call_count == 1
assert set_addon_options.call_args == call(
hass, "core_zwave_js", {"options": addon_options}
"core_zwave_js", AddonsOptions(config=addon_options)
)
assert start_addon.call_count == 1
assert start_addon.call_args == call("core_zwave_js")
@pytest.mark.parametrize("addon_info_side_effect", [HassioAPIError("Boom")])
@pytest.mark.parametrize("addon_info_side_effect", [SupervisorError("Boom")])
async def test_addon_info_failure(
hass: HomeAssistant,
addon_installed,
@ -747,7 +748,7 @@ async def test_addon_options_changed(
[
("1.0.0", True, 1, 1, None, None),
("1.0.0", False, 0, 0, None, None),
("1.0.0", True, 1, 1, HassioAPIError("Boom"), None),
("1.0.0", True, 1, 1, SupervisorError("Boom"), None),
("1.0.0", True, 0, 1, None, HassioAPIError("Boom")),
],
)